[
  {
    "path": ".ci/azure/deploy.yml",
    "content": "jobs:\n- job:\n  displayName: \"Deploy Docs\"\n  pool:\n    vmImage: ubuntu-latest\n\n  steps:\n    # No need to checkout the repo here!\n    - checkout: none\n\n    - bash: |\n        echo $IS_TAG\n        echo $IS_MAIN\n        echo $BRANCH_NAME\n      displayName: Report branch parameters\n\n    - task: DownloadPipelineArtifact@2\n      inputs:\n        buildType: 'current'\n        artifactName: 'html_docs'\n        targetPath: 'html'\n\n    - bash: |\n        ls -l html\n      displayName: Report downloaded cache contents.\n\n    - bash: |\n        git config --global user.name ${GH_NAME}\n        git config --global user.email ${GH_EMAIL}\n        git config --list | grep user.\n      displayName: 'Configure git'\n      env:\n        GH_NAME: $(gh.name)\n        GH_EMAIL: $(gh.email)\n\n    # upload documentation to discretize-docs gh-pages on tags\n    - bash: |\n        git clone -q --branch gh-pages --depth 1 https://${GH_TOKEN}@github.com/simpeg/discretize-docs.git\n      displayName: Checkout doc repository\n      env:\n        GH_TOKEN: $(gh.token)\n\n    - bash: |\n        cd discretize-docs\n        rm -rf \"en/$BRANCH_NAME\"\n        mv ../html \"en/$BRANCH_NAME\"\n        touch .nojekyll\n      displayName: Set Doc Folder\n\n    - bash: |\n        # Update latest symlink\n        cd discretize-docs/en\n        rm -f latest\n        ln -s \"$BRANCH_NAME\" latest\n      displayName: Point Latest to tag\n      condition: eq(variables.IS_TAG, true)\n\n    - bash: |\n        # Commit and push\n        cd discretize-docs\n        git add --all\n        git commit -am \"Azure CI commit ref $(Build.SourceVersion)\"\n        git push\n      displayName: Push documentation to discretize-docs\n      env:\n        GH_TOKEN: $(gh.token)\n"
  },
  {
    "path": ".ci/azure/docs.yml",
    "content": "jobs:\n- job: BuildDocs\n  displayName: \"Build Documentation\"\n  pool:\n    vmImage: ubuntu-latest\n  variables:\n    python.version: \"3.11\"\n    doc.build: True\n    PYVISTA_OFF_SCREEN: True\n    DISPLAY: \":99\"\n  steps:\n    - bash:\n        git fetch --tags\n      displayName: Fetch tags\n\n    - bash: echo \"##vso[task.prependpath]$CONDA/bin\"\n      displayName: Add conda to PATH\n\n    - bash: .ci/azure/setup_env.sh\n      displayName: Setup discretize environment\n\n    - bash: |\n        source activate discretize-test\n        make -C docs html\n      displayName: 'Building HTML'\n\n    - bash: |\n        source activate discretize-test\n        make -C docs linkcheck\n      displayName: 'Checking Links'\n\n    - task: PublishPipelineArtifact@1\n      inputs:\n        targetPath: 'docs/_build/html'\n        artifact: 'html_docs'\n        parallel: true\n"
  },
  {
    "path": ".ci/azure/run_tests.sh",
    "content": "#!/bin/bash\nset -ex #echo on and exit if any line fails\n\n# TF_BUILD is set to True on azure pipelines.\nis_azure=$(echo \"${TF_BUILD:-false}\" | tr '[:upper:]' '[:lower:]')\ndo_doc=$(echo \"${DOC_BUILD:-false}\" | tr '[:upper:]' '[:lower:]')\ndo_cov=$(echo \"${COVERAGE:-false}\" | tr '[:upper:]' '[:lower:]')\n\ntest_args=\"\"\n\nsource activate discretize-test\n\nif [[ \"$is_azure\" == \"true\" ]]; then\n  if [[ \"$do_doc\" == \"true\" ]]; then\n    .ci/setup_headless_display.sh\n  fi\nfi\nif [[ \"do_cov\" == \"true\" ]]; then\n  echo \"Testing with coverage\"\n  test_args=\"--cov --cov-config=pyproject.toml $test_args\"\nfi\n\npytest -vv $test_args\n\nif [[ \"do_cov\" == \"true\" ]]; then\n  coverage xml\nfi\n\n"
  },
  {
    "path": ".ci/azure/sdist.yml",
    "content": "jobs:\n- job:\n  displayName: \"Build source dist.\"\n  pool:\n    vmImage: ubuntu-latest\n  steps:\n    - task: UsePythonVersion@0\n      inputs:\n        versionSpec: \"3.11\"\n\n    - bash:\n        git fetch --tags\n      displayName: Fetch tags\n\n    - bash: |\n        set -o errexit\n        python -m pip install --upgrade pip\n        pip install build\n      displayName: Install source build tools.\n\n    - bash: |\n        python -m build --skip-dependency-check --sdist .\n        ls -la dist\n      displayName: Build Source\n\n    - task: PublishPipelineArtifact@1\n      inputs:\n        targetPath: 'dist'\n        artifact: 'source_dist'"
  },
  {
    "path": ".ci/azure/setup_env.sh",
    "content": "#!/bin/bash\nset -ex #echo on and exit if any line fails\n\n# TF_BUILD is set to True on azure pipelines.\nis_azure=$(echo \"${TF_BUILD:-false}\" | tr '[:upper:]' '[:lower:]')\ndo_doc=$(echo \"${DOC_BUILD:-false}\" | tr '[:upper:]' '[:lower:]')\nis_free_threaded=$(echo \"${PYTHON_FREETHREADING:-false}\" | tr '[:upper:]' '[:lower:]')\nis_rc=$(echo \"${PYTHON_RELEASE_CANDIDATE:-false}\" | tr '[:upper:]' '[:lower:]')\nis_bare=$(echo \"${ENVIRON_BARE:-false}\" | tr '[:upper:]' '[:lower:]')\n\nif [[ \"$is_azure\" == \"true\" ]]; then\n  if [[ \"$do_doc\" == \"true\" ]]; then\n    .ci/setup_headless_display.sh\n  fi\nfi\n\nif [[ \"$is_free_threaded\" == \"true\" || \"$is_bare\" == \"true\" ]]; then\n  cp .ci/environment_test_bare.yml environment_test_with_pyversion.yml\n  echo \"  - python-freethreading=\"$PYTHON_VERSION >> environment_test_with_pyversion.yml\nelse\n  cp .ci/environment_test.yml environment_test_with_pyversion.yml\n  echo \"  - python=\"$PYTHON_VERSION >> environment_test_with_pyversion.yml\nfi\n\nif [[ \"$is_rc\" == \"true\" ]]; then\n  sed -i '/^channels:/a\\  - conda-forge/label/python_rc' environment_test_with_pyversion.yml\nfi\nconda env create --file environment_test_with_pyversion.yml\nrm environment_test_with_pyversion.yml\n\nif [[ \"$is_azure\" == \"true\" ]]; then\n  source activate discretize-test\n  pip install pytest-azurepipelines\nelse\n  conda activate discretize-test\nfi\n\n# The --vsenv config setting will prefer msvc compilers on windows.\n# but will do nothing on mac and linux.\npip install --no-build-isolation --editable . --config-settings=setup-args=\"--vsenv\"\n\necho \"Conda Environment:\"\nconda list\n\necho \"Installed discretize version:\"\npython -c \"import discretize; print(discretize.__version__)\""
  },
  {
    "path": ".ci/azure/setup_miniconda_macos.sh",
    "content": "#!/bin/bash\nset -ex #echo on and exit if any line fails\n\necho \"arch is $ARCH\"\nif [[ $ARCH == \"X64\" ]]; then\n  MINICONDA_ARCH_LABEL=\"x86_64\"\nelse\n  MINICONDA_ARCH_LABEL=\"arm64\"\nfi\necho $MINICONDA_ARCH_LABEL\nmkdir -p ~/miniconda3\ncurl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-$MINICONDA_ARCH_LABEL.sh -o ~/miniconda3/miniconda.sh\nbash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3\nrm ~/miniconda3/miniconda.sh\necho \"##vso[task.setvariable variable=CONDA;]${HOME}/miniconda3\"\n"
  },
  {
    "path": ".ci/azure/style.yml",
    "content": "jobs:\n  - job:\n    displayName: Run style checks with Black\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n      - task: UsePythonVersion@0\n        inputs:\n          versionSpec: \"3.11\"\n      - bash: .ci/install_style.sh\n        displayName: \"Install dependencies to run the checks\"\n      - script: black --check .\n        displayName: \"Run black\"\n\n  - job:\n    displayName: Run (permissive) style checks with flake8\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n      - task: UsePythonVersion@0\n        inputs:\n          versionSpec: \"3.11\"\n      - bash: .ci/install_style.sh\n        displayName: \"Install dependencies to run the checks\"\n      - script: flake8\n        displayName: \"Run flake8\""
  },
  {
    "path": ".ci/azure/test.yml",
    "content": "jobs:\n- job:\n  strategy:\n    matrix:\n      linux-Python311:\n        image: ubuntu-latest\n        python.version: '3.11'\n        coverage: True\n      linux-Python312:\n        image: ubuntu-latest\n        python.version: '3.12'\n      linux-Python313:\n        image: ubuntu-latest\n        python.version: '3.13'\n      linux-Python313t:\n        image: ubuntu-latest\n        python.version: '3.13'\n        environ.bare: True\n        python.freethreading: True\n        coverage: True\n      linux-Python314:\n        image: ubuntu-latest\n        environ.bare: True\n        python.version: '3.14'\n      linux-Python314t:\n        image: ubuntu-latest\n        python.version: '3.14'\n        environ.bare: True\n        python.freethreading: True\n      osx-Python311:\n        image: macOS-latest\n        python.version: '3.11'\n      osx-Python312:\n        image: macOS-latest\n        python.version: '3.12'\n      osx-Python313:\n        image: macOS-latest\n        python.version: '3.13'\n      osx-Python313t:\n        image: macOS-latest\n        python.version: '3.13'\n        python.freethreading: True\n      osx-Python314:\n        image: macOS-latest\n        python.version: '3.14'\n        environ.bare: True\n      osx-Python314t:\n        image: macOS-latest\n        python.version: '3.14'\n        environ.bare: True\n        python.freethreading: True\n      win-Python311:\n        image: windows-latest\n        python.version: '3.11'\n      win-Python312:\n        image: windows-latest\n        python.version: '3.12'\n      win-Python313:\n        image: windows-latest\n        python.version: '3.13'\n      win-Python313t:\n        image: windows-latest\n        python.version: '3.13'\n        environ.bare: True\n        python.freethreading: True\n      win-Python314:\n        image: windows-latest\n        environ.bare: True\n        python.version: '3.14'\n      win-Python314t:\n        image: windows-latest\n        python.version: '3.14'\n        environ.bare: True\n        python.freethreading: True\n  displayName: \"${{ variables.image }} ${{ variables.python.version }}\"\n  pool:\n    vmImage: $(image)\n  variables:\n    varOS: $(Agent.OS)\n    ARCH: $(Agent.OSArchitecture)\n  steps:\n    - bash: .ci/azure/setup_miniconda_macos.sh\n      displayName: Install miniconda on mac\n      condition: eq(variables.varOS, 'Darwin')\n\n    - bash: echo \"##vso[task.prependpath]$CONDA/bin\"\n      displayName: Add conda to PATH\n      condition: ne(variables.varOS, 'Windows_NT')\n\n    - powershell: Write-Host \"##vso[task.prependpath]$env:CONDA\\Scripts\"\n      displayName: Add conda to PATH\n      condition: eq(variables.varOS, 'Windows_NT')\n\n    - bash: .ci/azure/setup_env.sh\n      displayName: Setup discretize environment\n\n    - bash: .ci/azure/run_tests.sh\n      displayName: 'Testing'\n\n    - bash: |\n        curl -Os https://uploader.codecov.io/latest/linux/codecov\n        chmod +x codecov\n        ./codecov\n      displayName: 'Upload coverage to codecov.io'\n      condition: variables.coverage\n"
  },
  {
    "path": ".ci/environment_test.yml",
    "content": "name: discretize-test\nchannels:\n  - conda-forge\ndependencies:\n  - numpy>=1.22.4\n  - scipy>=1.8\n\n  # optionals\n  - vtk>=6\n  - pyvista\n  - omf\n  - matplotlib\n\n  # documentation\n  - sphinx==8.1.3\n  - pydata-sphinx-theme==0.16.1\n  - sphinx-gallery==0.19.0\n  - numpydoc==1.9.0\n  - jupyter\n  - graphviz\n  - pillow\n  - pooch\n\n  # testing\n  - sympy\n  - pytest\n  - pytest-cov\n\n  # Building\n  - pip\n  - meson-python>=0.14.0\n  - meson\n  - ninja\n  - cython>=3.1.0\n  - setuptools_scm\n"
  },
  {
    "path": ".ci/environment_test_bare.yml",
    "content": "name: discretize-test\nchannels:\n  - conda-forge\ndependencies:\n  - numpy>=1.22.4\n  - scipy>=1.12\n\n  # testing\n  - sympy\n  - pytest\n  - pytest-cov\n\n  # Building\n  - pip\n  - meson-python>=0.14.0\n  - meson\n  - ninja\n  - cython>=3.1.0\n  - setuptools_scm\n"
  },
  {
    "path": ".ci/install_style.sh",
    "content": "#!/bin/bash\nset -ex #echo on and exit if any line fails\n\n# get directory of this script\nscript_dir=$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd )\nstyle_script=$script_dir/parse_style_requirements.py\n\n# parse the style requirements\nrequirements=$(python $style_script)\n\npip install $requirements\n\n"
  },
  {
    "path": ".ci/parse_style_requirements.py",
    "content": "import tomllib\nimport pathlib\n\nroot_dir = pathlib.Path(__file__).parent.parent.resolve()\npyproject_file = root_dir / \"pyproject.toml\"\n\nwith open(pyproject_file, \"rb\") as f:\n    pyproject = tomllib.load(f)\n\nstyle_requirements = pyproject[\"project\"][\"optional-dependencies\"][\"style\"]\nfor req in style_requirements:\n    print(req)\n"
  },
  {
    "path": ".ci/setup_headless_display.sh",
    "content": "#!/bin/sh\nset -x\n\nsudo apt update\n# Install items for headless pyvista display.\nsudo apt-get install -y \\\n  libglx-mesa0 \\\n  libgl1 \\\n  xvfb \\\n  x11-xserver-utils\n\n# qt dependents\nsudo apt-get install -y \\\n  libdbus-1-3 \\\n  libegl1 \\\n  libopengl0 \\\n  libosmesa6 \\\n  libxcb-cursor0 \\\n  libxcb-icccm4 \\\n  libxcb-image0 \\\n  libxcb-keysyms1 \\\n  libxcb-randr0 \\\n  libxcb-render-util0 \\\n  libxcb-shape0 \\\n  libxcb-xfixes0 \\\n  libxcb-xinerama0 \\\n  libxcb-xinput0 \\\n  libxkbcommon-x11-0 \\\n  mesa-utils \\\n  x11-utils\n\nwhich Xvfb\nXvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &\n\n# Debugging commands:\n# ls -l /etc/init.d/\n# sh -e /etc/init.d/xvfb start\n# give xvfb some time to start\nsleep 3\nset +x\n"
  },
  {
    "path": ".git_archival.txt",
    "content": "node: $Format:%H$\nnode-date: $Format:%cI$\ndescribe-name: $Format:%(describe:tags=true,match=*[0-9]*)$\nref-names: $Format:%D$"
  },
  {
    "path": ".gitattributes",
    "content": ".git_archival.txt  export-subst\n# Excluding files from an sdist generated by meson-python\n\n.azure-pipelines/* export-ignore\n.ci/* export-ignore\ndocs/* export-ignore\nexamples/* export-ignore\ntests/* export-ignore\ntutorials/* export-ignore\n\n.coveragerc export-ignore\n.flake8 export-ignore\n.git* export-ignore\n*.yml export-ignore\n*.yaml export-ignore\nrequirements_style.txt export-ignore"
  },
  {
    "path": ".github/workflows/build_distributions.yml",
    "content": "name: Build Distribution artifacts\n\non: [push, pull_request]\n\njobs:\n  build_wheels:\n    name: Build wheels on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        # macos-15-intel is an Intel runner, macos-14 is Apple silicon\n        os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-15-intel, macos-14]\n\n    steps:\n      - uses: actions/checkout@v5\n\n      - name: Build wheels\n        uses: pypa/cibuildwheel@v3.2.0\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}\n          path: ./wheelhouse/*.whl\n\n  build_sdist:\n    name: Build source distribution\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Build sdist\n        run: pipx run build --sdist\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: cibw-sdist\n          path: dist/*.tar.gz\n\n  upload_pypi:\n    needs: [build_wheels, build_sdist]\n    runs-on: ubuntu-latest\n    environment: pypi\n    permissions:\n      id-token: write\n    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          # unpacks all CIBW artifacts into dist/\n          pattern: cibw-*\n          path: dist\n          merge-multiple: true\n\n      - uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          user: __token__\n          password: ${{ secrets.PYPI_API_TOKEN }}\n          skip-existing: true\n          packages-dir: ./dist/\n"
  },
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\n.eggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n__pycache__\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\nnosetests.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n*.sublime-project\n*.sublime-workspace\n.DS_Store\ntree_ext.cpp\nsimplex_helpers.cpp\ninterputils_cython.c\n\n# Jupyter\n*.ipynb\n\n# docs\ndocs/_build/\ndocs/warnings.txt\ndocs/api/generated/*\ndocs/examples\ndocs/gallery/*\ndocs/tutorials/*\n\n# Examples data\n/examples/Chile_GRAV_4_Miller\n/examples/*.tar.gz\nexamples/Chile_GRAV_4_Miller\nexamples/*.tar.gz\n\n# downloads for examples\n*.tar.gz\n*.dat\n*.dir\n*.bak\n\n# setuptools_scm\ndiscretize/version.py\n\n.idea/\n\ndocs/sg_execution_times.rst\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/psf/black-pre-commit-mirror\n    rev: 24.3.0\n    hooks:\n      - id: black\n        language_version: python3.11\n  - repo: https://github.com/pycqa/flake8\n    rev: 7.0.0\n    hooks:\n      - id: flake8\n        language_version: python3.11\n        additional_dependencies:\n          - flake8-bugbear==23.12.2\n          - flake8-builtins==2.2.0\n          - flake8-mutable==1.2.0\n          - flake8-rst-docstrings==0.3.0\n          - flake8-docstrings==1.7.0\n          - flake8-pyproject==1.2.3"
  },
  {
    "path": "AUTHORS.rst",
    "content": "- Rowan Cockett, (`@rowanc1 <https://github.com/rowanc1/>`_)\n- Lindsey Heagy, (`@lheagy <https://github.com/lheagy/>`_)\n- Seogi Kang, (`@sgkang <https://github.com/sgkang/>`_)\n- Brendan Smithyman, (`@bsmithyman <https://github.com/bsmithyman/>`_)\n- Gudni Rosenkjaer, (`@grosenkj <https://github.com/grosenkj/>`_)\n- Dom Fournier, (`@fourndo <https://github.com/fourndo/>`_)\n- Dave Marchant, (`@dwfmarchant <https://github.com/dwfmarchant/>`_)\n- Lars Ruthotto, (`@lruthotto <https://github.com/lruthotto/>`_)\n- Mike Wathen, (`@wathenmp <https://github.com/wathenmp/>`_)\n- Luz Angelica Caudillo-Mata, (`@lacmajedrez <https://github.com/lacmajedrez/>`_)\n- Eldad Haber, (`@eldadhaber <https://github.com/eldadhaber/>`_)\n- Doug Oldenburg, (`@dougoldenburg <https://github.com/dougoldenburg/>`_)\n- Devin Cowan, (`@dccowan <https://github.com/dccowan/>`_)\n- Adam Pidlisecky, (`@aPid1 <https://github.com/aPid1/>`_)\n- Dieter Werthmüller, (`@prisae <https://github.com/prisae/>`_)\n- Bane Sullivan, (`@banesullivan <https://github.com/banesullivan>`_)\n- Joseph Capriotti, (`@jcapriot <https://github.com/jcapriot/>`_)\n"
  },
  {
    "path": "CITATION.rst",
    "content": "Citing discretize\n-----------------\n\nThere is a `paper about discretize <http://dx.doi.org/10.1016/j.cageo.2015.09.015>`_ as it is used in SimPEG,\nif you use this code, please help our scientific visibility by citing our work!\n\n\n    Cockett, R., Kang, S., Heagy, L. J., Pidlisecky, A., & Oldenburg, D. W. (2015). SimPEG: An open source framework for simulation and gradient based parameter estimation in geophysical applications. Computers & Geosciences.\n\n\nBibTex:\n\n.. code:: Latex\n\n    @article{Cockett2015,\n      title={SimPEG: An open source framework for simulation and gradient based parameter estimation in geophysical applications},\n      author={Cockett, Rowan and Kang, Seogi and Heagy, Lindsey J and Pidlisecky, Adam and Oldenburg, Douglas W},\n      journal={Computers \\& Geosciences},\n      year={2015},\n      publisher={Elsevier}\n    }\n\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013-2025 SimPEG Developers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.rst",
    "content": ".. image:: https://raw.github.com/simpeg/discretize/main/docs/images/discretize-logo.png\n    :alt: Discretize Logo\n\ndiscretize\n==========\n\n.. image:: https://img.shields.io/pypi/v/discretize.svg\n    :target: https://pypi.python.org/pypi/discretize\n    :alt: Latest PyPI version\n\n.. image:: https://anaconda.org/conda-forge/discretize/badges/version.svg\n    :target: https://anaconda.org/conda-forge/discretize\n    :alt: Latest conda-forge version\n\n.. image:: https://img.shields.io/github/license/simpeg/simpeg.svg\n    :target: https://github.com/simpeg/discretize/blob/main/LICENSE\n    :alt: MIT license\n\n.. image:: https://dev.azure.com/simpeg/discretize/_apis/build/status/simpeg.discretize?branchName=main\n    :target: https://dev.azure.com/simpeg/discretize/_build/latest?definitionId=1&branchName=main\n    :alt: Azure pipelines build status\n\n.. image:: https://codecov.io/gh/simpeg/discretize/branch/main/graph/badge.svg\n    :target: https://codecov.io/gh/simpeg/discretize\n    :alt: Coverage status\n\n.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.596411.svg\n   :target: https://doi.org/10.5281/zenodo.596411\n\n.. image:: https://img.shields.io/discourse/users?server=http%3A%2F%2Fsimpeg.discourse.group%2F\n    :target: http://simpeg.discourse.group/\n\n.. image:: https://img.shields.io/badge/simpeg-purple?logo=mattermost&label=Mattermost\n    :target: https://mattermost.softwareunderground.org/simpeg\n\n.. image:: https://img.shields.io/badge/Youtube%20channel-GeoSci.xyz-FF0000.svg?logo=youtube\n    :target: https://www.youtube.com/channel/UCBrC4M8_S4GXhyHht7FyQqw\n\n\n**discretize** - A python package for finite volume discretization.\n\nThe vision is to create a package for finite volume simulation with a\nfocus on large scale inverse problems.\nThis package has the following features:\n\n* modular with respect to the spacial discretization\n* built with the inverse problem in mind\n* supports 1D, 2D and 3D problems\n* access to sparse matrix operators\n* access to derivatives to mesh variables\n\n.. image:: https://raw.githubusercontent.com/simpeg/figures/master/finitevolume/cell-anatomy-tensor.png\n\nCurrently, discretize supports:\n\n* Tensor Meshes (1D, 2D and 3D)\n* Cylindrically Symmetric Meshes\n* QuadTree and OcTree Meshes (2D and 3D)\n* Logically Rectangular Meshes (2D and 3D)\n* Triangular (2D) and Tetrahedral (3D) Meshes\n\nInstalling\n^^^^^^^^^^\n**discretize** is on conda-forge, and is the recommended installation method.\n\n.. code:: shell\n\n    conda install -c conda-forge discretize\n\nPrebuilt wheels of **discretize** are on pypi for most platforms\n\n.. code:: shell\n\n    pip install discretize\n\nTo install from source, note this requires a `c++` compiler supporting the `c++17` standard.\n\n.. code:: shell\n\n    git clone https://github.com/simpeg/discretize.git\n    cd discretize\n    pip install .\n\nCiting discretize\n^^^^^^^^^^^^^^^^^\n\nPlease cite the SimPEG paper when using discretize in your work:\n\n\n    Cockett, R., Kang, S., Heagy, L. J., Pidlisecky, A., & Oldenburg, D. W. (2015). SimPEG: An open source framework for simulation and gradient based parameter estimation in geophysical applications. Computers & Geosciences.\n\n**BibTex:**\n\n.. code:: Latex\n\n    @article{cockett2015simpeg,\n      title={SimPEG: An open source framework for simulation and gradient based parameter estimation in geophysical applications},\n      author={Cockett, Rowan and Kang, Seogi and Heagy, Lindsey J and Pidlisecky, Adam and Oldenburg, Douglas W},\n      journal={Computers \\& Geosciences},\n      year={2015},\n      publisher={Elsevier}\n    }\n\nLinks\n^^^^^\n\nWebsite:\nhttp://simpeg.xyz\n\nDocumentation:\nhttp://discretize.simpeg.xyz\n\nCode:\nhttps://github.com/simpeg/discretize\n\nTests:\nhttps://dev.azure.com/simpeg/discretize/_build\n\nBugs & Issues:\nhttps://github.com/simpeg/discretize/issues\n\nQuestions:\nhttp://simpeg.discourse.group/\n\nChat:\nhttps://mattermost.softwareunderground.org/simpeg\n\n"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "trigger:\n  branches:\n    include:\n    - 'main'\n    exclude:\n    - '*no-ci*'\n  tags:\n    include:\n    - '*'\n\npr:\n  branches:\n    include:\n    - '*'\n    exclude:\n    - '*no-ci*'\n\nvariables:\n  BRANCH_NAME: $(Build.SourceBranchName)\n  IS_TAG: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')]\n  IS_MAIN: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]\n\nstages:\n  - stage: StyleChecks\n    displayName: \"Style Checks\"\n    jobs:\n      - template: .ci/azure/style.yml\n\n  - stage: Testing\n    dependsOn: StyleChecks\n    jobs:\n      - template: .ci/azure/test.yml\n\n  - stage: DocBuild\n    dependsOn: StyleChecks\n    jobs:\n      - template: .ci/azure/docs.yml\n\n  - stage: Deploy\n    displayName: \"Deploy Docs\"\n    dependsOn:\n      - Testing\n      - DocBuild\n    condition: and(succeeded(), or(eq(variables.IS_TAG, true), eq(variables.IS_MAIN, true)))\n    jobs:\n      - template: .ci/azure/deploy.yml\n\n\n"
  },
  {
    "path": "discretize/Tests/__init__.py",
    "content": "from discretize.tests import *  # NOQA F401,F403\nfrom discretize.utils.code_utils import deprecate_module\n\n# note this needs to be a module with an __init__ so we can avoid name clash\n# with tests.py in the discretize directory on systems that are agnostic to Case.\ndeprecate_module(\n    \"discretize.Tests\", \"discretize.tests\", removal_version=\"1.0.0\", error=True\n)\n"
  },
  {
    "path": "discretize/Tests/meson.build",
    "content": "\npython_sources = [\n  '__init__.py',\n]\n\npy.install_sources(\n  python_sources,\n  subdir: 'discretize/Tests'\n)\n"
  },
  {
    "path": "discretize/View.py",
    "content": "\"\"\"Deprecated view module.\"\"\"\n\nfrom discretize.utils.code_utils import deprecate_module\n\ndeprecate_module(\n    \"discretize.View\",\n    \"discretize.mixins.mpl_mod\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\ntry:\n    from discretize.mixins.mpl_mod import Slicer  # NOQA F401\nexcept ImportError:\n    pass\n"
  },
  {
    "path": "discretize/__init__.py",
    "content": "\"\"\"\n=====================================\nDiscretize Meshes (:mod:`discretize`)\n=====================================\n.. currentmodule:: discretize\n\nThe ``discretize`` package contains four types of meshes for soliving partial differential\nequations using the finite volume method.\n\nMesh Classes\n============\n.. autosummary::\n  :toctree: generated/\n\n  TensorMesh\n  CylindricalMesh\n  CurvilinearMesh\n  TreeMesh\n  SimplexMesh\n\nMesh Cells\n==========\nThe :class:`~discretize.tensor_cell.TensorCell` and\n:class:`~discretize.tree_mesh.TreeCell` classes were designed specifically to\ndefine the cells within tensor and tree meshes, respectively.\nInstances of :class:`~discretize.tree_mesh.TreeCell` and\n:class:`~discretize.tensor_cell.TensorCell` are not meant to be created on\ntheir own.\nHowever, they can be returned directly by indexing a particular cell within\na tensor or tree mesh.\n\n.. autosummary::\n  :toctree: generated/\n\n  tensor_cell.TensorCell\n  tree_mesh.TreeCell\n\"\"\"\n\nfrom discretize.tensor_mesh import TensorMesh\nfrom discretize.cylindrical_mesh import CylMesh, CylindricalMesh\nfrom discretize.curvilinear_mesh import CurvilinearMesh\nfrom discretize.unstructured_mesh import SimplexMesh\nfrom discretize.utils.io_utils import load_mesh\nfrom .tensor_cell import TensorCell\n\ntry:\n    from discretize.tree_mesh import TreeMesh\nexcept ImportError as err:\n    import os\n\n    # Check if being called from non-standard location (i.e. a git repository)\n    # is tree_ext.pyx here? will not be in the folder if installed to site-packages...\n    file_test = os.path.dirname(os.path.abspath(__file__)) + \"/_extensions/tree_ext.pyx\"\n    if os.path.isfile(file_test):\n        # Then we are being run from a repository\n        raise ImportError(\n            \"\"\"\n            It would appear that discretize is being imported from its source code\n            directory and is unable to load its compiled extension modules. Try changing\n            your directory and re-launching your python interpreter.\n\n            If this was intentional, you need to install discretize in an editable mode.\n            \"\"\"\n        )\n    else:\n        raise err\nfrom discretize import tests\n\n__author__ = \"SimPEG Team\"\n__license__ = \"MIT\"\n__copyright__ = \"2013 - 2023, SimPEG Developers, https://simpeg.xyz\"\n\nfrom importlib.metadata import version, PackageNotFoundError\n\n# Version\ntry:\n    # - Released versions just tags:       0.8.0\n    # - GitHub commits add .dev#+hash:     0.8.1.dev4+g2785721\n    # - Uncommitted changes add timestamp: 0.8.1.dev4+g2785721.d20191022\n    __version__ = version(\"discretize\")\nexcept PackageNotFoundError:\n    # If it was not installed, then we don't know the version. We could throw a\n    # warning here, but this case *should* be rare. discretize should be\n    # installed properly!\n    from datetime import datetime\n\n    __version__ = \"unknown-\" + datetime.today().strftime(\"%Y%m%d\")\n"
  },
  {
    "path": "discretize/_extensions/__init__.py",
    "content": ""
  },
  {
    "path": "discretize/_extensions/geom.cpp",
    "content": "#include <algorithm>\n#include <limits>\n#include \"geom.h\"\n#include <algorithm>\n#include <limits>\n\n// Define the 3D cross product as a pre-processor macro\n#define CROSS3D(e0, e1, out) \\\n    out[0] = e0[1] * e1[2] - e0[2] * e1[1]; \\\n    out[1] = e0[2] * e1[0] - e0[0] * e1[2]; \\\n    out[2] = e0[0] * e1[1] - e0[1] * e1[0];\n\n// simple geometric objects for intersection tests with an aabb\n\nGeometric::Geometric(){\n    dim = 0;\n}\n\nGeometric::Geometric(int_t dim){\n    this->dim = dim;\n}\n\nBall::Ball() : Geometric(){\n    x0 = NULL;\n    r = 0;\n    rsq = 0;\n}\n\nBall::Ball(int_t dim, double* x0, double r) : Geometric(dim){\n    this->x0 = x0;\n    this->r = r;\n    this->rsq = r * r;\n}\n\nbool Ball::intersects_cell(double *a, double *b) const{\n    // check if I intersect the ball\n    double dx;\n    double r2_test = 0.0;\n    for(int_t i=0; i<dim; ++i){\n        dx = std::max(a[i], std::min(x0[i], b[i])) - x0[i];\n        r2_test += dx * dx;\n    }\n    return r2_test < rsq;\n}\n\nLine::Line() : Geometric(){\n    x0 = NULL;\n    x1 = NULL;\n    for(int_t i=0; i<3; ++i) inv_dx[i] = 1;\n}\n\nLine::Line(int_t dim, double* x0, double *x1) : Geometric(dim){\n    this->x0 = x0;\n    this->x1 = x1;\n    for(int_t i=0; i<dim; ++i){\n        inv_dx[i] = 1.0/(x1[i] - x0[i]);\n    }\n}\n\nbool Line::intersects_cell(double *a, double *b) const{\n    double t_near = -std::numeric_limits<double>::infinity();\n    double t_far = std::numeric_limits<double>::infinity();\n    double t0, t1;\n\n    for(int_t i=0; i<dim; ++i){\n        // do a quick test if the line has no chance of intersecting the aabb\n        if(x0[i] == x1[i] && (x0[i] < a[i] || x0[i] > b[i])){\n            return false;\n        }\n        if(std::max(x0[i], x1[i]) < a[i]){\n            return false;\n        }\n        if(std::min(x0[i], x1[i]) > b[i]){\n            return false;\n        }\n        if (x0[i] != x1[i]){\n            t0 = (a[i] - x0[i]) * inv_dx[i];\n            t1 = (b[i] - x0[i]) * inv_dx[i];\n            if (t0 > t1){\n                std::swap(t0, t1);\n            }\n            t_near = std::max(t_near, t0);\n            t_far = std::min(t_far, t1);\n            if (t_near > t_far || t_far < 0 || t_near > 1){\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\nBox::Box() : Geometric(){\n    x0 = NULL;\n    x1 = NULL;\n}\n\nBox::Box(int_t dim, double* x0, double *x1) : Geometric(dim){\n    this->x0 = x0;\n    this->x1 = x1;\n}\n\nbool Box::intersects_cell(double *a, double *b) const{\n    for(int_t i=0; i<dim; ++i){\n        if(std::max(x0[i], x1[i]) < a[i]){\n            return false;\n        }\n        if(std::min(x0[i], x1[i]) > b[i]){\n            return false;\n        }\n    }\n    return true;\n}\n\nPlane::Plane() : Geometric(){\n    origin = NULL;\n    normal = NULL;\n}\n\nPlane::Plane(int_t dim, double* origin, double *normal) : Geometric(dim){\n    this->origin = origin;\n    this->normal = normal;\n}\n\nbool Plane::intersects_cell(double *a, double *b) const{\n    double center;\n    double half_width;\n    double s = 0.0;\n    double r = 0.0;\n    for(int_t i=0;i<dim;++i){\n        center = (b[i] + a[i]) * 0.5;\n        half_width = center - a[i];\n        r += half_width * std::abs(normal[i]);\n        s += normal[i] * (center - origin[i]);\n    }\n    return std::abs(s) <= r;\n}\n\nTriangle::Triangle() : Geometric(){\n    x0 = NULL;\n    x1 = NULL;\n    x2 = NULL;\n    for(int_t i=0; i<3; ++i){\n        e0[i] = 0.0;\n        e1[i] = 0.0;\n        e2[i] = 0.0;\n        normal[i] = 0.0;\n    }\n}\n\nTriangle::Triangle(int_t dim, double* x0, double *x1, double *x2) : Geometric(dim){\n    this->x0 = x0;\n    this->x1 = x1;\n    this->x2 = x2;\n\n    for(int_t i=0; i<dim; ++i){\n      e0[i] = x1[i] - x0[i];\n      e1[i] = x2[i] - x1[i];\n      e2[i] = x2[i] - x0[i];\n    }\n    if(dim > 2){\n        normal[0] = e0[1] * e1[2] - e0[2] * e1[1];\n        normal[1] = e0[2] * e1[0] - e0[0] * e1[2];\n        normal[2] = e0[0] * e1[1] - e0[1] * e1[0];\n    }\n}\n\nbool Triangle::intersects_cell(double *a, double *b) const{\n    double center;\n    double v0[3], v1[3], v2[3], half[3];\n    double vmin, vmax;\n    double p0, p1, p2, pmin, pmax, rad;\n    for(int_t i=0; i < dim; ++i){\n        center = 0.5 * (b[i] + a[i]);\n        v0[i] = x0[i] - center;\n        v1[i] = x1[i] - center;\n        vmin = std::min(v0[i], v1[i]);\n        vmax = std::max(v0[i], v1[i]);\n        v2[i] = x2[i] - center;\n        vmin = std::min(vmin, v2[i]);\n        vmax = std::max(vmax, v2[i]);\n        half[i] = center - a[i];\n\n        // Bounding box check\n        if (vmin > half[i] || vmax < -half[i]){\n            return false;\n        }\n    }\n    // first do the 3 edge cross tests that apply in 2D and 3D\n\n    // edge 0 cross z_hat\n    //p0 = e0[1] * v0[0] - e0[0] * v0[1];\n    p1 = e0[1] * v1[0] - e0[0] * v1[1];\n    p2 = e0[1] * v2[0] - e0[0] * v2[1];\n    pmin = std::min(p1, p2);\n    pmax = std::max(p1, p2);\n    rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n\n    // edge 1 cross z_hat\n    p0 = e1[1] * v0[0] - e1[0] * v0[1];\n    p1 = e1[1] * v1[0] - e1[0] * v1[1];\n    //p2 = e1[1] * v2[0] - e1[0] * v2[1];\n    pmin = std::min(p0, p1);\n    pmax = std::max(p0, p1);\n    rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n\n    // edge 2 cross z_hat\n    //p0 = e2[1] * v0[0] - e2[0] * v0[1];\n    p1 = e2[1] * v1[0] - e2[0] * v1[1];\n    p2 = e2[1] * v2[0] - e2[0] * v2[1];\n    pmin = std::min(p1, p2);\n    pmax = std::max(p1, p2);\n    rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n\n    if(dim > 2){\n        // edge 0 cross x_hat\n        p0 = e0[2] * v0[1] - e0[1] * v0[2];\n        //p1 = e0[2] * v1[1] - e0[1] * v1[2];\n        p2 = e0[2] * v2[1] - e0[1] * v2[2];\n        pmin = std::min(p0, p2);\n        pmax = std::max(p0, p2);\n        rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n        // edge 0 cross y_hat\n        p0 = -e0[2] * v0[0] + e0[0] * v0[2];\n        //p1 = -e0[2] * v1[0] + e0[0] * v1[2];\n        p2 = -e0[2] * v2[0] + e0[0] * v2[2];\n        pmin = std::min(p0, p2);\n        pmax = std::max(p0, p2);\n        rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n        // edge 1 cross x_hat\n        p0 = e1[2] * v0[1] - e1[1] * v0[2];\n        //p1 = e1[2] * v1[1] - e1[1] * v1[2];\n        p2 = e1[2] * v2[1] - e1[1] * v2[2];\n        pmin = std::min(p0, p2);\n        pmax = std::max(p0, p2);\n        rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n        // edge 1 cross y_hat\n        p0 = -e1[2] * v0[0] + e1[0] * v0[2];\n        //p1 = -e1[2] * v1[0] + e1[0] * v1[2];\n        p2 = -e1[2] * v2[0] + e1[0] * v2[2];\n        pmin = std::min(p0, p2);\n        pmax = std::max(p0, p2);\n        rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n        // edge 2 cross x_hat\n        p0 = e2[2] * v0[1] - e2[1] * v0[2];\n        p1 = e2[2] * v1[1] - e2[1] * v1[2];\n        //p2 = e2[2] * v2[1] - e2[1] * v2[2];\n        pmin = std::min(p0, p1);\n        pmax = std::max(p0, p1);\n        rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n        // edge 2 cross y_hat\n        p0 = -e2[2] * v0[0] + e2[0] * v0[2];\n        p1 = -e2[2] * v1[0] + e2[0] * v1[2];\n        //p2 = -e2[2] * v2[0] + e2[0] * v2[2];\n        pmin = std::min(p0, p1);\n        pmax = std::max(p0, p1);\n        rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n\n        // triangle normal axis\n        pmin = 0.0;\n        pmax = 0.0;\n        for(int_t i=0; i<dim; ++i){\n            if(normal[i] > 0){\n                pmin += normal[i] * (-half[i] - v0[i]);\n                pmax += normal[i] * (half[i] - v0[i]);\n            }else{\n                pmin += normal[i] * (half[i] - v0[i]);\n                pmax += normal[i] * (-half[i] - v0[i]);\n            }\n        }\n        if (pmin > 0 || pmax < 0){\n            return false;\n        }\n    }\n    return true;\n}\n\nVerticalTriangularPrism::VerticalTriangularPrism() : Triangle(){\n    h = 0;\n}\n\nVerticalTriangularPrism::VerticalTriangularPrism(int_t dim, double* x0, double *x1, double *x2, double h) : Triangle(dim, x0, x1, x2){\n    this->h = h;\n}\n\nbool VerticalTriangularPrism::intersects_cell(double *a, double *b) const{\n    double center;\n    double v0[3], v1[3], v2[3], half[3];\n    double vmin, vmax;\n    double p0, p1, p2, p3, pmin, pmax, rad;\n    for(int_t i=0; i < dim; ++i){\n        center = 0.5 * (a[i] + b[i]);\n        v0[i] = x0[i] - center;\n        v1[i] = x1[i] - center;\n        vmin = std::min(v0[i], v1[i]);\n        vmax = std::max(v0[i], v1[i]);\n        v2[i] = x2[i] - center;\n        vmin = std::min(vmin, v2[i]);\n        vmax = std::max(vmax, v2[i]);\n        if(i == 2){\n            vmax += h;\n        }\n        half[i] = center - a[i];\n\n        // Bounding box check\n        if (vmin > half[i] || vmax < -half[i]){\n            return false;\n        }\n    }\n    // first do the 3 edge cross tests that apply in 2D and 3D\n\n    // edge 0 cross z_hat\n    //p0 = e0[1] * v0[0] - e0[0] * v0[1];\n    p1 = e0[1] * v1[0] - e0[0] * v1[1];\n    p2 = e0[1] * v2[0] - e0[0] * v2[1];\n    pmin = std::min(p1, p2);\n    pmax = std::max(p1, p2);\n    rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n\n    // edge 1 cross z_hat\n    p0 = e1[1] * v0[0] - e1[0] * v0[1];\n    p1 = e1[1] * v1[0] - e1[0] * v1[1];\n    //p2 = e1[1] * v2[0] - e1[0] * v2[1];\n    pmin = std::min(p0, p1);\n    pmax = std::max(p0, p1);\n    rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n\n    // edge 2 cross z_hat\n    //p0 = e2[1] * v0[0] - e2[0] * v0[1];\n    p1 = e2[1] * v1[0] - e2[0] * v1[1];\n    p2 = e2[1] * v2[0] - e2[0] * v2[1];\n    pmin = std::min(p1, p2);\n    pmax = std::max(p1, p2);\n    rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n\n    // edge 0 cross x_hat\n    p0 = e0[2] * v0[1] - e0[1] * v0[2];\n    p1 = e0[2] * v0[1] - e0[1] * (v0[2] + h);\n    p2 = e0[2] * v2[1] - e0[1] * v2[2];\n    p3 = e0[2] * v2[1] - e0[1] * (v2[2] + h);\n    pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n    pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n    rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n    // edge 0 cross y_hat\n    p0 = -e0[2] * v0[0] + e0[0] * v0[2];\n    p1 = -e0[2] * v0[0] + e0[0] * (v0[2] + h);\n    p2 = -e0[2] * v2[0] + e0[0] * v2[2];\n    p3 = -e0[2] * v2[0] + e0[0] * (v2[2] + h);\n    pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n    pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n    rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n    // edge 1 cross x_hat\n    p0 = e1[2] * v0[1] - e1[1] * v0[2];\n    p1 = e1[2] * v0[1] - e1[1] * (v0[2] + h);\n    p2 = e1[2] * v2[1] - e1[1] * v2[2];\n    p3 = e1[2] * v2[1] - e1[1] * (v2[2] + h);\n    pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n    pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n    rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n    // edge 1 cross y_hat\n    p0 = -e1[2] * v0[0] + e1[0] * v0[2];\n    p1 = -e1[2] * v0[0] + e1[0] * (v0[2] + h);\n    p2 = -e1[2] * v2[0] + e1[0] * v2[2];\n    p3 = -e1[2] * v2[0] + e1[0] * (v2[2] + h);\n    pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n    pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n    rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n    // edge 2 cross x_hat\n    p0 = e2[2] * v0[1] - e2[1] * v0[2];\n    p1 = e2[2] * v0[1] - e2[1] * (v0[2] + h);\n    p2 = e2[2] * v1[1] - e2[1] * v1[2];\n    p3 = e2[2] * v1[1] - e2[1] * (v1[2] + h);\n    pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n    pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n    rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n    // edge 2 cross y_hat\n    p0 = -e2[2] * v0[0] + e2[0] * v0[2];\n    p1 = -e2[2] * v0[0] + e2[0] * (v0[2] + h);\n    p2 = -e2[2] * v1[0] + e2[0] * v1[2];\n    p3 = -e2[2] * v1[0] + e2[0] * (v1[2] + h);\n    pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n    pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n    rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n\n    // triangle normal axis\n    p0 = normal[0] * v0[0] + normal[1] * v0[1] + normal[2] * v0[2];\n    p1 = normal[0] * v0[0] + normal[1] * v0[1] + normal[2] * (v0[2] + h);\n    pmin = std::min(p0, p1);\n    pmax = std::max(p0, p1);\n    rad = std::abs(normal[0]) * half[0] + std::abs(normal[1]) * half[1] + std::abs(normal[2]) * half[2];\n    if (pmin > rad || pmax < -rad){\n        return false;\n    }\n    // the axes defined by the three vertical prism faces\n    // should already be tested by the e0, e1, e2 cross z_hat tests\n    return true;\n}\n\nTetrahedron::Tetrahedron() : Geometric(){\n    x0 = NULL;\n    x1 = NULL;\n    x2 = NULL;\n    x3 = NULL;\n    for(int_t i=0; i<6; ++i){\n        for(int_t j=0; j<3; ++j){\n            edge_tans[i][j] = 0.0;\n        }\n    }\n    for(int_t i=0; i<4; ++i){\n        for(int_t j=0; j<3; ++j){\n            face_normals[i][j] = 0.0;\n        }\n    }\n}\n\nTetrahedron::Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double *x3) : Geometric(dim){\n    this->x0 = x0;\n    this->x1 = x1;\n    this->x2 = x2;\n    this->x3 = x3;\n    for(int_t i=0; i<dim; ++i){\n        edge_tans[0][i] = x1[i] - x0[i];\n        edge_tans[1][i] = x2[i] - x0[i];\n        edge_tans[2][i] = x2[i] - x1[i];\n        edge_tans[3][i] = x3[i] - x0[i];\n        edge_tans[4][i] = x3[i] - x1[i];\n        edge_tans[5][i] = x3[i] - x2[i];\n    }\n    // cross e0, e1 (x0, x1, x2)\n    CROSS3D(edge_tans[0], edge_tans[1], face_normals[0])\n\n    // cross e0, e3 (x0, x1, x3)\n    CROSS3D(edge_tans[0], edge_tans[3], face_normals[1])\n\n    // cross e1, e3 (x0, x2, x3)\n    CROSS3D(edge_tans[1], edge_tans[3], face_normals[2])\n\n    // cross e2, e5 (x1, x2, x3)\n    CROSS3D(edge_tans[2], edge_tans[5], face_normals[3])\n}\n\nbool Tetrahedron::intersects_cell(double *a, double *b) const{\n    double v0[3], v1[3], v2[3], v3[3], half[3];\n    double p0, p1, p2, p3, pmin, pmax, rad;\n    double center;\n    for(int_t i=0; i < dim; ++i){\n        center = 0.5 * (a[i] + b[i]);\n        v0[i] = x0[i] - center;\n        v1[i] = x1[i] - center;\n        v2[i] = x2[i] - center;\n        v3[i] = x3[i] - center;\n        half[i] = center - a[i];\n        pmin = std::min(std::min(std::min(v0[i], v1[i]), v2[i]), v3[i]);\n        pmax = std::max(std::max(std::max(v0[i], v1[i]), v2[i]), v3[i]);\n        // Bounding box check\n        if (pmin > half[i] || pmax < -half[i]){\n            return false;\n        }\n    }\n    // first do the 3 edge cross tests that apply in 2D and 3D\n    const double *axis;\n\n    for(int_t i=0; i<6; ++i){\n        // edge cross [1, 0, 0]\n        p0 = edge_tans[i][2] * v0[1] - edge_tans[i][1] * v0[2];\n        p1 = edge_tans[i][2] * v1[1] - edge_tans[i][1] * v1[2];\n        p2 = edge_tans[i][2] * v2[1] - edge_tans[i][1] * v2[2];\n        p3 = edge_tans[i][2] * v3[1] - edge_tans[i][1] * v3[2];\n        pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n        pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n        rad = std::abs(edge_tans[i][2]) * half[1] + std::abs(edge_tans[i][1]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n\n        p0 = -edge_tans[i][2] * v0[0] + edge_tans[i][0] * v0[2];\n        p1 = -edge_tans[i][2] * v1[0] + edge_tans[i][0] * v1[2];\n        p2 = -edge_tans[i][2] * v2[0] + edge_tans[i][0] * v2[2];\n        p3 = -edge_tans[i][2] * v3[0] + edge_tans[i][0] * v3[2];\n        pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n        pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n        rad = std::abs(edge_tans[i][2]) * half[0] + std::abs(edge_tans[i][0]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n\n        p0 = edge_tans[i][1] * v0[0] - edge_tans[i][0] * v0[1];\n        p1 = edge_tans[i][1] * v1[0] - edge_tans[i][0] * v1[1];\n        p2 = edge_tans[i][1] * v2[0] - edge_tans[i][0] * v2[1];\n        p3 = edge_tans[i][1] * v3[0] - edge_tans[i][0] * v3[1];\n        pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n        pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n        rad = std::abs(edge_tans[i][1]) * half[0] + std::abs(edge_tans[i][0]) * half[1];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n    }\n    // triangle face normals\n    for(int_t i=0; i<4; ++i){\n        axis = face_normals[i];\n        p0 = axis[0] * v0[0] + axis[1] * v0[1] + axis[2] * v0[2];\n        p1 = axis[0] * v1[0] + axis[1] * v1[1] + axis[2] * v1[2];\n        p2 = axis[0] * v2[0] + axis[1] * v2[1] + axis[2] * v2[2];\n        p3 = axis[0] * v3[0] + axis[1] * v3[1] + axis[2] * v3[2];\n        pmin = std::min(std::min(std::min(p0, p1), p2), p3);\n        pmax = std::max(std::max(std::max(p0, p1), p2), p3);\n        rad = std::abs(axis[0]) * half[0] + std::abs(axis[1]) * half[1] + std::abs(axis[2]) * half[2];\n        if (pmin > rad || pmax < -rad){\n            return false;\n        }\n    }\n    return true;\n}"
  },
  {
    "path": "discretize/_extensions/geom.h",
    "content": "#ifndef __GEOM_H\n#define __GEOM_H\n// simple geometric objects for intersection tests with an aabb\n\ntypedef std::size_t int_t;\n\nclass Geometric{\n    public:\n        int_t dim;\n\n        Geometric();\n        Geometric(int_t dim);\n        virtual bool intersects_cell(double *a, double *b) const = 0;\n};\n\nclass Ball : public Geometric{\n    public:\n        double *x0;\n        double r;\n        double rsq;\n\n        Ball();\n        Ball(int_t dim, double* x0, double r);\n        virtual bool intersects_cell(double *a, double *b) const;\n};\n\nclass Line : public Geometric{\n    public:\n        double *x0;\n        double *x1;\n        double inv_dx[3];\n\n        Line();\n        Line(int_t dim, double* x0, double *x1);\n        virtual bool intersects_cell(double *a, double *b) const;\n};\n\nclass Box : public Geometric{\n    public:\n        double *x0;\n        double *x1;\n\n        Box();\n        Box(int_t dim, double* x0, double *x1);\n        virtual bool intersects_cell(double *a, double *b) const;\n};\n\nclass Plane : public Geometric{\n    public:\n        double *origin;\n        double *normal;\n\n        Plane();\n        Plane(int_t dim, double* origin, double *normal);\n        virtual bool intersects_cell(double *a, double *b) const;\n};\n\nclass Triangle : public Geometric{\n    public:\n        double *x0;\n        double *x1;\n        double *x2;\n        double e0[3];\n        double e1[3];\n        double e2[3];\n        double normal[3];\n\n        Triangle();\n        Triangle(int_t dim, double* x0, double *x1, double *x2);\n        virtual bool intersects_cell(double *a, double *b) const;\n};\n\nclass VerticalTriangularPrism : public Triangle{\n    public:\n        double h;\n\n        VerticalTriangularPrism();\n        VerticalTriangularPrism(int_t dim, double* x0, double *x1, double *x2, double h);\n        virtual bool intersects_cell(double *a, double *b) const;\n};\n\nclass Tetrahedron : public Geometric{\n    public:\n        double *x0;\n        double *x1;\n        double *x2;\n        double *x3;\n        double edge_tans[6][3];\n        double face_normals[4][3];\n\n        Tetrahedron();\n        Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double *x3);\n        virtual bool intersects_cell(double *a, double *b) const;\n };\n\n#endif"
  },
  {
    "path": "discretize/_extensions/geom.pxd",
    "content": "from libcpp cimport bool\n\ncdef extern from \"geom.h\":\n    ctypedef int int_t\n    cdef cppclass Ball:\n        Ball() except +\n        Ball(int_t dim, double * x0, double r) except +\n\n    cdef cppclass Line:\n        Line() except +\n        Line(int_t dim, double * x0, double *x1) except +\n\n    cdef cppclass Box:\n        Box() except +\n        Box(int_t dim, double * x0, double *x1) except +\n\n    cdef cppclass Plane:\n        Plane() except +\n        Plane(int_t dim, double * origin, double *normal) except +\n\n    cdef cppclass Triangle:\n        Triangle() except +\n        Triangle(int_t dim, double * x0, double *x1, double *x2) except +\n\n    cdef cppclass VerticalTriangularPrism:\n        VerticalTriangularPrism() except +\n        VerticalTriangularPrism(int_t dim, double * x0, double *x1, double *x2, double h) except +\n\n    cdef cppclass Tetrahedron:\n        Tetrahedron() except +\n        Tetrahedron(int_t dim, double * x0, double *x1, double *x2, double *x3) except +"
  },
  {
    "path": "discretize/_extensions/interputils_cython.pxd",
    "content": "cimport numpy as np\ncdef np.int64_t _bisect_left(np.float64_t[:] a, np.float64_t x) nogil\ncdef np.int64_t _bisect_right(np.float64_t[:] a, np.float64_t x) nogil\n"
  },
  {
    "path": "discretize/_extensions/interputils_cython.pyx",
    "content": "# cython: embedsignature=True, language_level=3\n# cython: linetrace=True\n# cython: freethreading_compatible = True\nimport numpy as np\nimport cython\ncimport numpy as np\nimport scipy.sparse as sp\n\ndef _interp_point_1D(np.ndarray[np.float64_t, ndim=1] x, float xr_i):\n    \"\"\"\n        given a point, xr_i, this will find which two integers it lies between.\n\n        :param numpy.ndarray x: Tensor vector of 1st dimension of grid.\n        :param float xr_i: Location of a point\n        :rtype: int,int,float,float\n        :return: index1, index2, portion1, portion2\n    \"\"\"\n    cdef IIFF xs\n    _get_inds_ws(x,xr_i,&xs)\n    return xs.i1,xs.i2,xs.w1,xs.w2\n\ncdef struct IIFF:\n    np.int64_t i1,i2\n    np.float64_t w1,w2\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.nonecheck(False)\ncdef np.int64_t _bisect_left(np.float64_t[:] a, np.float64_t x) nogil:\n    cdef np.int64_t lo, hi, mid\n    lo = 0\n    hi = a.shape[0]\n    while lo < hi:\n      mid = (lo+hi)//2\n      if a[mid] < x: lo = mid+1\n      else: hi = mid\n    return lo\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.nonecheck(False)\ncdef np.int64_t _bisect_right(np.float64_t[:] a, np.float64_t x) nogil:\n    cdef np.int64_t lo, hi, mid\n    lo = 0\n    hi = a.shape[0]\n    while lo < hi:\n      mid = (lo+hi)//2\n      if x < a[mid]: hi = mid\n      else: lo = mid+1\n    return lo\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.nonecheck(False)\n@cython.cdivision(True)\ncdef void _get_inds_ws(np.float64_t[:] x, np.float64_t xp, IIFF* out) nogil:\n    cdef np.int64_t ind = _bisect_right(x,xp)\n    cdef np.int64_t nx = x.shape[0]\n    out.i2 = ind\n    out.i1 = ind-1\n    out.i2 = max(min(out.i2,nx-1),0)\n    out.i1 = max(min(out.i1,nx-1),0)\n    if(out.i1==out.i2):\n        out.w1 = 0.5\n    else:\n        out.w1 = (x[out.i2]-xp)/(x[out.i2]-x[out.i1])\n    out.w2 = 1-out.w1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.nonecheck(False)\ndef _interpmat1D(np.ndarray[np.float64_t, ndim=1] locs,\n                   np.ndarray[np.float64_t, ndim=1] x):\n    cdef int nx = x.size\n    cdef IIFF xs\n    cdef int npts = locs.shape[0]\n    cdef int i\n\n    cdef np.ndarray[np.int64_t,ndim=1] inds = np.empty(npts*2,dtype=np.int64)\n    cdef np.ndarray[np.float64_t,ndim=1] vals = np.empty(npts*2,dtype=np.float64)\n    for i in range(npts):\n        _get_inds_ws(x,locs[i],&xs)\n\n        inds[2*i  ] = xs.i1\n        inds[2*i+1] = xs.i2\n        vals[2*i  ] = xs.w1\n        vals[2*i+1] = xs.w2\n\n    return inds,vals\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.nonecheck(False)\ndef _interpmat2D(np.ndarray[np.float64_t, ndim=2] locs,\n                   np.ndarray[np.float64_t, ndim=1] x,\n                   np.ndarray[np.float64_t, ndim=1] y):\n    cdef int nx,ny\n    nx,ny = len(x),len(y)\n    cdef int npts = locs.shape[0]\n    cdef int i\n    cdef IIFF xs,ys\n\n    cdef np.ndarray[np.int64_t,ndim=2] inds = np.empty((npts*4,2),dtype=np.int64)\n    cdef np.ndarray[np.float64_t,ndim=1] vals = np.empty(npts*4,dtype=np.float64)\n    for i in range(npts):\n        _get_inds_ws(x,locs[i,0],&xs)\n        _get_inds_ws(y,locs[i,1],&ys)\n\n        inds[4*i  ,0] = xs.i1\n        inds[4*i+1,0] = xs.i1\n        inds[4*i+2,0] = xs.i2\n        inds[4*i+3,0] = xs.i2\n        inds[4*i  ,1] = ys.i1\n        inds[4*i+1,1] = ys.i2\n        inds[4*i+2,1] = ys.i1\n        inds[4*i+3,1] = ys.i2\n\n        vals[4*i  ] = xs.w1*ys.w1\n        vals[4*i+1] = xs.w1*ys.w2\n        vals[4*i+2] = xs.w2*ys.w1\n        vals[4*i+3] = xs.w2*ys.w2\n\n    return inds,vals\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.nonecheck(False)\ndef _interpmat3D(np.ndarray[np.float64_t, ndim=2] locs,\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 nx,ny,nz\n    nx,ny,nz = len(x),len(y),len(z)\n    cdef IIFF xs,ys,zs\n    cdef int npts = locs.shape[0]\n    cdef int i\n\n    cdef np.ndarray[np.int64_t,ndim=2] inds = np.empty((npts*8,3),dtype=np.int64)\n    cdef np.ndarray[np.float64_t,ndim=1] vals = np.empty(npts*8,dtype=np.float64)\n    for i in range(npts):\n        _get_inds_ws(x,locs[i,0],&xs)\n        _get_inds_ws(y,locs[i,1],&ys)\n        _get_inds_ws(z,locs[i,2],&zs)\n\n        inds[8*i  ,0] = xs.i1\n        inds[8*i+1,0] = xs.i1\n        inds[8*i+2,0] = xs.i2\n        inds[8*i+3,0] = xs.i2\n        inds[8*i+4,0] = xs.i1\n        inds[8*i+5,0] = xs.i1\n        inds[8*i+6,0] = xs.i2\n        inds[8*i+7,0] = xs.i2\n\n        inds[8*i  ,1] = ys.i1\n        inds[8*i+1,1] = ys.i2\n        inds[8*i+2,1] = ys.i1\n        inds[8*i+3,1] = ys.i2\n        inds[8*i+4,1] = ys.i1\n        inds[8*i+5,1] = ys.i2\n        inds[8*i+6,1] = ys.i1\n        inds[8*i+7,1] = ys.i2\n\n        inds[8*i  ,2] = zs.i1\n        inds[8*i+1,2] = zs.i1\n        inds[8*i+2,2] = zs.i1\n        inds[8*i+3,2] = zs.i1\n        inds[8*i+4,2] = zs.i2\n        inds[8*i+5,2] = zs.i2\n        inds[8*i+6,2] = zs.i2\n        inds[8*i+7,2] = zs.i2\n\n        vals[8*i  ] = xs.w1*ys.w1*zs.w1\n        vals[8*i+1] = xs.w1*ys.w2*zs.w1\n        vals[8*i+2] = xs.w2*ys.w1*zs.w1\n        vals[8*i+3] = xs.w2*ys.w2*zs.w1\n        vals[8*i+4] = xs.w1*ys.w1*zs.w2\n        vals[8*i+5] = xs.w1*ys.w2*zs.w2\n        vals[8*i+6] = xs.w2*ys.w1*zs.w2\n        vals[8*i+7] = xs.w2*ys.w2*zs.w2\n\n    return inds,vals\n\n@cython.boundscheck(False)\n@cython.cdivision(True)\ndef _tensor_volume_averaging(mesh_in, mesh_out, values=None, output=None):\n\n    cdef np.int32_t[:] i1_in, i1_out, i2_in, i2_out, i3_in, i3_out\n    cdef np.float64_t[:] w1, w2, w3\n    w1 = np.array([1.0], dtype=np.float64)\n    w2 = np.array([1.0], dtype=np.float64)\n    w3 = np.array([1.0], dtype=np.float64)\n    i1_in = np.array([0], dtype=np.int32)\n    i1_out = np.array([0], dtype=np.int32)\n    i2_in = np.array([0], dtype=np.int32)\n    i2_out = np.array([0], dtype=np.int32)\n    i3_in = np.array([0], dtype=np.int32)\n    i3_out = np.array([0], dtype=np.int32)\n    cdef int dim = mesh_in.dim\n    w1, i1_in, i1_out = _volume_avg_weights(mesh_in.nodes_x, mesh_out.nodes_x)\n    if dim > 1:\n        w2, i2_in, i2_out = _volume_avg_weights(mesh_in.nodes_y, mesh_out.nodes_y)\n    if dim > 2:\n        w3, i3_in, i3_out = _volume_avg_weights(mesh_in.nodes_z, mesh_out.nodes_z)\n\n    cdef (np.int32_t, np.int32_t, np.int32_t) w_shape = (w1.shape[0], w2.shape[0], w3.shape[0])\n    cdef (np.int32_t, np.int32_t, np.int32_t) mesh_in_shape\n    cdef (np.int32_t, np.int32_t, np.int32_t) mesh_out_shape\n\n    nCv_in = [len(h) for h in mesh_in.h]\n    nCv_out = [len(h) for h in mesh_out.h]\n    if dim == 1:\n        mesh_in_shape = (nCv_in[0], 1, 1)\n        mesh_out_shape = (nCv_out[0], 1, 1)\n    elif dim == 2:\n        mesh_in_shape = (nCv_in[0], nCv_in[1], 1)\n        mesh_out_shape = (nCv_out[0], nCv_out[1], 1)\n    elif dim == 3:\n        mesh_in_shape = (*nCv_in, )\n        mesh_out_shape = (*nCv_out, )\n\n    cdef np.float64_t[::1, :, :] val_in\n    cdef np.float64_t[::1, :, :] val_out\n    cdef int i1, i2, i3, i1i, i2i, i3i, i1o, i2o, i3o\n    cdef np.float64_t w_3, w_32\n    cdef np.float64_t[::1, :, :] vol = mesh_out.cell_volumes.reshape(mesh_out_shape, order='F').astype(np.float64)\n\n    if values is not None:\n        # If given a values array, do the operation\n        val_in = values.reshape(mesh_in_shape, order='F').astype(np.float64)\n        if output is None:\n            output = np.zeros(mesh_out.n_cells, dtype=np.float64)\n        else:\n            output = np.require(output, dtype=np.float64, requirements=['A', 'W'])\n        v_o = output.reshape(mesh_out_shape, order='F')\n        v_o.fill(0)\n        val_out = v_o\n        for i3 in range(w_shape[2]):\n            i3i = i3_in[i3]\n            i3o = i3_out[i3]\n            w_3 = w3[i3]\n            for i2 in range(w_shape[1]):\n                i2i = i2_in[i2]\n                i2o = i2_out[i2]\n                w_32 = w_3*w2[i2]\n                for i1 in range(w_shape[0]):\n                    i1i = i1_in[i1]\n                    i1o = i1_out[i1]\n                    val_out[i1o, i2o, i3o] += w_32*w1[i1]*val_in[i1i, i2i, i3i]/vol[i1o, i2o, i3o]\n        return output\n\n    # Else, build and return a sparse matrix representing the operation\n    i_i = np.empty(w_shape, dtype=np.int32, order='F')\n    i_o = np.empty(w_shape, dtype=np.int32, order='F')\n    ws = np.empty(w_shape, dtype=np.float64, order='F')\n    cdef np.int32_t[::1,:,:] i_in = i_i\n    cdef np.int32_t[::1,:,:] i_out = i_o\n    cdef np.float64_t[::1, :, :] w = ws\n    for i3 in range(w.shape[2]):\n        i3i = i3_in[i3]\n        i3o = i3_out[i3]\n        w_3 = w3[i3]\n        for i2 in range(w.shape[1]):\n            i2i = i2_in[i2]\n            i2o = i2_out[i2]\n            w_32 = w_3*w2[i2]\n            for i1 in range(w.shape[0]):\n                i1i = i1_in[i1]\n                i1o = i1_out[i1]\n                w[i1, i2, i3] = w_32*w1[i1]/vol[i1o, i2o, i3o]\n                i_in[i1, i2, i3] = (i3i*mesh_in_shape[1] + i2i)*mesh_in_shape[0] + i1i\n                i_out[i1, i2, i3] = (i3o*mesh_out_shape[1] + i2o)*mesh_out_shape[0] + i1o\n    ws = ws.reshape(-1, order='F')\n    i_i = i_i.reshape(-1, order='F')\n    i_o = i_o.reshape(-1, order='F')\n    A = sp.csr_matrix((ws, (i_o, i_i)), shape=(mesh_out.nC, mesh_in.nC))\n    return A\n\n@cython.boundscheck(False)\ndef _volume_avg_weights(np.float64_t[:] x1, np.float64_t[:] x2):\n    cdef int n1 = x1.shape[0]\n    cdef int n2 = x2.shape[0]\n    cdef np.float64_t[:] xs = np.empty(n1 + n2)\n    # Fill xs with uniques and truncate\n    cdef int i1, i2, i, ii\n    i1 = i2 = i = 0\n    while i1<n1 or i2<n2:\n        if i1<n1 and i2<n2:\n            if x1[i1]<x2[i2]:\n                xs[i] = x1[i1]\n                i1 += 1\n            elif x1[i1]>x2[i2]:\n                xs[i] = x2[i2]\n                i2 += 1\n            else:\n                xs[i] = x1[i1]\n                i1 += 1\n                i2 += 1\n        elif i1<n1 and i2==n2:\n            xs[i] = x1[i1]\n            i1 += 1\n        elif i2<n2 and i1==n1:\n            xs[i] = x2[i2]\n            i2 += 1\n        i += 1\n    cdef int nh = i-1\n    hs = np.empty(nh)\n    ix1 = np.empty(nh, dtype=np.int32)\n    ix2 = np.empty(nh, dtype=np.int32)\n\n    cdef np.float64_t[:] _hs = hs\n    cdef np.int32_t[:] _ix1 = ix1\n    cdef np.int32_t[:] _ix2 = ix2\n    cdef np.float64_t center\n\n    i1 = i2 = ii = 0\n    for i in range(nh):\n        center = 0.5*(xs[i]+xs[i+1])\n        if x2[0] <= center and center <= x2[n2-1]:\n            _hs[ii] = xs[i+1]-xs[i]\n            while i1<n1-1 and center>=x1[i1]:\n                i1 += 1\n            while i2<n2-1 and center>=x2[i2]:\n                i2 += 1\n            _ix1[ii] = min(max(i1-1, 0), n1-1)\n            _ix2[ii] = min(max(i2-1, 0), n2-1)\n            ii += 1\n\n    hs = hs[:ii]\n    ix1 = ix1[:ii]\n    ix2 = ix2[:ii]\n    return hs, ix1, ix2\n"
  },
  {
    "path": "discretize/_extensions/meson.build",
    "content": "# NumPy include directory\nnumpy_nodepr_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_22_API_VERSION']\nnp_dep = dependency('numpy')\n\n# Deal with M_PI & friends; add `use_math_defines` to c_args or cpp_args\n# Cython doesn't always get this right itself (see, e.g., gh-16800), so\n# explicitly add the define as a compiler flag for Cython-generated code.\nis_windows = host_machine.system() == 'windows'\nif is_windows\n  use_math_defines = ['-D_USE_MATH_DEFINES']\nelse\n  use_math_defines = []\nendif\n\nc_undefined_ok = ['-Wno-maybe-uninitialized']\n\ncython_c_args = [numpy_nodepr_api, use_math_defines]\n\ncy_line_trace = get_option('cy_line_trace')\nif cy_line_trace\n  cython_c_args += ['-DCYTHON_TRACE_NOGIL=1']\nendif\n\ncython_args = []\nif cy.version().version_compare('>=3.1.0')\n  cython_args += ['-Xfreethreading_compatible=True']\nendif\n\ncython_cpp_args = cython_c_args\n\nmodule_path = 'discretize/_extensions'\n\npy.extension_module(\n    'interputils_cython',\n    'interputils_cython.pyx',\n    cython_args: cython_args,\n    c_args: cython_c_args,\n    install: true,\n    subdir: module_path,\n    dependencies : [py_dep, np_dep],\n)\n\npy.extension_module(\n    'tree_ext',\n    ['tree_ext.pyx' , 'tree.cpp', 'geom.cpp'],\n    cython_args: cython_args,\n    cpp_args: cython_cpp_args,\n    install: true,\n    subdir: module_path,\n    dependencies : [py_dep, np_dep],\n    override_options : ['cython_language=cpp'],\n)\n\npy.extension_module(\n    'simplex_helpers',\n    'simplex_helpers.pyx',\n    cython_args: cython_args,\n    cpp_args: cython_cpp_args,\n    install: true,\n    subdir: module_path,\n    dependencies : [py_dep, np_dep],\n    override_options : ['cython_language=cpp'],\n)\n\npython_sources = [\n  '__init__.py',\n]\n\npy.install_sources(\n  python_sources,\n  subdir: module_path\n)"
  },
  {
    "path": "discretize/_extensions/simplex_helpers.pyx",
    "content": "# distutils: language=c++\n# cython: embedsignature=True, language_level=3\n# cython: linetrace=True\n# cython: freethreading_compatible = True\n\nfrom libcpp.pair cimport pair\nfrom libcpp.unordered_map cimport unordered_map\nfrom cython.operator cimport dereference\ncimport cython\ncimport numpy as np\nfrom cython cimport view\nfrom libc.math cimport sqrt\n\ncdef extern from \"triplet.h\":\n    cdef cppclass triplet[T, U, V]:\n        T v1\n        U v2\n        V v3\n        triplet()\n        triplet(T, U, V)\nimport numpy as np\n\nctypedef fused ints:\n    size_t\n    np.int32_t\n    np.int64_t\n\nctypedef fused pointers:\n    size_t\n    np.intp_t\n    np.int32_t\n    np.int64_t\n\n@cython.boundscheck(False)\ndef _build_faces_edges(ints[:, :] simplices):\n    # the node index in each simplex must be in increasing order\n    cdef:\n        int dim = simplices.shape[1] - 1\n        ints n_simplex = simplices.shape[0]\n        ints[:] simplex\n        ints v1, v2, v3\n\n        unordered_map[pair[ints, ints], ints] edges\n        pair[ints, ints] edge\n        ints n_edges = 0\n        ints edges_per_simplex = 3 if dim==2 else 6\n        ints[:, :] simplex_edges\n\n        unordered_map[triplet[ints, ints, ints], ints] faces\n        triplet[ints, ints, ints] face\n        ints n_faces = 0\n        ints faces_per_simplex = dim + 1\n        ints[:, :] simplex_faces\n\n    if ints is size_t:\n        int_type = np.uintp\n    elif ints is np.int32_t:\n        int_type = np.int32\n    elif ints is np.int64_t:\n        int_type = np.int64\n\n    simplex_edges = np.empty((n_simplex, edges_per_simplex), dtype=int_type)\n\n    if dim == 3:\n        simplex_faces = np.empty((n_simplex, faces_per_simplex), dtype=int_type)\n    else:\n        simplex_faces = simplex_edges\n\n    cdef ints[:,:] edge_pairs = np.array(\n        [[1, 2], [0, 2], [0, 1], [0, 3], [1, 3], [2, 3]],\n        dtype=int_type\n    )\n\n    for i_simp in range(n_simplex):\n        simplex = simplices[i_simp]\n\n        # build edges\n        for i_edge in range(edges_per_simplex):\n\n            v1 = simplex[edge_pairs[i_edge, 0]]\n            v2 = simplex[edge_pairs[i_edge, 1]]\n            edge = pair[ints, ints](v1, v2)\n\n            edge_search = edges.find(edge)\n            if edge_search != edges.end():\n                ind = dereference(edge_search).second\n            else:\n                ind = n_edges\n                edges[edge] = ind\n                n_edges += 1\n            simplex_edges[i_simp, i_edge] = ind\n\n        # build faces in 3D\n        if dim == 3:\n            for i_face in range(4):\n                if i_face == 0:\n                    v1 = simplex[1]\n                    v2 = simplex[2]\n                    v3 = simplex[3]\n                elif i_face == 1:\n                    v1 = simplex[0]\n                    v2 = simplex[2]\n                    v3 = simplex[3]\n                elif i_face == 2:\n                    v1 = simplex[0]\n                    v2 = simplex[1]\n                    v3 = simplex[3]\n                else:\n                    v1 = simplex[0]\n                    v2 = simplex[1]\n                    v3 = simplex[2]\n                face = triplet[ints, ints, ints](v1, v2, v3)\n\n                face_search = faces.find(face)\n                if face_search != faces.end():\n                    ind = dereference(face_search).second\n                else:\n                    ind = n_faces\n                    faces[face] = ind\n                    n_faces += 1\n                simplex_faces[i_simp, i_face] = ind\n\n    cdef ints[:, :] _edges = np.empty((n_edges, 2), dtype=int_type)\n    for edge_it in edges:\n        _edges[edge_it.second, 0] = edge_it.first.first\n        _edges[edge_it.second, 1] = edge_it.first.second\n\n    cdef ints[:, :] _faces\n    if dim == 3:\n        _faces =  np.empty((n_faces, 3), dtype=int_type)\n        for face_it in faces:\n            _faces[face_it.second, 0] = face_it.first.v1\n            _faces[face_it.second, 1] = face_it.first.v2\n            _faces[face_it.second, 2] = face_it.first.v3\n    else:\n        _faces = _edges\n\n    cdef ints[:, :] face_edges\n    cdef ints[:] _face\n\n    if dim == 3:\n        face_edges = np.empty((n_faces, 3), dtype=int_type)\n        for i_face in range(n_faces):\n            _face = _faces[i_face]\n            # get indices of each edge in the face\n            # 3 edges per face\n            for i_edge in range(3):\n                if i_edge == 0:\n                    v1 = _face[1]\n                    v2 = _face[2]\n                elif i_edge == 1:\n                    v1 = _face[0]\n                    v2 = _face[2]\n                elif i_edge == 2:\n                    v1 = _face[0]\n                    v2 = _face[1]\n                # because of how faces were constructed, v1 < v2 always\n                edge = pair[ints, ints](v1, v2)\n                ind = edges[edge]\n                face_edges[i_face, i_edge] = ind\n    else:\n        face_edges = np.empty((1, 1), dtype=int_type)\n\n    return simplex_faces, _faces, simplex_edges, _edges, face_edges\n\n@cython.boundscheck(False)\ndef _build_adjacency(ints[:, :] simplex_faces, n_faces):\n    cdef:\n        size_t n_cells = simplex_faces.shape[0]\n        int dim = simplex_faces.shape[1] - 1\n        np.int64_t[:, :] neighbors\n        np.int64_t[:] visited\n        ints[:] simplex\n\n        ints i_cell, j, k, i_face, i_other\n\n    if ints is size_t:\n        int_type = np.uintp\n    elif ints is np.int32_t:\n        int_type = np.int32\n    elif ints is np.int64_t:\n        int_type = np.int64\n\n    neighbors = np.full((n_cells, dim + 1), -1, dtype=np.int64)\n    visited = np.full((n_faces), -1, dtype=np.int64)\n\n    for i_cell in range(n_cells):\n        simplex = simplex_faces[i_cell]\n        for j in range(dim + 1):\n            i_face = simplex[j]\n            i_other = visited[i_face]\n            if i_other == -1:\n                visited[i_face] = i_cell\n            else:\n                neighbors[i_cell, j] = i_other\n                k = 0\n                while (k < dim + 1) and (simplex_faces[i_other, k] != i_face):\n                    k += 1\n                neighbors[i_other, k] = i_cell\n    return neighbors\n\n@cython.boundscheck(False)\n@cython.linetrace(False)\ncdef void _compute_bary_coords(\n    np.float64_t[:] point,\n    np.float64_t[:, :] Tinv,\n    np.float64_t[:] shift,\n    np.float64_t * bary\n) nogil:\n    cdef:\n        int dim = point.shape[0]\n        int i, j\n\n    bary[dim] = 1.0\n    for i in range(dim):\n        bary[i] = 0.0\n        for j in range(dim):\n            bary[i] += Tinv[i, j] * (point[j] - shift[j])\n        bary[dim] -= bary[i]\n\n@cython.boundscheck(False)\ndef _directed_search(\n    np.float64_t[:, :] locs,\n    pointers[:] nearest_cc,\n    np.float64_t[:, :] nodes,\n    ints[:, :] simplex_nodes,\n    np.int64_t[:, :] neighbors,\n    np.float64_t[:, :, :] transform,\n    np.float64_t[:, :] shift,\n    np.float64_t eps=1E-15,\n    bint zeros_outside=False,\n    bint return_bary=True\n):\n    cdef:\n        int i, j\n        pointers i_simp\n        int n_locs = locs.shape[0], dim = locs.shape[1]\n        int max_directed = 1 + simplex_nodes.shape[0] // 4\n        int i_directed\n        bint is_inside\n        np.int64_t[:] inds = np.full(len(locs), -1, dtype=np.int64)\n        np.float64_t[:, :] all_barys = np.empty((1, 1), dtype=np.float64)\n        np.float64_t barys[4]\n        np.float64_t[:] loc\n        np.float64_t[:, :] Tinv\n        np.float64_t[:] rD\n    if return_bary:\n        all_barys = np.empty((len(locs), dim+1), dtype=np.float64)\n\n    for i in range(n_locs):\n        loc = locs[i]\n\n        i_simp = nearest_cc[i]  # start at the nearest cell center\n        i_directed = 0\n        while i_directed < max_directed:\n            Tinv = transform[i_simp]\n            rD = shift[i_simp]\n            _compute_bary_coords(loc, Tinv, rD, barys)\n            j = 0\n            is_inside = True\n            while j <= dim:\n                if barys[j] < -eps:\n                    is_inside = False\n                    # if not -1, move towards neighbor\n                    if neighbors[i_simp, j] != -1:\n                        i_simp = neighbors[i_simp, j]\n                        break\n                j += 1\n            # If inside, I found my container\n            if is_inside:\n                break\n            # Else, if I cycled through every bary\n            # without breaking out of the above loop, that means I'm completely outside\n            elif j == dim + 1:\n                if zeros_outside:\n                    i_simp = -1\n                break\n            i_directed += 1\n\n        if i_directed == max_directed:\n            # made it through the whole loop without breaking out\n            # Mark as failed\n            i_simp = -2\n        inds[i] = i_simp\n        if return_bary:\n            for j in range(dim+1):\n                all_barys[i, j] = barys[j]\n\n    if return_bary:\n        return np.array(inds), np.array(all_barys)\n    return np.array(inds)\n\n@cython.boundscheck(False)\n@cython.cdivision(True)\ndef _interp_cc(\n  np.float64_t[:, :] locs,\n  np.float64_t[:, :] cell_centers,\n  np.float64_t[:] mat_data,\n  ints[:] mat_indices,\n  ints[:] mat_indptr,\n):\n    cdef:\n        ints i, j, diff, start, stop, i_d\n        ints n_max_per_row = 0\n        ints[:] close_cells\n        int dim = locs.shape[1]\n        np.float64_t[:, :] drs\n        np.float64_t[:] rs\n        np.float64_t[:] rhs\n        np.float64_t[:] lambs\n        np.float64_t[:] weights\n        np.float64_t[:] point\n        np.float64_t[:] close_cell\n        np.float64_t det, weight_sum\n        np.float64_t xx, xy, xz, yy, yz, zz\n        bint too_close\n        np.float64_t eps = 1E-15\n    # Find maximum number per row to pre-allocate a storage\n    for i in range(locs.shape[0]):\n        diff = mat_indptr[i+1] - mat_indptr[i]\n        if diff > n_max_per_row:\n          n_max_per_row = diff\n    #\n    drs = np.empty((n_max_per_row, dim), dtype=np.float64)\n    rs = np.empty((n_max_per_row,), dtype=np.float64)\n    rhs = np.empty((dim,),dtype=np.float64)\n    lambs = np.empty((dim,),dtype=np.float64)\n\n    for i in range(locs.shape[0]):\n        point = locs[i]\n        start = mat_indptr[i]\n        stop = mat_indptr[i+1]\n        diff = stop-start\n        close_cells = mat_indices[start:stop]\n        for j in range(diff):\n            rs[j] = 0.0\n            close_cell = cell_centers[close_cells[j]]\n            for i_d in range(dim):\n                drs[j, i_d] = close_cell[i_d] - point[i_d]\n                rs[j] += drs[j, i_d]*drs[j, i_d]\n            rs[j] = sqrt(rs[j])\n        weights = mat_data[start:stop]\n        weights[:] = 0.0\n        too_close = False\n        i_d = 0\n        for j in range(diff):\n            if rs[j] < eps:\n                too_close = True\n                i_d = j\n        if too_close:\n            weights[i_d] = 1.0\n        else:\n            for j in range(diff):\n                for i_d in range(dim):\n                    drs[j, i_d] /= rs[j]\n            xx = xy = yy = 0.0\n            rhs[:] = 0.0\n            if dim == 2:\n                for j in range(diff):\n                    xx += drs[j, 0] * drs[j, 0]\n                    xy += drs[j, 0] * drs[j, 1]\n                    yy += drs[j, 1] * drs[j, 1]\n                    rhs[0] -= drs[j, 0]\n                    rhs[1] -= drs[j, 1]\n                det = xx * yy - xy * xy\n                lambs[0] = (yy * rhs[0] - xy * rhs[1])/det\n                lambs[1] = (-xy * rhs[0] + xx * rhs[1])/det\n\n            if dim == 3:\n                zz = xz = yz = 0.0\n                for j in range(diff):\n                    xx += drs[j, 0] * drs[j, 0]\n                    xy += drs[j, 0] * drs[j, 1]\n                    yy += drs[j, 1] * drs[j, 1]\n                    xz += drs[j, 0] * drs[j, 2]\n                    yz += drs[j, 1] * drs[j, 2]\n                    zz += drs[j, 2] * drs[j, 2]\n                    rhs[0] -= drs[j, 0]\n                    rhs[1] -= drs[j, 1]\n                    rhs[2] -= drs[j, 2]\n\n                det = (\n                      xx * (yy * zz - yz * yz)\n                      + xy * (xz * yz - xy * zz)\n                      + xz * (xy * yz - xz * yy)\n                )\n                lambs[0] = (\n                    (yy * zz - yz * yz) * rhs[0]\n                    + (xz * yz - xy * zz) * rhs[1]\n                    + (xy * yz - xz * yy) * rhs[2]\n                )/det\n                lambs[1] = (\n                    (xz * yz - xy * zz) * rhs[0]\n                    + (xx * zz - xz * xz) * rhs[1]\n                    + (xy * xz - xx * yz) * rhs[2]\n                )/det\n                lambs[2] = (\n                    (xy * yz - xz * yy) * rhs[0]\n                    + (xz * xy - xx * yz) * rhs[1]\n                    + (xx * yy - xy * xy) * rhs[2]\n                )/det\n\n            weight_sum = 0.0\n            for j in range(diff):\n                weights[j] = 1.0\n                for i_d in range(dim):\n                    weights[j] += lambs[i_d] * drs[j, i_d]\n                weights[j] /= rs[j]\n                weight_sum += weights[j]\n            for j in range(diff):\n                weights[j] /= weight_sum\n"
  },
  {
    "path": "discretize/_extensions/tree.cpp",
    "content": "#include <vector>\n#include <map>\n#include \"tree.h\"\n#include \"geom.h\"\n#include <iostream>\n#include <algorithm>\n#include <limits>\n\nNode::Node(){\n    location_ind[0] = 0;\n    location_ind[1] = 0;\n    location_ind[2] = 0;\n    location[0] = 0;\n    location[1] = 0;\n    location[2] = 0;\n    key = 0;\n    reference = 0;\n    index = 0;\n    hanging = false;\n    parents[0] = NULL;\n    parents[1] = NULL;\n    parents[2] = NULL;\n    parents[3] = NULL;\n};\n\nNode::Node(int_t ix, int_t iy, int_t iz, double* xs, double *ys, double *zs){\n    location_ind[0] = ix;\n    location_ind[1] = iy;\n    location_ind[2] = iz;\n    location[0] = xs[ix];\n    location[1] = ys[iy];\n    location[2] = zs[iz];\n    key = key_func(ix, iy, iz);\n    reference = 0;\n    index = 0;\n    hanging = false;\n    parents[0] = NULL;\n    parents[1] = NULL;\n    parents[2] = NULL;\n    parents[3] = NULL;\n};\n\nEdge::Edge(){\n    location_ind[0] = 0;\n    location_ind[1] = 0;\n    location_ind[2] = 0;\n    location[0] = 0;\n    location[1] = 0;\n    location[2] = 0;\n    key = 0;\n    index = 0;\n    reference = 0;\n    length = 0.0;\n    hanging = false;\n    points[0] = NULL;\n    points[1] = NULL;\n    parents[0] = NULL;\n    parents[1] = NULL;\n};\n\nEdge::Edge(Node& p1, Node& p2){\n      points[0] = &p1;\n      points[1] = &p2;\n      int_t ix, iy, iz;\n      ix = (p1.location_ind[0]+p2.location_ind[0])/2;\n      iy = (p1.location_ind[1]+p2.location_ind[1])/2;\n      iz = (p1.location_ind[2]+p2.location_ind[2])/2;\n      key = key_func(ix, iy, iz);\n      location_ind[0] = ix;\n      location_ind[1] = iy;\n      location_ind[2] = iz;\n\n      location[0] = (p1[0]+p2[0]) * 0.5;\n      location[1] = (p1[1]+p2[1]) * 0.5;\n      location[2] = (p1[2]+p2[2]) * 0.5;\n      length = (p2[0]-p1[0])\n                + (p2[1]-p1[1])\n                + (p2[2]-p1[2]);\n      reference = 0;\n      index = 0;\n      hanging = false;\n      parents[0] = NULL;\n      parents[1] = NULL;\n}\n\nFace::Face(){\n    location_ind[0] = 0;\n    location_ind[1] = 0;\n    location_ind[2] = 0;\n    location[0] = 0.0;\n    location[1] = 0.0;\n    location[2] = 0.0;\n    key = 0;\n    reference = 0;\n    index = 0;\n    area = 0;\n    hanging = false;\n    points[0] = NULL;\n    points[1] = NULL;\n    points[2] = NULL;\n    points[3] = NULL;\n    edges[0] = NULL;\n    edges[1] = NULL;\n    edges[2] = NULL;\n    edges[3] = NULL;\n    parent = NULL;\n}\n\nFace::Face(Node& p1, Node& p2, Node& p3, Node& p4){\n    points[0] = &p1;\n    points[1] = &p2;\n    points[2] = &p3;\n    points[3] = &p4;\n    int_t ix, iy, iz;\n    ix = (p1.location_ind[0]+p2.location_ind[0]+p3.location_ind[0]+p4.location_ind[0])/4;\n    iy = (p1.location_ind[1]+p2.location_ind[1]+p3.location_ind[1]+p4.location_ind[1])/4;\n    iz = (p1.location_ind[2]+p2.location_ind[2]+p3.location_ind[2]+p4.location_ind[2])/4;\n    key = key_func(ix, iy, iz);\n    location_ind[0] = ix;\n    location_ind[1] = iy;\n    location_ind[2] = iz;\n\n    location[0] = (p1[0]+p2[0]+p3[0]+p4[0]) * 0.25;\n    location[1] = (p1[1]+p2[1]+p3[1]+p4[1]) * 0.25;\n    location[2] = (p1[2]+p2[2]+p3[2]+p4[2]) * 0.25;\n    area = ((p2[0]-p1[0]) + (p2[1]-p1[1]) + (p2[2]-p1[2])) *\n           ((p3[0]-p1[0]) + (p3[1]-p1[1]) + (p3[2]-p1[2]));\n    reference = 0;\n    index = 0;\n    hanging = false;\n    parent = NULL;\n    edges[0] = NULL;\n    edges[1] = NULL;\n    edges[2] = NULL;\n    edges[3] = NULL;\n}\n\nNode * set_default_node(node_map_t& nodes, int_t x, int_t y, int_t z,\n                        double *xs, double *ys, double *zs){\n  int_t key = key_func(x, y, z);\n  auto [it, inserted] = nodes.try_emplace(key, nullptr);\n  if(inserted){\n        // construct a new item at the emplaced location\n        it->second = new Node(x, y, z, xs, ys, zs);\n  }\n  return it->second;\n}\n\nEdge * set_default_edge(edge_map_t& edges, Node& p1, Node& p2){\n  int_t xC = (p1.location_ind[0]+p2.location_ind[0])/2;\n  int_t yC = (p1.location_ind[1]+p2.location_ind[1])/2;\n  int_t zC = (p1.location_ind[2]+p2.location_ind[2])/2;\n  int_t key = key_func(xC, yC, zC);\n  auto [it, inserted] = edges.try_emplace(key, nullptr);\n  if(inserted){\n        // construct a new item at the emplaced location\n        it->second = new Edge(p1, p2);\n  }\n  return it->second;\n};\n\nFace * set_default_face(face_map_t& faces, Node& p1, Node& p2, Node& p3, Node& p4){\n    int_t x, y, z, key;\n    x = (p1.location_ind[0]+p2.location_ind[0]+p3.location_ind[0]+p4.location_ind[0])/4;\n    y = (p1.location_ind[1]+p2.location_ind[1]+p3.location_ind[1]+p4.location_ind[1])/4;\n    z = (p1.location_ind[2]+p2.location_ind[2]+p3.location_ind[2]+p4.location_ind[2])/4;\n    key = key_func(x, y, z);\n    auto [it, inserted] = faces.try_emplace(key, nullptr);\n    if(inserted){\n        // construct a new item at the emplaced location\n        it->second = new Face(p1, p2, p3, p4);\n    }\n    return it->second;\n}\n\nCell::Cell(Node *pts[8], int_t ndim, int_t maxlevel){\n    n_dim = ndim;\n    int_t n_points = 1<<n_dim;\n    for(int_t i = 0; i < n_points; ++i)\n        points[i] = pts[i];\n    index = -1;\n    level = 0;\n    max_level = maxlevel;\n    parent = NULL;\n    Node p1 = *pts[0];\n    Node p2 = *pts[n_points - 1];\n    location_ind[0] = (p1.location_ind[0]+p2.location_ind[0])/2;\n    location_ind[1] = (p1.location_ind[1]+p2.location_ind[1])/2;\n    location_ind[2] = (p1.location_ind[2]+p2.location_ind[2])/2;\n    location[0] = (p1[0]+p2[0]) * 0.5;\n    location[1] = (p1[1]+p2[1]) * 0.5;\n    location[2] = (p1[2]+p2[2]) * 0.5;\n    volume = (p2[0]-p1[0]) * (p2[1]-p1[1]);\n    if(n_dim==3)\n        volume *= (p2[2] - p1[2]);\n    key = key_func(location_ind[0], location_ind[1], location_ind[2]);\n    for(int_t i = 0; i < n_points; ++i)\n        children[i] = NULL;\n    for(int_t i = 0; i < 2*n_dim; ++i)\n        neighbors[i] = NULL;\n};\n\nCell::Cell(Node *pts[8], Cell *parent){\n    n_dim = parent->n_dim;\n    int_t n_points = 1<<n_dim;\n    for(int_t i = 0; i < n_points; ++i)\n        points[i] = pts[i];\n    index = -1;\n    level = parent->level + 1;\n    max_level = parent->max_level;\n    Node p1 = *pts[0];\n    Node p2 = *pts[n_points - 1];\n    location_ind[0] = (p1.location_ind[0]+p2.location_ind[0])/2;\n    location_ind[1] = (p1.location_ind[1]+p2.location_ind[1])/2;\n    location_ind[2] = (p1.location_ind[2]+p2.location_ind[2])/2;\n    location[0] = (p1[0]+p2[0]) * 0.5;\n    location[1] = (p1[1]+p2[1]) * 0.5;\n    location[2] = (p1[2]+p2[2]) * 0.5;\n\n    volume = (p2[0]-p1[0]) * (p2[1]-p1[1]);\n    if(n_dim == 3)\n        volume *= (p2[2] - p1[2]);\n\n    key = key_func(location_ind[0], location_ind[1], location_ind[2]);\n\n    for(int_t i = 0; i < n_points; ++i)\n        children[i] = NULL;\n    for(int_t i = 0; i < 2*n_dim; ++i)\n        neighbors[i] = NULL;\n};\n\nvoid Cell::spawn(node_map_t& nodes, Cell *kids[8], double *xs, double *ys, double *zs){\n    /*      z0              z0+dz/2          z0+dz\n        p03--p13--p04    p20--p21--p22   p07--p27--p08\n        |     |    |     |     |    |    |     |    |\n        p10--p11--p12    p17--p18--p19   p24--p25--p26\n        |     |    |     |     |    |    |     |    |\n        p01--p09--p02    p14--p15--p16   p05--p23--p06\n    */\n    Node *p1 = points[0];\n    Node *p2 = points[1];\n    Node *p3 = points[2];\n    Node *p4 = points[3];\n\n    int_t x0, y0, xC, yC, xF, yF, z0;\n\n    x0 = p1->location_ind[0];\n    y0 = p1->location_ind[1];\n    xF = p4->location_ind[0];\n    yF = p4->location_ind[1];\n    z0 = p1->location_ind[2];\n    xC = location_ind[0];\n    yC = location_ind[1];\n\n    Node *p9, *p10, *p11, *p12, *p13;\n    p9  = set_default_node(nodes, xC, y0, z0, xs, ys, zs);\n    p10 = set_default_node(nodes, x0, yC, z0, xs, ys, zs);\n    p11 = set_default_node(nodes, xC, yC, z0, xs, ys, zs);\n    p12 = set_default_node(nodes, xF, yC, z0, xs, ys, zs);\n    p13 = set_default_node(nodes, xC, yF, z0, xs, ys, zs);\n\n    //Increment node references for new nodes\n    p9->reference += 2;\n    p10->reference += 2;\n    p11->reference += 4;\n    p12->reference += 2;\n    p13->reference += 2;\n\n    if(n_dim>2){\n        Node *p5 = points[4];\n        Node *p6 = points[5];\n        Node *p7 = points[6];\n        Node *p8 = points[7];\n\n        int_t zC, zF;\n\n        zF = p8->location_ind[2];\n        zC = location_ind[2];\n\n        Node *p14, *p15, *p16, *p17, *p18, *p19, *p20, *p21, *p22;\n        Node *p23, *p24, *p25, *p26, *p27;\n\n        p14 = set_default_node(nodes, x0, y0, zC, xs, ys, zs);\n        p15 = set_default_node(nodes, xC, y0, zC, xs, ys, zs);\n        p16 = set_default_node(nodes, xF, y0, zC, xs, ys, zs);\n        p17 = set_default_node(nodes, x0, yC, zC, xs, ys, zs);\n        p18 = set_default_node(nodes, xC, yC, zC, xs, ys, zs);\n        p19 = set_default_node(nodes, xF, yC, zC, xs, ys, zs);\n        p20 = set_default_node(nodes, x0, yF, zC, xs, ys, zs);\n        p21 = set_default_node(nodes, xC, yF, zC, xs, ys, zs);\n        p22 = set_default_node(nodes, xF, yF, zC, xs, ys, zs);\n\n        p23 = set_default_node(nodes, xC, y0, zF, xs, ys, zs);\n        p24 = set_default_node(nodes, x0, yC, zF, xs, ys, zs);\n        p25 = set_default_node(nodes, xC, yC, zF, xs, ys, zs);\n        p26 = set_default_node(nodes, xF, yC, zF, xs, ys, zs);\n        p27 = set_default_node(nodes, xC, yF, zF, xs, ys, zs);\n\n        //Increment node references\n        p14->reference += 2;\n        p15->reference += 4;\n        p16->reference += 2;\n        p17->reference += 4;\n        p18->reference += 8;\n        p19->reference += 4;\n        p20->reference += 2;\n        p21->reference += 4;\n        p22->reference += 2;\n\n        p23->reference += 2;\n        p24->reference += 2;\n        p25->reference += 4;\n        p26->reference += 2;\n        p27->reference += 2;\n\n        Node * pQC1[8] = { p1, p9,p10,p11,p14,p15,p17,p18};\n        Node * pQC2[8] = { p9, p2,p11,p12,p15,p16,p18,p19};\n        Node * pQC3[8] = {p10,p11, p3,p13,p17,p18,p20,p21};\n        Node * pQC4[8] = {p11,p12,p13, p4,p18,p19,p21,p22};\n        Node * pQC5[8] = {p14,p15,p17,p18, p5,p23,p24,p25};\n        Node * pQC6[8] = {p15,p16,p18,p19,p23, p6,p25,p26};\n        Node * pQC7[8] = {p17,p18,p20,p21,p24,p25, p7,p27};\n        Node * pQC8[8] = {p18,p19,p21,p22,p25,p26,p27, p8};\n\n        kids[0] = new Cell(pQC1, this);\n        kids[1] = new Cell(pQC2, this);\n        kids[2] = new Cell(pQC3, this);\n        kids[3] = new Cell(pQC4, this);\n        kids[4] = new Cell(pQC5, this);\n        kids[5] = new Cell(pQC6, this);\n        kids[6] = new Cell(pQC7, this);\n        kids[7] = new Cell(pQC8, this);\n    }\n    else{\n        Node * pQC1[8] = { p1, p9,p10,p11, NULL, NULL, NULL, NULL};\n        Node * pQC2[8] = { p9, p2,p11,p12, NULL, NULL, NULL, NULL};\n        Node * pQC3[8] = {p10,p11, p3,p13, NULL, NULL, NULL, NULL};\n        Node * pQC4[8] = {p11,p12,p13, p4, NULL, NULL, NULL, NULL};\n        kids[0] = new Cell(pQC1, this);\n        kids[1] = new Cell(pQC2, this);\n        kids[2] = new Cell(pQC3, this);\n        kids[3] = new Cell(pQC4, this);\n    }\n};\n\nvoid Cell::set_neighbor(Cell * other, int_t position){\n    if(other==NULL){\n        return;\n    }\n    if(level != other->level){\n        neighbors[position] = other;\n    }else{\n        neighbors[position] = other;\n        other->neighbors[position^1] = this;\n    }\n};\n\nvoid Cell::shift_centers(double *shift){\n    for(int_t id = 0; id<n_dim; ++id){\n        location[id] += shift[id];\n    }\n    if(!is_leaf()){\n        for(int_t i = 0; i < (1<<n_dim); ++i){\n            children[i]->shift_centers(shift);\n        }\n    }\n}\n\n// intersections tests:\n\nbool Cell::intersects_point(double *x){\n    // A simple bounding box check:\n    double *p0 = min_node()->location;\n    double *p1 = max_node()->location;\n    for(int_t i=0; i < n_dim; ++i){\n        if(x[i] < p0[i] || x[i] > p1[i]){\n            return false;\n        }\n    }\n    return true;\n}\n\nvoid Cell::insert_cell(node_map_t& nodes, double *new_cell, int_t p_level, double *xs, double *ys, double *zs, bool diag_balance){\n    //Inserts a cell at min(max_level,p_level) that contains the given point\n    if(p_level > level){\n        // Need to go look in children,\n        // Need to spawn children if i don't have any...\n        if(is_leaf()){\n            divide(nodes, xs, ys, zs, true, diag_balance);\n        }\n        int ix = new_cell[0] > children[0]->points[3]->location[0];\n        int iy = new_cell[1] > children[0]->points[3]->location[1];\n        int iz = n_dim>2 && new_cell[2]>children[0]->points[7]->location[2];\n        children[ix + 2*iy + 4*iz]->insert_cell(nodes, new_cell, p_level, xs, ys, zs, diag_balance);\n    }\n};\n\nvoid Cell::refine_func(node_map_t& nodes, function test_func, double *xs, double *ys, double *zs, bool diag_balance){\n    // return if I'm at the maximum level\n    if (level == max_level){\n        return;\n    }\n    if(is_leaf()){\n        // only evaluate the function on leaf cells\n        int test_level = (*test_func)(this);\n        if(test_level < 0){\n            test_level = (max_level + 1) - (abs(test_level) % (max_level + 1));\n        }\n        if (test_level <= level){\n            return;\n        }\n        divide(nodes, xs, ys, zs, true, diag_balance);\n    }\n    // should only be here if not a leaf cell, or I was divided by the function\n    // recurse into children\n    for(int_t i = 0; i < (1<<n_dim); ++i){\n        children[i]->refine_func(nodes, test_func, xs, ys, zs, diag_balance);\n    }\n}\n\nvoid Cell::refine_image(node_map_t& nodes, double* image, int_t *shape_cells, double *xs, double*ys, double *zs, bool diag_balance){\n    // early exit if my level is higher than or equal to target\n    if (level == max_level){\n        return;\n    }\n    int_t start_ix = points[0]->location_ind[0]/2;\n    int_t start_iy = points[0]->location_ind[1]/2;\n    int_t start_iz = n_dim == 2 ? 0 : points[0]->location_ind[2]/2;\n    int_t end_ix = points[3]->location_ind[0]/2;\n    int_t end_iy = points[3]->location_ind[1]/2;\n    int_t end_iz = n_dim == 2? 1 : points[7]->location_ind[2]/2;\n    int_t nx = shape_cells[0];\n    int_t ny = shape_cells[1];\n    int_t nz = shape_cells[2];\n\n    int_t i_image = (nx * ny) * start_iz + nx * start_iy + start_ix;\n    double val_start = image[i_image];\n    bool all_unique = true;\n\n    // if any of the image data contained in the cell are different, subdivide myself\n    for(int_t iz=start_iz; iz<end_iz && all_unique; ++iz)\n        for(int_t iy=start_iy; iy<end_iy && all_unique; ++iy)\n            for(int_t ix=start_ix; ix<end_ix && all_unique; ++ix){\n                i_image = (nx * ny) * iz + nx * iy + ix;\n                all_unique = image[i_image] == val_start;\n            }\n\n    if(!all_unique){\n        if(is_leaf()){\n            divide(nodes, xs, ys, zs, true, diag_balance);\n        }\n        // recurse into children\n        for(int_t i = 0; i < (1<<n_dim); ++i){\n            children[i]->refine_image(nodes, image, shape_cells, xs, ys, zs, diag_balance);\n        }\n    }\n}\n\nvoid Cell::divide(node_map_t& nodes, double* xs, double* ys, double* zs, bool balance, bool diag_balance){\n    // Gaurd against dividing a cell that is already at the max level\n    if (level == max_level){\n        return;\n    }\n    //If i haven't already been split...\n    if(is_leaf()){\n        spawn(nodes, children, xs, ys, zs);\n\n        //If I need to be split, and my neighbor is below my level\n        //Then it needs to be split\n        //-x,+x,-y,+y,-z,+z\n        if(balance){\n            for(int_t i = 0; i < 2*n_dim; ++i){\n                if(neighbors[i] != NULL && neighbors[i]->level < level){\n                    neighbors[i]->divide(nodes, xs, ys, zs, balance, diag_balance);\n                }\n            }\n        }\n        if(diag_balance){\n            Cell *neighbor;\n            if (neighbors[0] != NULL){\n                // -x-y\n                if (neighbors[2] != NULL){\n                    neighbor = neighbors[0]->neighbors[2];\n                    if(neighbor->level < level){\n                        neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                    }\n                }\n                // -x+y\n                if (neighbors[3] != NULL){\n                    neighbor = neighbors[0]->neighbors[3];\n                    if(neighbor->level < level){\n                        neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                    }\n                }\n            }\n            if (neighbors[1] != NULL){\n                // +x-y\n                if (neighbors[2] != NULL){\n                    neighbor = neighbors[1]->neighbors[2];\n                    if(neighbor->level < level){\n                        neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                    }\n                }\n                // +x+y\n                if (neighbors[3] != NULL){\n                    neighbor = neighbors[1]->neighbors[3];\n                    if(neighbor->level < level){\n                        neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                    }\n                }\n            }\n            if(n_dim == 3){\n                // -z\n                if (neighbors[4] != NULL){\n                    if (neighbors[0] != NULL){\n                        // -z-x\n                        neighbor = neighbors[4]->neighbors[0];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                        // -z-x-y\n                        if (neighbors[2] != NULL){\n                            neighbor = neighbors[4]->neighbors[0]->neighbors[2];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                        // -z-x+y\n                        if (neighbors[3] != NULL){\n                            neighbor = neighbors[4]->neighbors[0]->neighbors[3];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                    }\n                    if (neighbors[1] != NULL){\n                        // -z+x\n                        neighbor = neighbors[4]->neighbors[1];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                        // -z+x-y\n                        if (neighbors[2] != NULL){\n                            neighbor = neighbors[4]->neighbors[1]->neighbors[2];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                        // -z+x+y\n                        if (neighbors[3] != NULL){\n                            neighbor = neighbors[4]->neighbors[1]->neighbors[3];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                    }\n                    if (neighbors[2] != NULL){\n                        // -z-y\n                        neighbor = neighbors[4]->neighbors[2];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                    }\n                    if (neighbors[3] != NULL){\n                        // -z+y\n                        neighbor = neighbors[4]->neighbors[3];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                    }\n                }\n                // +z\n                if (neighbors[5] != NULL){\n                    if (neighbors[0] != NULL){\n                        // +z-x\n                        neighbor = neighbors[5]->neighbors[0];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                        // +z-x-y\n                        if (neighbors[2] != NULL){\n                            neighbor = neighbors[5]->neighbors[0]->neighbors[2];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                        // +z-x+y\n                        if (neighbors[3] != NULL){\n                            neighbor = neighbors[5]->neighbors[0]->neighbors[3];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                    }\n                    if (neighbors[1] != NULL){\n                        // +z+x\n                        neighbor = neighbors[5]->neighbors[1];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                        // +z+x-y\n                        if (neighbors[2] != NULL){\n                            neighbor = neighbors[5]->neighbors[1]->neighbors[2];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                        // +z+x+y\n                        if (neighbors[3] != NULL){\n                            neighbor = neighbors[5]->neighbors[1]->neighbors[3];\n                            if(neighbor->level < level){\n                                neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                            }\n                        }\n                    }\n                    if (neighbors[2] != NULL){\n                        // +z-y\n                        neighbor = neighbors[5]->neighbors[2];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                    }\n                    if (neighbors[3] != NULL){\n                        // +z+y\n                        neighbor = neighbors[5]->neighbors[3];\n                        if(neighbor->level < level){\n                            neighbor->divide(nodes, xs, ys, zs, balance, diag_balance);\n                        }\n                    }\n                }\n            }\n        }\n\n        //Set children's neighbors (first do the easy ones)\n        // all of the children live next to each other\n        children[0]->set_neighbor(children[1], 1);\n        children[0]->set_neighbor(children[2], 3);\n        children[1]->set_neighbor(children[3], 3);\n        children[2]->set_neighbor(children[3], 1);\n\n        if(n_dim == 3){\n            children[4]->set_neighbor(children[5], 1);\n            children[4]->set_neighbor(children[6], 3);\n            children[5]->set_neighbor(children[7], 3);\n            children[6]->set_neighbor(children[7], 1);\n\n            children[0]->set_neighbor(children[4], 5);\n            children[1]->set_neighbor(children[5], 5);\n            children[2]->set_neighbor(children[6], 5);\n            children[3]->set_neighbor(children[7], 5);\n        }\n\n        // -x direction\n        if(neighbors[0]!=NULL && !(neighbors[0]->is_leaf())){\n            children[0]->set_neighbor(neighbors[0]->children[1], 0);\n            children[2]->set_neighbor(neighbors[0]->children[3], 0);\n        }\n        else{\n            children[0]->set_neighbor(neighbors[0], 0);\n            children[2]->set_neighbor(neighbors[0], 0);\n        }\n        // +x direction\n        if(neighbors[1]!=NULL && !neighbors[1]->is_leaf()){\n            children[1]->set_neighbor(neighbors[1]->children[0], 1);\n            children[3]->set_neighbor(neighbors[1]->children[2], 1);\n        }else{\n            children[1]->set_neighbor(neighbors[1], 1);\n            children[3]->set_neighbor(neighbors[1], 1);\n        }\n        // -y direction\n        if(neighbors[2]!=NULL && !neighbors[2]->is_leaf()){\n            children[0]->set_neighbor(neighbors[2]->children[2], 2);\n            children[1]->set_neighbor(neighbors[2]->children[3], 2);\n        }else{\n            children[0]->set_neighbor(neighbors[2], 2);\n            children[1]->set_neighbor(neighbors[2], 2);\n        }\n        // +y direction\n        if(neighbors[3]!=NULL && !neighbors[3]->is_leaf()){\n            children[2]->set_neighbor(neighbors[3]->children[0], 3);\n            children[3]->set_neighbor(neighbors[3]->children[1], 3);\n        }else{\n            children[2]->set_neighbor(neighbors[3], 3);\n            children[3]->set_neighbor(neighbors[3], 3);\n        }\n        if(n_dim==3){\n            // -x direction\n            if(neighbors[0]!=NULL && !(neighbors[0]->is_leaf())){\n                children[4]->set_neighbor(neighbors[0]->children[5], 0);\n                children[6]->set_neighbor(neighbors[0]->children[7], 0);\n            }\n            else{\n                children[4]->set_neighbor(neighbors[0], 0);\n                children[6]->set_neighbor(neighbors[0], 0);\n            }\n            // +x direction\n            if(neighbors[1]!=NULL && !neighbors[1]->is_leaf()){\n                children[5]->set_neighbor(neighbors[1]->children[4], 1);\n                children[7]->set_neighbor(neighbors[1]->children[6], 1);\n            }else{\n                children[5]->set_neighbor(neighbors[1], 1);\n                children[7]->set_neighbor(neighbors[1], 1);\n            }\n            // -y direction\n            if(neighbors[2]!=NULL && !neighbors[2]->is_leaf()){\n                children[4]->set_neighbor(neighbors[2]->children[6], 2);\n                children[5]->set_neighbor(neighbors[2]->children[7], 2);\n            }else{\n                children[4]->set_neighbor(neighbors[2], 2);\n                children[5]->set_neighbor(neighbors[2], 2);\n            }\n            // +y direction\n            if(neighbors[3]!=NULL && !neighbors[3]->is_leaf()){\n                children[6]->set_neighbor(neighbors[3]->children[4], 3);\n                children[7]->set_neighbor(neighbors[3]->children[5], 3);\n            }else{\n                children[6]->set_neighbor(neighbors[3], 3);\n                children[7]->set_neighbor(neighbors[3], 3);\n            }\n            // -z direction\n            if(neighbors[4]!=NULL && !neighbors[4]->is_leaf()){\n                children[0]->set_neighbor(neighbors[4]->children[4], 4);\n                children[1]->set_neighbor(neighbors[4]->children[5], 4);\n                children[2]->set_neighbor(neighbors[4]->children[6], 4);\n                children[3]->set_neighbor(neighbors[4]->children[7], 4);\n            }else{\n                children[0]->set_neighbor(neighbors[4], 4);\n                children[1]->set_neighbor(neighbors[4], 4);\n                children[2]->set_neighbor(neighbors[4], 4);\n                children[3]->set_neighbor(neighbors[4], 4);\n            }\n            // +z direction\n            if(neighbors[5]!=NULL && !neighbors[5]->is_leaf()){\n                children[4]->set_neighbor(neighbors[5]->children[0], 5);\n                children[5]->set_neighbor(neighbors[5]->children[1], 5);\n                children[6]->set_neighbor(neighbors[5]->children[2], 5);\n                children[7]->set_neighbor(neighbors[5]->children[3], 5);\n            }else{\n                children[4]->set_neighbor(neighbors[5], 5);\n                children[5]->set_neighbor(neighbors[5], 5);\n                children[6]->set_neighbor(neighbors[5], 5);\n                children[7]->set_neighbor(neighbors[5], 5);\n            }\n        }\n    }\n};\n\nvoid Cell::build_cell_vector(cell_vec_t& cells){\n    if(this->is_leaf()){\n        cells.push_back(this);\n        return;\n    }\n    for(int_t i = 0; i < (1<<n_dim); ++i){\n        children[i]->build_cell_vector(cells);\n    }\n}\n\nCell* Cell::containing_cell(double x, double y, double z){\n    if(is_leaf()){\n      return this;\n    }\n    int ix = x > children[0]->points[3]->location[0];\n    int iy = y > children[0]->points[3]->location[1];\n    int iz = n_dim>2 && z>children[0]->points[7]->location[2];\n    return children[ix + 2*iy + 4*iz]->containing_cell(x, y, z);\n};\n\nCell::~Cell(){\n        if(is_leaf()){\n            return;\n        }\n        for(int_t i = 0; i< (1<<n_dim); ++i){\n            delete children[i];\n        }\n};\n\nTree::Tree(){\n    nx = 0;\n    ny = 0;\n    nz = 0;\n    n_dim = 0;\n    max_level = 0;\n};\n\nvoid Tree::set_dimension(int_t dim){\n    n_dim = dim;\n}\n\nvoid Tree::set_levels(int_t l_x, int_t l_y, int_t l_z){\n    int_t min_l = std::min(l_x, l_y);\n    if(n_dim == 3) min_l = std::min(min_l, l_z);\n    max_level = min_l;\n    if(l_x != l_y || (n_dim==3 && l_y!=l_z)) ++max_level;\n\n    nx = 2<<l_x;\n    ny = 2<<l_y;\n    nz = (n_dim == 3)? 2<<l_z : 0;\n\n    nx_roots = 1<<(l_x-(max_level-1));\n    ny_roots = 1<<(l_y-(max_level-1));\n    nz_roots = (n_dim==3)? 1<<(l_z-(max_level-1)) : 1;\n\n    if (l_x==l_y && (n_dim==2 || l_y==l_z)){\n        --nx_roots;\n        --ny_roots;\n        --nz_roots;\n    }\n    ixs = new int_t[nx_roots+1];\n    iys = new int_t[ny_roots+1];\n    izs = new int_t[nz_roots+1];\n\n    int_t min_n = 2<<min_l;\n    for(int_t i=0; i<nx_roots+1; ++i){\n        ixs[i] = min_n*i;\n    }\n    for(int_t i=0; i<ny_roots+1; ++i){\n        iys[i] = min_n*i;\n    }\n    for(int_t i=0; i<nz_roots+1; ++i){\n        izs[i] = min_n*i;\n    }\n    if(n_dim == 2) nz_roots = 1;\n\n    // Initialize root cell container\n    roots.resize(nz_roots);\n    for(int_t iz=0; iz<nz_roots; ++iz){\n        roots[iz].resize(ny_roots);\n        for(int_t iy=0; iy<ny_roots; ++iy){\n            roots[iz][iy].resize(nx_roots);\n            for(int_t ix=0; ix<nx_roots; ++ix){\n                roots[iz][iy][ix] = NULL;\n            }\n        }\n    }\n};\n\nvoid Tree::set_xs(double *x, double *y, double *z){\n    xs = x;\n    ys = y;\n    zs = z;\n}\n\nvoid Tree::initialize_roots(){\n    if(roots[0][0][0]==NULL){\n        //Create grid of root nodes\n        std::vector<std::vector<std::vector<Node *> > > points;\n        if(n_dim == 2){\n            points.resize(1);\n        }else{\n            points.resize(nz_roots+1);\n        }\n        for(int_t iz = 0; iz<points.size(); ++iz){\n            points[iz].resize(ny_roots+1);\n            for(int_t iy = 0; iy<ny_roots+1; ++iy){\n                points[iz][iy].resize(nx_roots+1);\n                for(int_t ix = 0; ix<nx_roots+1; ++ix){\n                    points[iz][iy][ix] = new Node(ixs[ix], iys[iy], izs[iz],\n                                                  xs, ys, zs);\n                    nodes[points[iz][iy][ix]->key] = points[iz][iy][ix];\n                }\n            }\n        }\n\n        // Create grid of root cells\n        for (int_t iz = 0; iz<nz_roots; ++iz){\n            for (int_t iy = 0; iy<ny_roots; ++iy){\n                for (int_t ix = 0; ix<nx_roots; ++ix){\n                    Node *ps[8];\n                    ps[0] = points[iz][iy  ][ix  ];\n                    ps[1] = points[iz][iy  ][ix+1];\n                    ps[2] = points[iz][iy+1][ix  ];\n                    ps[3] = points[iz][iy+1][ix+1];\n                    if (n_dim == 3){\n                        ps[4] = points[iz+1][iy  ][ix  ];\n                        ps[5] = points[iz+1][iy  ][ix+1];\n                        ps[6] = points[iz+1][iy+1][ix  ];\n                        ps[7] = points[iz+1][iy+1][ix+1];\n                    }\n                    roots[iz][iy][ix] = new Cell(ps, n_dim, max_level);\n                    if (nx==ny && (n_dim==2 || ny==nz)){\n                        roots[iz][iy][ix]->level = 0;\n                    }else{\n                        roots[iz][iy][ix]->level = 1;\n                    }\n                    for(int_t i = 0; i < (1<<n_dim); ++i){\n                        ps[i]->reference += 1;\n                    }\n                }\n            }\n        }\n        // Set root cell neighbors\n        // +x neighbors\n        for(int_t iz=0; iz<nz_roots; ++iz)\n            for (int_t iy=0; iy<ny_roots; ++iy)\n                for(int_t ix=0; ix<nx_roots-1; ++ix)\n                    roots[iz][iy][ix]->set_neighbor(roots[iz][iy][ix+1], 1);\n        // +y neighbors\n        for(int_t iz=0; iz<nz_roots; ++iz)\n            for(int_t iy=0; iy<ny_roots-1; ++iy)\n                for(int_t ix=0; ix<nx_roots; ++ix)\n                    roots[iz][iy][ix]->set_neighbor(roots[iz][iy+1][ix], 3);\n        // +z neighbors\n        for(int_t iz=0; iz<nz_roots-1; ++iz)\n            for(int_t iy=0; iy<ny_roots; ++iy)\n                for(int_t ix=0; ix<nx_roots; ++ix)\n                    roots[iz][iy][ix]->set_neighbor(roots[iz+1][iy][ix], 5);\n    }\n}\n\nvoid Tree::insert_cell(double *new_center, int_t p_level, bool diagonal_balance){\n    // find containing root\n    int_t ix = 0;\n    int_t iy = 0;\n    int_t iz = 0;\n    while (new_center[0]>=xs[ixs[ix+1]] && ix<nx_roots-1){\n        ++ix;\n    }\n    while (new_center[1]>=ys[iys[iy+1]] && iy<ny_roots-1){\n        ++iy;\n    }\n    if(n_dim == 3){\n        while(new_center[2]>=zs[izs[iz+1]] && iz<nz_roots-1){\n            ++iz;\n        }\n    }\n    roots[iz][iy][ix]->insert_cell(nodes, new_center, p_level, xs, ys, zs, diagonal_balance);\n}\n\nvoid Tree::refine_function(function test_func, bool diagonal_balance){\n    //Now we can divide\n    for(int_t iz=0; iz<nz_roots; ++iz)\n        for(int_t iy=0; iy<ny_roots; ++iy)\n            for(int_t ix=0; ix<nx_roots; ++ix)\n                roots[iz][iy][ix]->refine_func(nodes, test_func, xs, ys, zs, diagonal_balance);\n};\n\nvoid Tree::refine_image(double *image, bool diagonal_balance){\n    int_t shape_cells[3];\n    shape_cells[0] = nx/2;\n    shape_cells[1] = ny/2;\n    shape_cells[2] = nz/2;\n    for(int_t iz=0; iz<nz_roots; ++iz)\n        for(int_t iy=0; iy<ny_roots; ++iy)\n            for(int_t ix=0; ix<nx_roots; ++ix)\n                roots[iz][iy][ix]->refine_image(nodes, image, shape_cells, xs, ys, zs, diagonal_balance);\n}\n\n\nvoid Tree::finalize_lists(){\n    for(int_t iz=0; iz<nz_roots; ++iz)\n        for(int_t iy=0; iy<ny_roots; ++iy)\n            for(int_t ix=0; ix<nx_roots; ++ix)\n                roots[iz][iy][ix]->build_cell_vector(cells);\n    if(n_dim == 3){\n        // Generate Faces and edges\n        for(std::vector<Cell *>::size_type i = 0; i != cells.size(); i++){\n            Cell *cell = cells[i];\n            Node *p[8];\n            for(int_t it = 0; it < 8; ++it)\n                p[it] = cell->points[it];\n\n            Edge *ex[4];\n            Edge *ey[4];\n            Edge *ez[4];\n\n            ex[0] = set_default_edge(edges_x, *p[0], *p[1]);\n            ex[1] = set_default_edge(edges_x, *p[2], *p[3]);\n            ex[2] = set_default_edge(edges_x, *p[4], *p[5]);\n            ex[3] = set_default_edge(edges_x, *p[6], *p[7]);\n\n            ey[0] = set_default_edge(edges_y, *p[0], *p[2]);\n            ey[1] = set_default_edge(edges_y, *p[1], *p[3]);\n            ey[2] = set_default_edge(edges_y, *p[4], *p[6]);\n            ey[3] = set_default_edge(edges_y, *p[5], *p[7]);\n\n            ez[0] = set_default_edge(edges_z, *p[0], *p[4]);\n            ez[1] = set_default_edge(edges_z, *p[1], *p[5]);\n            ez[2] = set_default_edge(edges_z, *p[2], *p[6]);\n            ez[3] = set_default_edge(edges_z, *p[3], *p[7]);\n\n            Face *fx1, *fx2, *fy1, *fy2, *fz1, *fz2;\n            fx1 = set_default_face(faces_x, *p[0], *p[2], *p[4], *p[6]);\n            fx2 = set_default_face(faces_x, *p[1], *p[3], *p[5], *p[7]);\n            fy1 = set_default_face(faces_y, *p[0], *p[1], *p[4], *p[5]);\n            fy2 = set_default_face(faces_y, *p[2], *p[3], *p[6], *p[7]);\n            fz1 = set_default_face(faces_z, *p[0], *p[1], *p[2], *p[3]);\n            fz2 = set_default_face(faces_z, *p[4], *p[5], *p[6], *p[7]);\n\n            fx1->edges[0] = ez[0];\n            fx1->edges[1] = ey[2];\n            fx1->edges[2] = ez[2];\n            fx1->edges[3] = ey[0];\n\n            fx2->edges[0] = ez[1];\n            fx2->edges[1] = ey[3];\n            fx2->edges[2] = ez[3];\n            fx2->edges[3] = ey[1];\n\n            fy1->edges[0] = ez[0];\n            fy1->edges[1] = ex[2];\n            fy1->edges[2] = ez[1];\n            fy1->edges[3] = ex[0];\n\n            fy2->edges[0] = ez[2];\n            fy2->edges[1] = ex[3];\n            fy2->edges[2] = ez[3];\n            fy2->edges[3] = ex[1];\n\n            fz1->edges[0] = ey[0];\n            fz1->edges[1] = ex[1];\n            fz1->edges[2] = ey[1];\n            fz1->edges[3] = ex[0];\n\n            fz2->edges[0] = ey[2];\n            fz2->edges[1] = ex[3];\n            fz2->edges[2] = ey[3];\n            fz2->edges[3] = ex[2];\n\n            cell->faces[0] = fx1;\n            cell->faces[1] = fx2;\n            cell->faces[2] = fy1;\n            cell->faces[3] = fy2;\n            cell->faces[4] = fz1;\n            cell->faces[5] = fz2;\n\n            for(int_t it = 0; it < 4; ++it){\n                cell->edges[it    ] = ex[it];\n                cell->edges[it + 4] = ey[it];\n                cell->edges[it + 8] = ez[it];\n            }\n\n            for(int_t it = 0; it < 6; ++it)\n                cell->faces[it]->reference++;\n            for(int_t it = 0; it < 12; ++it)\n                cell->edges[it]->reference++;\n\n        }\n\n        // Process hanging x faces\n        for(face_it_type it = faces_x.begin(); it != faces_x.end(); ++it){\n            Face *face = it->second;\n            if(face->reference < 2){\n                int_t x;\n                x = face->location_ind[0];\n                if(x==0 || x==nx) continue; // Face was on the outside, and is not hanging\n\n                if(nodes.count(face->key)) continue; // I will have children (there is a node at my center)\n                Node *node;\n\n                //Find Parent\n                int_t ip;\n                for(int_t i = 0; i < 4; ++i){\n                    node = face->points[i];\n                    ip = i;\n                    if(faces_x.count(node->key)){\n                        face->parent = faces_x[node->key];\n                        break;\n                    }\n                }\n\n                //all of my edges are hanging, and most of my points\n                for(int_t i = 0; i < 4; ++i){\n                    face->edges[i]->hanging = true;\n                    face->points[i]->hanging = true;\n                }\n                // the point oposite the parent node key should not be hanging\n                // and also label the edges' parents\n                if(face->points[ip^3]->reference != 6)\n                    face->points[ip^3]->hanging = false;\n\n                face->edges[0]->parents[0] = face->parent->edges[0];\n                face->edges[0]->parents[1] = face->parent->edges[((ip&1)^1)<<1]; //2020\n\n                face->edges[1]->parents[0] = face->parent->edges[1];\n                face->edges[1]->parents[1] = face->parent->edges[ip>>1<<1^1]; //1133\n\n                face->edges[2]->parents[0] = face->parent->edges[((ip&1)^1)<<1]; //2020\n                face->edges[2]->parents[1] = face->parent->edges[2];\n\n                face->edges[3]->parents[0] = face->parent->edges[ip>>1<<1^1]; //1133\n                face->edges[3]->parents[1] = face->parent->edges[3];\n\n                face->points[ip^1]->parents[0] = face->parent->points[(ip&1)^1]; //1010\n                face->points[ip^1]->parents[1] = face->parent->points[(ip&1)^3]; //3232\n                face->points[ip^1]->parents[2] = face->parent->points[(ip&1)^1]; //1010\n                face->points[ip^1]->parents[3] = face->parent->points[(ip&1)^3]; //3232\n\n                face->points[ip^2]->parents[0] = face->parent->points[(ip>>1^1)<<1]; //2200\n                face->points[ip^2]->parents[1] = face->parent->points[(ip>>1^1)<<1^1]; //3311\n                face->points[ip^2]->parents[2] = face->parent->points[(ip>>1^1)<<1]; //2200\n                face->points[ip^2]->parents[3] = face->parent->points[(ip>>1^1)<<1^1]; //3311\n\n                face->hanging = true;\n                hanging_faces_x.push_back(face);\n                for(int_t i = 0; i < 4; ++i)\n                    node->parents[i] = face->parent->points[i];\n            }\n        }\n\n        // Process hanging y faces\n        for(face_it_type it = faces_y.begin(); it != faces_y.end(); ++it){\n            Face *face = it->second;\n            if(face->reference < 2){\n                int_t y;\n                y = face->location_ind[1];\n                if(y==0 || y==ny) continue; // Face was on the outside, and is not hanging\n                if(nodes.count(face->key)) continue; // I will have children (there is a node at my center)\n                Node *node;\n\n                //Find Parent\n                int_t ip;\n                for(int_t i = 0; i < 4; ++i){\n                    node = face->points[i];\n                    ip = i;\n                    if(faces_y.count(node->key)){\n                        face->parent = faces_y[node->key];\n                        break;\n                    }\n                }\n                //all of my edges are hanging, and most of my points\n                for(int_t i = 0; i < 4; ++i){\n                    face->edges[i]->hanging = true;\n                    face->points[i]->hanging = true;\n                }\n                // the point oposite the parent node key should not be hanging\n                // and also label the edges' parents\n                if(face->points[ip^3]->reference != 6)\n                    face->points[ip^3]->hanging = false;\n\n                face->edges[0]->parents[0] = face->parent->edges[0];\n                face->edges[0]->parents[1] = face->parent->edges[((ip&1)^1)<<1]; //2020\n\n                face->edges[1]->parents[0] = face->parent->edges[1];\n                face->edges[1]->parents[1] = face->parent->edges[ip>>1<<1^1]; //1133\n\n                face->edges[2]->parents[0] = face->parent->edges[((ip&1)^1)<<1]; //2020\n                face->edges[2]->parents[1] = face->parent->edges[2];\n\n                face->edges[3]->parents[0] = face->parent->edges[ip>>1<<1^1]; //1133\n                face->edges[3]->parents[1] = face->parent->edges[3];\n\n                face->points[ip^1]->parents[0] = face->parent->points[(ip&1)^1]; //1010\n                face->points[ip^1]->parents[1] = face->parent->points[(ip&1)^3]; //3232\n                face->points[ip^1]->parents[2] = face->parent->points[(ip&1)^1]; //1010\n                face->points[ip^1]->parents[3] = face->parent->points[(ip&1)^3]; //3232\n\n                face->points[ip^2]->parents[0] = face->parent->points[(ip>>1^1)<<1]; //2200\n                face->points[ip^2]->parents[1] = face->parent->points[(ip>>1^1)<<1^1]; //3311\n                face->points[ip^2]->parents[2] = face->parent->points[(ip>>1^1)<<1]; //2200\n                face->points[ip^2]->parents[3] = face->parent->points[(ip>>1^1)<<1^1]; //3311\n\n                face->hanging = true;\n                hanging_faces_y.push_back(face);\n                for(int_t i = 0; i < 4; ++i){\n                    node->parents[i] = face->parent->points[i];\n                }\n            }\n        }\n\n        // Process hanging z faces\n        for(face_it_type it = faces_z.begin(); it != faces_z.end(); ++it){\n            Face *face = it->second;\n            if(face->reference < 2){\n                int_t z;\n                z = face->location_ind[2];\n                if(z==0 || z==nz){\n                    // Face was on the outside, and is not hanging\n                    continue;\n                }\n                //check if I am a parent or a child\n                if(nodes.count(face->key)){\n                    // I will have children (there is a node at my center)\n                    continue;\n                }\n                Node *node;\n\n                //Find Parent\n                int_t ip;\n                for(int_t i = 0; i < 4; ++i){\n                    node = face->points[i];\n                    ip = i;\n                    if(faces_z.count(node->key)){\n                        face->parent = faces_z[node->key];\n                        ip = i;\n                        break;\n                    }\n                }\n                //all of my edges are hanging, and most of my points\n                for(int_t i = 0; i < 4; ++i){\n                    face->edges[i]->hanging = true;\n                    face->points[i]->hanging = true;\n                }\n                // the point oposite the parent node key should not be hanging\n                // most of the time\n                // and also label the edges' parents\n                if(face->points[ip^3]->reference != 6)\n                    face->points[ip^3]->hanging = false;\n\n                face->edges[0]->parents[0] = face->parent->edges[0];\n                face->edges[0]->parents[1] = face->parent->edges[((ip&1)^1)<<1]; //2020\n\n                face->edges[1]->parents[0] = face->parent->edges[1];\n                face->edges[1]->parents[1] = face->parent->edges[ip>>1<<1^1]; //1133\n\n                face->edges[2]->parents[0] = face->parent->edges[((ip&1)^1)<<1]; //2020\n                face->edges[2]->parents[1] = face->parent->edges[2];\n\n                face->edges[3]->parents[0] = face->parent->edges[ip>>1<<1^1]; //1133\n                face->edges[3]->parents[1] = face->parent->edges[3];\n\n                face->points[ip^1]->parents[0] = face->parent->points[(ip&1)^1]; //1010\n                face->points[ip^1]->parents[1] = face->parent->points[(ip&1)^3]; //3232\n                face->points[ip^1]->parents[2] = face->parent->points[(ip&1)^1]; //1010\n                face->points[ip^1]->parents[3] = face->parent->points[(ip&1)^3]; //3232\n\n                face->points[ip^2]->parents[0] = face->parent->points[(ip>>1^1)<<1]; //2200\n                face->points[ip^2]->parents[1] = face->parent->points[(ip>>1^1)<<1^1]; //3311\n                face->points[ip^2]->parents[2] = face->parent->points[(ip>>1^1)<<1]; //2200\n                face->points[ip^2]->parents[3] = face->parent->points[(ip>>1^1)<<1^1]; //3311\n\n                face->hanging = true;\n                hanging_faces_z.push_back(face);\n                for(int_t i = 0; i < 4; ++i){\n                    node->parents[i] = face->parent->points[i];\n                }\n            }\n        }\n\n    }\n    else{\n        //Generate Edges (and 1 face for consistency)\n        for(std::vector<Cell *>::size_type i=0; i != cells.size(); i++){\n            Cell *cell = cells[i];\n            Node *p[4];\n            for(int_t i = 0; i < 4; ++i)\n                p[i] = cell->points[i];\n            Edge *e[4];\n            e[0] = set_default_edge(edges_x, *p[0], *p[1]);\n            e[1] = set_default_edge(edges_x, *p[2], *p[3]);\n            e[2] = set_default_edge(edges_y, *p[0], *p[2]);\n            e[3] = set_default_edge(edges_y, *p[1], *p[3]);\n\n            Face *face = set_default_face(faces_z, *p[0], *p[1], *p[2], *p[3]);\n            cell->edges[0] = e[0]; // -x\n            cell->edges[1] = e[1]; // +x\n            cell->edges[2] = e[2]; // -y\n            cell->edges[3] = e[3]; // +y\n\n            // number these clockwise from x0,y0\n            face->edges[0] = e[2]; // -y\n            face->edges[1] = e[1]; // +x\n            face->edges[2] = e[3]; // +y\n            face->edges[3] = e[0]; // -x\n\n            for(int_t i = 0; i < 4; ++i){\n                e[i]->reference++;\n            }\n\n            face->hanging=false;\n        }\n\n        //Process hanging x edges\n        for(edge_it_type it = edges_x.begin(); it != edges_x.end(); ++it){\n            Edge *edge = it->second;\n            if(edge->reference < 2){\n                int_t y = edge->location_ind[1];\n                if(y==0 || y==ny) continue; //I am on the boundary\n                if(nodes.count(edge->key)) continue; //I am a parent\n                //I am a hanging edge find my parent\n                Node *node;\n                if(edges_x.count(edge->points[0]->key)){\n                    node = edge->points[0];\n                }else{\n                    node = edge->points[1];\n                }\n                edge->parents[0] = edges_x[node->key];\n                edge->parents[1] = edge->parents[0];\n\n                node->hanging = true;\n                for(int_t i = 0; i<4; ++i)\n                    node->parents[i] = edge->parents[0]->points[i%2];\n                edge->hanging = true;\n            }\n        }\n\n        //Process hanging y edges\n        for(edge_it_type it = edges_y.begin(); it != edges_y.end(); ++it){\n            Edge *edge = it->second;\n            if(edge->reference < 2){\n                int_t x = edge->location_ind[0];\n                if(x==0 || x==nx) continue; //I am on the boundary\n                if(nodes.count(edge->key)) continue; //I am a parent\n                //I am a hanging edge find my parent\n                Node *node;\n                if(edges_y.count(edge->points[0]->key)){\n                    node = edge->points[0];\n                }else{\n                    node = edge->points[1];\n                }\n                edge->parents[0] = edges_y[node->key];\n                edge->parents[1] = edge->parents[0];\n\n                node->hanging = true;\n                for(int_t i = 0; i < 4; ++i)\n                    node->parents[i] = edge->parents[0]->points[i%2];\n                edge->hanging = true;\n            }\n        }\n    }\n    //List hanging edges x\n    for(edge_it_type it = edges_x.begin(); it != edges_x.end(); ++it){\n        Edge *edge = it->second;\n        if(edge->hanging){\n            hanging_edges_x.push_back(edge);\n        }\n    }\n    //List hanging edges y\n    for(edge_it_type it = edges_y.begin(); it != edges_y.end(); ++it){\n        Edge *edge = it->second;\n        if(edge->hanging){\n            hanging_edges_y.push_back(edge);\n        }\n    }\n    if(n_dim==3){\n        //List hanging edges z\n        for(edge_it_type it = edges_z.begin(); it != edges_z.end(); ++it){\n            Edge *edge = it->second;\n            if(edge->hanging){\n                hanging_edges_z.push_back(edge);\n            }\n        }\n    }\n\n    //List hanging nodes\n    for(node_it_type it = nodes.begin(); it != nodes.end(); ++it){\n        Node *node = it->second;\n        if(node->hanging){\n            hanging_nodes.push_back(node);\n        }\n    }\n}\n\nvoid Tree::number(){\n    //Number Nodes\n    int_t ii, ih;\n    ii = 0;\n    ih = nodes.size() - hanging_nodes.size();\n    for(node_it_type it = nodes.begin(); it != nodes.end(); ++it){\n        Node *node = it->second;\n        if(node->hanging){\n            node->index = ih;\n            ++ih;\n        }else{\n            node->index = ii;\n            ++ii;\n        }\n    }\n\n    //Number Cells\n    for(std::vector<Cell *>::size_type i = 0; i != cells.size(); ++i)\n        cells[i]->index = i;\n\n    //Number edges_x\n    ii = 0;\n    ih = edges_x.size() - hanging_edges_x.size();\n    for(edge_it_type it = edges_x.begin(); it != edges_x.end(); ++it){\n        Edge *edge = it->second;\n        if(edge->hanging){\n          edge->index = ih;\n          ++ih;\n        }else{\n          edge->index = ii;\n          ++ii;\n        }\n    }\n    //Number edges_y\n    ii = 0;\n    ih = edges_y.size() - hanging_edges_y.size();\n    for(edge_it_type it = edges_y.begin(); it != edges_y.end(); ++it){\n        Edge *edge = it->second;\n        if(edge->hanging){\n          edge->index = ih;\n          ++ih;\n        }else{\n          edge->index = ii;\n          ++ii;\n        }\n    }\n\n    if(n_dim==3){\n        //Number faces_x\n        ii = 0;\n        ih = faces_x.size() - hanging_faces_x.size();\n        for(face_it_type it = faces_x.begin(); it != faces_x.end(); ++it){\n            Face *face = it->second;\n            if(face->hanging){\n                face->index = ih;\n                ++ih;\n            }else{\n                face->index = ii;\n                ++ii;\n            }\n        }\n        //Number faces_y\n        ii = 0;\n        ih = faces_y.size() - hanging_faces_y.size();\n        for(face_it_type it = faces_y.begin(); it != faces_y.end(); ++it){\n            Face *face = it->second;\n            if(face->hanging){\n                face->index = ih;\n                ++ih;\n            }else{\n                face->index = ii;\n                ++ii;\n            }\n        }\n\n        //Number faces_z\n        ii = 0;\n        ih = faces_z.size() - hanging_faces_z.size();\n        for(face_it_type it = faces_z.begin(); it != faces_z.end(); ++it){\n            Face *face = it->second;\n            if(face->hanging){\n                face->index = ih;\n                ++ih;\n            }else{\n                face->index = ii;\n                ++ii;\n            }\n        }\n\n        //Number edges_z\n        ii = 0;\n        ih = edges_z.size() - hanging_edges_z.size();\n        for(edge_it_type it = edges_z.begin(); it != edges_z.end(); ++it){\n            Edge *edge = it->second;\n            if(edge->hanging){\n              edge->index = ih;\n              ++ih;\n            }else{\n              edge->index = ii;\n              ++ii;\n            }\n        }\n    }else{\n        //Ensure Fz and cells are numbered the same in 2D\n        for(std::vector<Cell *>::size_type i = 0; i != cells.size(); ++i)\n            faces_z[cells[i]->key]->index = cells[i]->index;\n    }\n\n};\n\nTree::~Tree(){\n    if (roots.size() == 0){\n        return;\n    }\n    for(int_t iz=0; iz<nz_roots; ++iz){\n        for(int_t iy=0; iy<ny_roots; ++iy){\n            for(int_t ix=0; ix<nx_roots; ++ix){\n                delete roots[iz][iy][ix];\n            }\n        }\n    }\n    delete[] ixs;\n    delete[] iys;\n    delete[] izs;\n    for(node_it_type it = nodes.begin(); it != nodes.end(); ++it){\n        delete it->second;\n    }\n    for(face_it_type it = faces_x.begin(); it != faces_x.end(); ++it){\n        delete it->second;\n    }\n    for(face_it_type it = faces_y.begin(); it != faces_y.end(); ++it){\n        delete it->second;\n    }\n    for(face_it_type it = faces_z.begin(); it != faces_z.end(); ++it){\n        delete it->second;\n    }\n    for(edge_it_type it = edges_x.begin(); it != edges_x.end(); ++it){\n        delete it->second;\n    }\n    for(edge_it_type it = edges_y.begin(); it != edges_y.end(); ++it){\n        delete it->second;\n    }\n    for(edge_it_type it = edges_z.begin(); it != edges_z.end(); ++it){\n        delete it->second;\n    }\n    roots.clear();\n    cells.clear();\n    nodes.clear();\n    faces_x.clear();\n    faces_y.clear();\n    faces_z.clear();\n    edges_x.clear();\n    edges_y.clear();\n    edges_z.clear();\n};\n\nCell* Tree::containing_cell(double x, double y, double z){\n    // find containing root\n    int_t ix = 0;\n    int_t iy = 0;\n    int_t iz = 0;\n    while (x>=xs[ixs[ix+1]] && ix<nx_roots-1){\n        ++ix;\n    }\n    while (y>=ys[iys[iy+1]] && iy<ny_roots-1){\n        ++iy;\n    }\n    if(n_dim == 3){\n        while(z>=zs[izs[iz+1]] && iz<nz_roots-1){\n            ++iz;\n        }\n    }\n    return roots[iz][iy][ix]->containing_cell(x, y, z);\n}\n\nvoid Tree::shift_cell_centers(double *shift){\n    for(int_t iz=0; iz<nz_roots; ++iz)\n        for(int_t iy=0; iy<ny_roots; ++iy)\n            for(int_t ix=0; ix<nx_roots; ++ix)\n                roots[iz][iy][ix]->shift_centers(shift);\n}\n"
  },
  {
    "path": "discretize/_extensions/tree.h",
    "content": "#ifndef __TREE_H\r\n#define __TREE_H\r\n\r\n#include <vector>\r\n#include <map>\r\n#include <iostream>\r\n#include <algorithm>\r\n\r\n#include \"geom.h\"\r\n\r\ntypedef std::size_t int_t;\r\n\r\ninline int_t key_func(int_t x, int_t y){\r\n//Double Cantor pairing\r\n    return ((x+y)*(x+y+1))/2+y;\r\n}\r\ninline int_t key_func(int_t x, int_t y, int_t z){\r\n    return key_func(key_func(x, y), z);\r\n}\r\nclass Node;\r\nclass Edge;\r\nclass Face;\r\nclass Cell;\r\nclass Tree;\r\nclass PyWrapper;\r\ntypedef PyWrapper* function;\r\n\r\ntypedef std::map<int_t, Node *> node_map_t;\r\ntypedef std::map<int_t, Edge *> edge_map_t;\r\ntypedef std::map<int_t, Face *> face_map_t;\r\ntypedef node_map_t::iterator node_it_type;\r\ntypedef edge_map_t::iterator edge_it_type;\r\ntypedef face_map_t::iterator face_it_type;\r\ntypedef std::vector<Cell *> cell_vec_t;\r\ntypedef std::vector<int_t> int_vec_t;\r\n\r\nclass PyWrapper{\r\n  public:\r\n    void *py_func;\r\n    int (*eval)(void *, Cell*);\r\n\r\n  PyWrapper(){\r\n    py_func = NULL;\r\n  };\r\n\r\n  void set(void* func, int (*wrapper)(void*, Cell*)){\r\n    py_func = func;\r\n    eval = wrapper;\r\n  };\r\n\r\n  int operator()(Cell * cell){\r\n    return eval(py_func, cell);\r\n  };\r\n};\r\n\r\nclass Node{\r\n  public:\r\n    int_t location_ind[3];\r\n    double location[3];\r\n    int_t key;\r\n    int_t reference;\r\n    int_t index;\r\n    bool hanging;\r\n    Node *parents[4];\r\n    Node();\r\n    Node(int_t, int_t, int_t, double*, double*, double*);\r\n    double operator[](int_t index){\r\n      return location[index];\r\n    };\r\n};\r\n\r\nclass Edge{\r\n  public:\r\n    int_t location_ind[3];\r\n    double location[3];\r\n    int_t key;\r\n    int_t reference;\r\n    int_t index;\r\n    double length;\r\n    bool hanging;\r\n    Node *points[2];\r\n    Edge *parents[2];\r\n    Edge();\r\n    Edge(Node& p1, Node&p2);\r\n    double operator[](int_t index){\r\n      return location[index];\r\n    };\r\n};\r\n\r\nclass Face{\r\n    public:\r\n        int_t location_ind[3];\r\n        double location[3];\r\n        int_t key;\r\n        int_t reference;\r\n        int_t index;\r\n        double area;\r\n        bool hanging;\r\n        Node *points[4];\r\n        Edge *edges[4];\r\n        Face *parent;\r\n        Face();\r\n        Face(Node& p1, Node& p2, Node& p3, Node& p4);\r\n        double operator[](int_t index){\r\n          return location[index];\r\n    };\r\n};\r\n\r\nclass Cell{\r\n  public:\r\n    int_t n_dim;\r\n    Cell *parent, *children[8], *neighbors[6];\r\n    Node *points[8];\r\n    Edge *edges[12];\r\n    Face *faces[6];\r\n\r\n    int_t location_ind[3], key, level, max_level;\r\n    long long int index; // non root parents will have a -1 value\r\n    double location[3];\r\n    double operator[](int_t index){\r\n      return location[index];\r\n    };\r\n    double volume;\r\n\r\n    Cell();\r\n    Cell(Node *pts[8], int_t ndim, int_t maxlevel);//, function func);\r\n    Cell(Node *pts[8], Cell *parent);\r\n    ~Cell();\r\n\r\n    inline Node* min_node(){ return points[0];};\r\n    inline Node* max_node(){ return points[(1<<n_dim)-1];};\r\n\r\n    // intersection tests\r\n    bool intersects_point(double *x);\r\n    Cell* containing_cell(double, double, double);\r\n    void insert_cell(node_map_t &nodes, double *new_center, int_t p_level, double* xs, double *ys, double *zs, bool diag_balance=false);\r\n\r\n    void refine_func(node_map_t& nodes, function test_func, double *xs, double *ys, double* zs, bool diag_balance=false);\r\n    void refine_image(node_map_t& nodes, double* image, int_t *shape_cells, double *xs, double*ys, double *zs, bool diagonal_balance=false);\r\n\r\n    inline bool is_leaf(){ return children[0]==NULL;};\r\n    void spawn(node_map_t& nodes, Cell *kids[8], double* xs, double *ys, double *zs);\r\n    void divide(node_map_t& nodes, double* xs, double* ys, double* zs, bool balance=true, bool diag_balance=false);\r\n    void set_neighbor(Cell* other, int_t direction);\r\n    void build_cell_vector(cell_vec_t& cells);\r\n\r\n    void shift_centers(double * shift);\r\n\r\n    template <class T>\r\n    void refine_geom(node_map_t& nodes, const T& geom, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false){\r\n        // early exit if my level is higher than or equal to target\r\n        if (level >= p_level || level == max_level){\r\n            return;\r\n        }\r\n        double *a = min_node()->location;\r\n        double *b = max_node()->location;\r\n        // if I intersect cell, I will need to be divided (if I'm not already)\r\n        if (geom.intersects_cell(a, b)){\r\n            if(is_leaf()){\r\n                divide(nodes, xs, ys, zs, true, diag_balance);\r\n            }\r\n            // recurse into children\r\n            for(int_t i = 0; i < (1<<n_dim); ++i){\r\n                children[i]->refine_geom(nodes, geom, p_level, xs, ys, zs, diag_balance);\r\n            }\r\n        }\r\n    }\r\n\r\n    template <class T>\r\n    void find_cells_geom(int_vec_t &cells, const T& geom){\r\n        double *a = min_node()->location;\r\n        double *b = max_node()->location;\r\n        if(geom.intersects_cell(a, b)){\r\n            if(this->is_leaf()){\r\n                cells.push_back(index);\r\n                return;\r\n            }\r\n            for(int_t i = 0; i < (1<<n_dim); ++i){\r\n                children[i]->find_cells_geom(cells, geom);\r\n            }\r\n        }\r\n    }\r\n};\r\n\r\nclass Tree{\r\n  public:\r\n    int_t n_dim;\r\n    std::vector<std::vector<std::vector<Cell *> > > roots;\r\n    int_t max_level, nx, ny, nz;\r\n    int_t *ixs, *iys, *izs;\r\n    int_t nx_roots, ny_roots, nz_roots;\r\n    double *xs;\r\n    double *ys;\r\n    double *zs;\r\n\r\n    std::vector<Cell *> cells;\r\n    node_map_t nodes;\r\n    edge_map_t edges_x, edges_y, edges_z;\r\n    face_map_t faces_x, faces_y, faces_z;\r\n    std::vector<Node *> hanging_nodes;\r\n    std::vector<Edge *> hanging_edges_x, hanging_edges_y, hanging_edges_z;\r\n    std::vector<Face *> hanging_faces_x, hanging_faces_y, hanging_faces_z;\r\n\r\n    Tree();\r\n    ~Tree();\r\n\r\n    void set_dimension(int_t dim);\r\n    void set_levels(int_t l_x, int_t l_y, int_t l_z);\r\n    void set_xs(double *x , double *y, double *z);\r\n    void initialize_roots();\r\n    void number();\r\n    void finalize_lists();\r\n\r\n    void shift_cell_centers(double *shift);\r\n\r\n    void insert_cell(double *new_center, int_t p_level, bool diagonal_balance=false);\r\n    Cell* containing_cell(double, double, double);\r\n\r\n    void refine_function(function test_func, bool diagonal_balance=false);\r\n\r\n    void refine_image(double* image, bool diagonal_balance=false);\r\n\r\n    template <class T>\r\n    void refine_geom(const T& geom, int_t p_level, bool diagonal_balance=false){\r\n        for(int_t iz=0; iz<nz_roots; ++iz)\r\n            for(int_t iy=0; iy<ny_roots; ++iy)\r\n                for(int_t ix=0; ix<nx_roots; ++ix)\r\n                    roots[iz][iy][ix]->refine_geom(nodes, geom, p_level, xs, ys, zs, diagonal_balance);\r\n    };\r\n\r\n    template <class T>\r\n    int_vec_t find_cells_geom(const T& geom){\r\n        int_vec_t intersections;\r\n        for(int_t iz=0; iz<nz_roots; ++iz){\r\n            for(int_t iy=0; iy<ny_roots; ++iy){\r\n                for(int_t ix=0; ix<nx_roots; ++ix){\r\n                    roots[iz][iy][ix]->find_cells_geom(intersections, geom);\r\n                }\r\n            }\r\n        }\r\n        return intersections;\r\n    };\r\n\r\n};\r\n\r\n#endif\r\n"
  },
  {
    "path": "discretize/_extensions/tree.pxd",
    "content": "from libcpp cimport bool\nfrom libcpp.vector cimport vector\nfrom libcpp.map cimport map\n\ncdef extern from \"tree.h\":\n    ctypedef int int_t\n\n    cdef cppclass Node:\n        int_t location_ind[3]\n        double location[3]\n        int_t key\n        int_t reference\n        int_t index\n        bool hanging\n        Node *parents[4]\n        Node()\n        Node(int_t, int_t, int_t, double, double, double)\n        double operator[](int_t)\n\n    cdef cppclass Edge:\n        int_t location_ind[3]\n        double location[3]\n        int_t key\n        int_t reference\n        int_t index\n        double length\n        bool hanging\n        Node *points[2]\n        Edge *parents[2]\n        Edge()\n        Edge(Node& p1, Node& p2)\n        double operator[](int_t)\n\n    cdef cppclass Face:\n        int_t location_ind[3]\n        double location[3]\n        int_t key\n        int_t reference\n        int_t index\n        double area\n        bool hanging\n        Node *points[4]\n        Edge *edges[4]\n        Face *parent\n        Face()\n        Face(Node& p1, Node& p2, Node& p3, Node& p4)\n        double operator[](int_t)\n\n    ctypedef map[int_t, Node *] node_map_t\n    ctypedef map[int_t, Edge *] edge_map_t\n    ctypedef map[int_t, Face *] face_map_t\n\n    cdef cppclass Cell:\n        int_t n_dim\n        Cell *parent\n        Cell *children[8]\n        Cell *neighbors[6]\n        Node *points[8]\n        Edge *edges[12]\n        Face *faces[6]\n        int_t location_ind[3]\n        double location[3]\n        int_t key, level, max_level\n        long long int index\n        double volume\n        inline bool is_leaf()\n        inline Node* min_node()\n        inline Node* max_node()\n        double operator[](int_t)\n\n    ctypedef int (*eval_func_ptr)(void*, Cell*)\n    cdef cppclass PyWrapper:\n        PyWrapper()\n        void set(void*, eval_func_ptr eval)\n\n    cdef cppclass Tree:\n        int_t n_dim\n        int_t max_level, nx, ny, nz\n\n        vector[Cell *] cells\n        node_map_t nodes\n        edge_map_t edges_x, edges_y, edges_z\n        face_map_t faces_x, faces_y, faces_z\n        vector[Node *] hanging_nodes\n        vector[Edge *] hanging_edges_x, hanging_edges_y, hanging_edges_z\n        vector[Face *] hanging_faces_x, hanging_faces_y, hanging_faces_z\n\n        Tree()\n\n        void set_dimension(int_t)\n        void set_levels(int_t, int_t, int_t)\n        void set_xs(double*, double*, double*)\n        void refine_function(PyWrapper *, bool)\n\n        void refine_geom[T](const T&, int_t, bool)\n\n        void refine_image(double*, bool)\n\n        void number()\n        void initialize_roots()\n        void insert_cell(double *new_center, int_t p_level, bool)\n        void finalize_lists()\n        Cell * containing_cell(double, double, double)\n        vector[int_t] find_cells_geom[T](const T& geom)\n        void shift_cell_centers(double*)\n"
  },
  {
    "path": "discretize/_extensions/tree_ext.pyx",
    "content": "# distutils: language=c++\n# cython: embedsignature=True, language_level=3\n# cython: linetrace=True\n# cython: freethreading_compatible=True\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport malloc, free\nfrom libcpp.vector cimport vector\nfrom libcpp cimport bool\nfrom libc.math cimport INFINITY\n\nfrom .tree cimport int_t, Tree as c_Tree, PyWrapper, Node, Edge, Face, Cell as c_Cell\nfrom . cimport geom\n\nimport scipy.sparse as sp\nimport numpy as np\nfrom .interputils_cython cimport _bisect_left, _bisect_right\n\nclass TreeMeshNotFinalizedError(RuntimeError):\n    \"\"\"Raise when a TreeMesh is not finalized.\"\"\"\n\n\ncdef class TreeCell:\n    \"\"\"A class for defining cells within instances of :class:`~discretize.TreeMesh`.\n\n    This cannot be created in python, it can only be accessed by indexing the\n    :class:`~discretize.TreeMesh` object. ``TreeCell`` is the object being passed\n    to the user defined refine function when calling the\n    :py:attr:`~discretize.TreeMesh.refine` method for a :class:`~discretize.TreeMesh`.\n\n    Examples\n    --------\n    Here, we define a basic :class:`~discretize.TreeMesh` whose refinement is defined\n    by a simple function handle. After we have finalized the mesh, we index a ``TreeCell``\n    from the mesh. Once indexed, the user may examine its properties (center location,\n    dimensions, index of its neighbors, etc...)\n\n    >>> from discretize import TreeMesh\n    >>> import numpy as np\n\n    Refine a tree mesh radially outward from the center\n\n    >>> mesh = TreeMesh([32,32])\n    >>> def func(cell):\n    ...     r = np.linalg.norm(cell.center-0.5)\n    ...     return mesh.max_level if r<0.2 else mesh.max_level-2\n    >>> mesh.refine(func)\n\n    Then we can index the mesh to get access to the cell,\n\n    >>> tree_cell = mesh[16]\n    >>> tree_cell.origin\n    array([0.375, 0.25 ])\n\n    \"\"\"\n    cdef double _x, _y, _z, _x0, _y0, _z0, _wx, _wy, _wz\n    cdef int_t _dim\n    cdef c_Cell* _cell\n    cdef void _set(self, c_Cell* cell):\n        self._cell = cell\n        self._dim = cell.n_dim\n\n    @property\n    def nodes(self):\n        \"\"\"Indices for this cell's nodes within its parent tree mesh.\n\n        This property returns the indices of the nodes in the parent\n        tree mesh which correspond to this tree cell's nodes.\n\n        Returns\n        -------\n        list of int\n            Indices for this cell's nodes within its parent tree mesh\n        \"\"\"\n        cdef Node *points[8]\n        points = self._cell.points\n        if self._dim == 3:\n            return [points[0].index, points[1].index,\n                    points[2].index, points[3].index,\n                    points[4].index, points[5].index,\n                    points[6].index, points[7].index]\n        return [points[0].index, points[1].index,\n                points[2].index, points[3].index]\n\n    @property\n    def edges(self):\n        \"\"\"Indices for this cell's edges within its parent tree mesh.\n\n        This property returns the indices of the edges in the parent\n        tree mesh which correspond to this tree cell's edges.\n\n        Returns\n        -------\n        list of int\n            Indices for this cell's edges within its parent tree mesh\n        \"\"\"\n        cdef Edge *edges[12]\n        edges = self._cell.edges\n        if self._dim == 2:\n            return [edges[0].index, edges[1].index,\n                    edges[2].index, edges[3].index]\n        return [\n            edges[0].index, edges[1].index, edges[2].index, edges[3].index,\n            edges[4].index, edges[5].index, edges[6].index, edges[7].index,\n            edges[8].index, edges[9].index, edges[10].index, edges[11].index,\n        ]\n\n    @property\n    def faces(self):\n        \"\"\"Indices for this cell's faces within its parent tree mesh.\n\n        This property returns the indices of the faces in the parent\n        tree mesh which correspond to this tree cell's faces.\n\n        Returns\n        -------\n        list of int\n            Indices for this cell's faces within its parent tree mesh\n        \"\"\"\n        cdef Face *faces[6]\n        faces = self._cell.faces\n        if self._dim == 3:\n            return [\n                faces[0].index, faces[1].index,\n                faces[2].index, faces[3].index,\n                faces[4].index, faces[5].index\n            ]\n        cdef Edge *edges[12]\n        edges = self._cell.edges\n        return [edges[2].index, edges[3].index,\n                edges[0].index, edges[1].index]\n\n    @property\n    def center(self):\n        \"\"\"Cell center location for the tree cell.\n\n        Returns\n        -------\n        (dim) numpy.ndarray\n            Cell center location for the tree cell\n        \"\"\"\n        loc = self._cell.location\n        if self._dim == 2:\n            return np.array([loc[0], loc[1]])\n        return np.array([loc[0], loc[1], loc[2]])\n\n    @property\n    def origin(self):\n        \"\"\"Origin location ('anchor point') for the tree cell.\n\n        This property returns the origin location (or 'anchor point') for the\n        tree cell. The origin location is defined as the bottom-left-front\n        corner of the tree cell.\n\n        Returns\n        -------\n        (dim) numpy.ndarray\n            Origin location ('anchor point') for the tree cell\n        \"\"\"\n        loc = self._cell.min_node().location\n        if self._dim == 2:\n            return np.array([loc[0], loc[1]])\n        return np.array([loc[0], loc[1], loc[2]])\n\n    @property\n    def x0(self):\n        \"\"\"Origin location ('anchor point') for the tree cell.\n\n        This property returns the origin location (or 'anchor point') for the\n        tree cell. The origin location is defined as the bottom-left-front\n        corner of the tree cell.\n\n        Returns\n        -------\n        (dim) numpy.ndarray\n            Origin location ('anchor point') for the tree cell\n        \"\"\"\n        return self.origin\n\n    @property\n    def h(self):\n        \"\"\"Cell dimension along each axis direction.\n\n        This property returns a 1D array containing the dimensions of the\n        tree cell along the x, y (and z) directions, respectively.\n\n        Returns\n        -------\n        (dim) numpy.ndarray\n            Cell dimension along each axis direction\n        \"\"\"\n        loc_min = self._cell.min_node().location\n        loc_max = self._cell.max_node().location\n\n        if self._dim == 2:\n            return np.array([\n                loc_max[0] - loc_min[0],\n                loc_max[1] - loc_min[1],\n            ])\n        return np.array([\n            loc_max[0] - loc_min[0],\n            loc_max[1] - loc_min[1],\n            loc_max[2] - loc_min[2],\n        ])\n\n    @property\n    def dim(self):\n        \"\"\"Dimension of the tree cell; 1, 2 or 3.\n\n        Returns\n        -------\n        int\n            Dimension of the tree cell; 1, 2 or 3\n        \"\"\"\n        return self._dim\n\n    @property\n    def index(self):\n        \"\"\"Index of the tree cell within its parent tree mesh.\n\n        Returns\n        -------\n        int\n            Index of the tree cell within its parent tree mesh\n        \"\"\"\n        return self._cell.index\n\n    @property\n    def bounds(self):\n        \"\"\"\n        Bounds of the cell.\n\n        Coordinates that define the bounds of the cell. Bounds are returned in\n        the following order: ``x0``, ``x1``, ``y0``, ``y1``, ``z0``, ``z1``.\n\n        Returns\n        -------\n        bounds : (2 * dim) array\n            Array with the cell bounds.\n        \"\"\"\n        loc_min = self._cell.min_node().location\n        loc_max = self._cell.max_node().location\n\n        if self.dim == 2:\n            return np.array(\n                [\n                    loc_min[0],\n                    loc_max[0],\n                    loc_min[1],\n                    loc_max[1],\n                ]\n            )\n        return np.array(\n            [\n                loc_min[0],\n                loc_max[0],\n                loc_min[1],\n                loc_max[1],\n                loc_min[2],\n                loc_max[2],\n            ]\n        )\n\n\n    @property\n    def neighbors(self):\n        \"\"\"Indices for this cell's neighbors within its parent tree mesh.\n\n        Returns a list containing the indexes for the cell's neighbors.\n        The ordering of the neighboring cells (i.e. the list) is\n        [-x, +x, -y, +y, -z, +z]. For each entry in the list, there\n        are several cases:\n\n            - *ind >= 0:* the cell has a single neighbor in this direct and *ind* denotes its index\n            - *ind = -1:* the neighbour in this direction lies outside the mesh\n            - *ind = [ind_1, ind_2, ...]:* When the level changes between cells it shares a boarder with more than 1 cell in this direction (2 if `dim==2`, 4 if `dim==3`).\n\n        Returns\n        -------\n        list of int or (list of int)\n            Indices for this cell's neighbors within its parent tree mesh\n        \"\"\"\n        neighbors = [-1]*self._dim*2\n\n        for i in range(self._dim*2):\n            neighbor = self._cell.neighbors[i]\n            if neighbor is NULL:\n                continue\n            elif neighbor.is_leaf():\n                neighbors[i] = neighbor.index\n            else:\n                if self._dim==2:\n                    if i==0:\n                        neighbors[i] = [neighbor.children[1].index,\n                                        neighbor.children[3].index]\n                    elif i==1:\n                        neighbors[i] = [neighbor.children[0].index,\n                                        neighbor.children[2].index]\n                    elif i==2:\n                        neighbors[i] = [neighbor.children[2].index,\n                                        neighbor.children[3].index]\n                    else:\n                        neighbors[i] = [neighbor.children[0].index,\n                                        neighbor.children[1].index]\n                else:\n                    if i==0:\n                        neighbors[i] = [neighbor.children[1].index,\n                                        neighbor.children[3].index,\n                                        neighbor.children[5].index,\n                                        neighbor.children[7].index]\n                    elif i==1:\n                        neighbors[i] = [neighbor.children[0].index,\n                                        neighbor.children[2].index,\n                                        neighbor.children[4].index,\n                                        neighbor.children[6].index]\n                    elif i==2:\n                        neighbors[i] = [neighbor.children[2].index,\n                                        neighbor.children[3].index,\n                                        neighbor.children[6].index,\n                                        neighbor.children[7].index]\n                    elif i==3:\n                        neighbors[i] = [neighbor.children[0].index,\n                                        neighbor.children[1].index,\n                                        neighbor.children[4].index,\n                                        neighbor.children[5].index]\n                    elif i==4:\n                        neighbors[i] = [neighbor.children[4].index,\n                                        neighbor.children[5].index,\n                                        neighbor.children[6].index,\n                                        neighbor.children[7].index]\n                    else:\n                        neighbors[i] = [neighbor.children[0].index,\n                                        neighbor.children[1].index,\n                                        neighbor.children[2].index,\n                                        neighbor.children[3].index]\n        return neighbors\n\n    @property\n    def _index_loc(self):\n        loc_ind = self._cell.location_ind\n        if self._dim == 2:\n            return tuple((loc_ind[0], loc_ind[1]))\n        return tuple((loc_ind[0], loc_ind[1], loc_ind[2]))\n\n    @property\n    def _level(self):\n        return self._cell.level\n\ncdef int _evaluate_func(void* function, c_Cell* cell) noexcept with gil:\n    # Wraps a function to be called in C++\n    func = <object> function\n    pycell = TreeCell()\n    pycell._set(cell)\n    return <int> func(pycell)\n\ncdef class _TreeMesh:\n    cdef c_Tree *tree\n    cdef PyWrapper *wrapper\n    cdef int_t _dim\n    cdef int_t[3] ls\n    cdef int _finalized\n    cdef bool _diagonal_balance\n    cdef cython.pymutex _tree_modify_lock\n\n    cdef double[:] _xs, _ys, _zs\n    cdef double[:] _origin\n\n    cdef object _cell_centers, _nodes, _hanging_nodes\n    cdef object _edges_x, _edges_y, _edges_z, _hanging_edges_x, _hanging_edges_y, _hanging_edges_z\n    cdef object _faces_x, _faces_y, _faces_z, _hanging_faces_x, _hanging_faces_y, _hanging_faces_z\n\n    cdef object _h_gridded\n    cdef object _cell_volumes, _face_areas, _edge_lengths\n    cdef object _average_face_x_to_cell, _average_face_y_to_cell, _average_face_z_to_cell, _average_face_to_cell, _average_face_to_cell_vector\n    cdef object _average_node_to_cell, _average_node_to_edge, _average_node_to_edge_x, _average_node_to_edge_y, _average_node_to_edge_z\n    cdef object _average_node_to_face, _average_node_to_face_x, _average_node_to_face_y, _average_node_to_face_z\n    cdef object _average_edge_x_to_cell, _average_edge_y_to_cell, _average_edge_z_to_cell, _average_edge_to_cell, _average_edge_to_cell_vector\n    cdef object _average_cell_to_face, _average_cell_vector_to_face, _average_cell_to_face_x, _average_cell_to_face_y, _average_cell_to_face_z\n    cdef object _face_divergence\n    cdef object _edge_curl, _nodal_gradient\n\n    cdef object __ubc_order, __ubc_indArr\n\n    def __cinit__(self, *args, **kwargs):\n        self.wrapper = new PyWrapper()\n        self.tree = new c_Tree()\n\n    def __init__(self, h, origin, bool diagonal_balance=False):\n        super().__init__(h=h, origin=origin)\n        def is_pow2(num):\n            return ((num & (num - 1)) == 0) and num != 0\n        for n in self.shape_cells:\n            if not is_pow2(n):\n                raise ValueError(\"length of cell width vectors must be a power of 2\")\n        h = self.h\n        origin = self.origin\n\n        nx2 = 2*len(h[0])\n        ny2 = 2*len(h[1])\n        self._dim = len(origin)\n        self._origin = origin\n\n        xs = np.empty(nx2 + 1, dtype=float)\n        xs[::2] = np.cumsum(np.r_[origin[0], h[0]])\n        xs[1::2] = (xs[:-1:2] + xs[2::2])/2\n        self._xs = xs\n        self.ls[0] = int(np.log2(len(h[0])))\n\n        ys = np.empty(ny2 + 1, dtype=float)\n        ys[::2] = np.cumsum(np.r_[origin[1],h[1]])\n        ys[1::2] = (ys[:-1:2] + ys[2::2])/2\n        self._ys = ys\n        self.ls[1] = int(np.log2(len(h[1])))\n\n        if self._dim > 2:\n            nz2 = 2*len(h[2])\n\n            zs = np.empty(nz2 + 1, dtype=float)\n            zs[::2] = np.cumsum(np.r_[origin[2],h[2]])\n            zs[1::2] = (zs[:-1:2] + zs[2::2])/2\n            self._zs = zs\n            self.ls[2] = int(np.log2(len(h[2])))\n        else:\n            self._zs = np.zeros(1, dtype=float)\n            self.ls[2] = 1\n\n        self.tree.set_dimension(self._dim)\n        self.tree.set_levels(self.ls[0], self.ls[1], self.ls[2])\n        self.tree.set_xs(&self._xs[0], &self._ys[0], &self._zs[0])\n        self.tree.initialize_roots()\n        self._finalized = False\n        self._diagonal_balance = diagonal_balance\n        self._clear_cache()\n\n    def _clear_cache(self):\n        self._cell_centers = None\n        self._nodes = None\n        self._hanging_nodes = None\n        self._h_gridded = None\n\n        self._edges_x = None\n        self._edges_y = None\n        self._edges_z = None\n        self._hanging_edges_x = None\n        self._hanging_edges_y = None\n        self._hanging_edges_z = None\n\n        self._faces_x = None\n        self._faces_y = None\n        self._faces_z = None\n        self._hanging_faces_x = None\n        self._hanging_faces_y = None\n        self._hanging_faces_z = None\n\n        self._cell_volumes = None\n        self._face_areas = None\n        self._edge_lengths = None\n\n        self._average_cell_to_face = None\n        self._average_cell_to_face_x = None\n        self._average_cell_to_face_y = None\n        self._average_cell_to_face_z = None\n\n        self._average_face_x_to_cell = None\n        self._average_face_y_to_cell = None\n        self._average_face_z_to_cell = None\n        self._average_face_to_cell = None\n        self._average_face_to_cell_vector = None\n\n        self._average_edge_x_to_cell = None\n        self._average_edge_y_to_cell = None\n        self._average_edge_z_to_cell = None\n        self._average_edge_to_cell = None\n        self._average_edge_to_cell_vector = None\n        self._average_edge_to_face = None\n\n        self._average_node_to_cell = None\n        self._average_node_to_edge = None\n        self._average_node_to_face = None\n        self._average_node_to_edge_x = None\n        self._average_node_to_edge_y = None\n        self._average_node_to_edge_z = None\n        self._average_node_to_face_x = None\n        self._average_node_to_face_y = None\n        self._average_node_to_face_z = None\n\n        self._face_divergence = None\n        self._nodal_gradient = None\n        self._edge_curl = None\n\n        self.__ubc_order = None\n        self.__ubc_indArr = None\n\n    def refine(self, function, finalize=True, diagonal_balance=None):\n        \"\"\"Refine :class:`~discretize.TreeMesh` with user-defined function.\n\n        Refines the :class:`~discretize.TreeMesh` according to a user-defined function.\n        The function is recursively called on each cell of the mesh. The user-defined\n        function **must** accept an object of type :class:`~discretize.tree_mesh.TreeCell`\n        and **must** return an integer-like object denoting the desired refinement\n        level. Instead of a function, the user may also supply an integer defining\n        the minimum refinement level for all cells.\n\n        Parameters\n        ----------\n        function : callable or int\n            a function defining the desired refinement level,\n            or an integer to refine all cells to at least that level.\n            The input argument of the function **must** be an instance of\n            :class:`~discretize.tree_mesh.TreeCell`.\n        finalize : bool, optional\n            whether to finalize the mesh\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        Here, we define a QuadTree mesh with a domain width of 1 along\n        the x and y axes. We then refine the mesh to its maximum level\n        at locations within a distance of 0.2 of point (0.5, 0.5). The\n        function accepts and instance of :class:`~discretize.tree_mesh.TreeCell`\n        and returns an integer value denoting its level of refinement.\n\n        >>> from discretize import TreeMesh\n        >>> from matplotlib import pyplot\n\n        Define a mesh and refine it radially outward using the custom defined function\n\n\n        >>> mesh = TreeMesh([32,32])\n        >>> def func(cell):\n        ...     r = np.linalg.norm(cell.center-0.5)\n        ...     return mesh.max_level if r<0.2 else mesh.max_level-2\n        >>> mesh.refine(func)\n\n        >>> mesh.plot_grid()\n        >>> pyplot.show()\n\n        \"\"\"\n        if isinstance(function, int):\n            level = function\n            function = lambda cell: level\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        #Wrapping function so it can be called in c++\n        cdef void * func_ptr = <void *> function\n        \n\n        with self._tree_modify_lock:\n            self.wrapper.set(func_ptr, _evaluate_func)\n            #Then tell c++ to build the tree\n            self.tree.refine_function(self.wrapper, diag_balance)\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def refine_ball(self, points, radii, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Refine :class:`~discretize.TreeMesh` using radial distance (ball) and refinement level for a cluster of points.\n\n        For each point in the array `points`, this method refines the tree mesh\n        based on the radial distance (ball) and refinement level supplied. The method\n        accomplishes this by determining which cells intersect ball(s) and refining them\n        to the prescribed level(s) of refinement.\n\n        Parameters\n        ----------\n        points : (N, dim) array_like\n            The centers of the refinement balls\n        radii : float or (N) array_like of float\n            A 1D array defining the radius for each ball\n        levels : int or (N) array_like of int\n            A 1D array defining the maximum refinement level for each ball\n        finalize : bool, optional\n            Whether to finalize after refining\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        We create a simple mesh and refine the tree mesh such that all cells that\n        intersect the spherical balls are at the given levels.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> tree_mesh = discretize.TreeMesh([32, 32])\n        >>> tree_mesh.max_level\n        5\n\n        Next we define the center and radius of the two spheres, as well as the level\n        we want to refine them to, and refine the mesh.\n\n        >>> centers = [[0.1, 0.3], [0.6, 0.8]]\n        >>> radii = [0.2, 0.3]\n        >>> levels = [4, 5]\n        >>> tree_mesh.refine_ball(centers, radii, levels)\n\n        Now lets look at the mesh, and overlay the balls on it to ensure it refined\n        where we wanted it to.\n\n        >>> ax = tree_mesh.plot_grid()\n        >>> circ = patches.Circle(centers[0], radii[0], facecolor='none', edgecolor='r', linewidth=3)\n        >>> ax.add_patch(circ)\n        >>> circ = patches.Circle(centers[1], radii[1], facecolor='none', edgecolor='k', linewidth=3)\n        >>> ax.add_patch(circ)\n        >>> plt.show()\n        \"\"\"\n        points = self._require_ndarray_with_dim('points', points, ndim=2, dtype=np.float64)\n        radii = np.require(np.atleast_1d(radii), dtype=np.float64, requirements='C')\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n\n        cdef int_t n_balls = _check_first_dim_broadcast(points=points, radii=radii, levels=levels)\n\n        cdef double[:, :] cs = points\n        cdef double[:] rs = radii\n        cdef int[:] ls = levels\n\n        cdef int_t cs_step = cs.shape[0] > 1\n        cdef int_t rs_step = rs.shape[0] > 1\n        cdef int_t l_step = ls.shape[0] > 1\n        cdef int_t i_c=0, i_r=0, i_l=0\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef geom.Ball ball\n        cdef int_t i\n        cdef int l\n        cdef int max_level = self.max_level\n        for i in range(n_balls):\n            ball = geom.Ball(self._dim, &cs[i_c, 0], rs[i_r])\n            l = _wrap_levels(ls[i_l], max_level)\n        \n            with self._tree_modify_lock:\n                    self.tree.refine_geom(ball, l, diag_balance)\n\n            i_c += cs_step\n            i_r += rs_step\n            i_l += l_step\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def refine_box(self, x0s, x1s, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Refine the :class:`~discretize.TreeMesh` within the axis aligned boxes to the desired level.\n\n        Refines the TreeMesh by determining if a cell intersects the given axis aligned\n        box(es) to the prescribed level(s).\n\n        Parameters\n        ----------\n        x0s : (N, dim) array_like\n            The minimum location of the boxes\n        x1s : (N, dim) array_like\n            The maximum location of the boxes\n        levels : int or (N) array_like of int\n            The level to refine intersecting cells to\n        finalize : bool, optional\n            Whether to finalize after refining\n        diagonal_balance : None or bool, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        We create a simple mesh and refine the TreeMesh such that all cells that\n        intersect the boxes are at the given levels.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> tree_mesh = discretize.TreeMesh([32, 32])\n        >>> tree_mesh.max_level\n        5\n\n        Next we define the origins and furthest corners of the two rectangles, as\n        well as the level we want to refine them to, and refine the mesh.\n\n        >>> x0s = [[0.1, 0.1], [0.8, 0.8]]\n        >>> x1s = [[0.3, 0.2], [0.9, 1.0]]\n        >>> levels = [4, 5]\n        >>> tree_mesh.refine_box(x0s, x1s, levels)\n\n        Now lets look at the mesh, and overlay the boxes on it to ensure it refined\n        where we wanted it to.\n\n        >>> ax = tree_mesh.plot_grid()\n        >>> rect = patches.Rectangle([0.1, 0.1], 0.2, 0.1, facecolor='none', edgecolor='r', linewidth=3)\n        >>> ax.add_patch(rect)\n        >>> rect = patches.Rectangle([0.8, 0.8], 0.1, 0.2, facecolor='none', edgecolor='k', linewidth=3)\n        >>> ax.add_patch(rect)\n        >>> plt.show()\n        \"\"\"\n        x0s = self._require_ndarray_with_dim('x0s', x0s, ndim=2, dtype=np.float64)\n        x1s = self._require_ndarray_with_dim('x1s', x1s, ndim=2, dtype=np.float64)\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n\n        cdef int_t n_boxes = _check_first_dim_broadcast(x0s=x0s, x1s=x1s, levels=levels)\n\n        cdef double[:, :] x0 = x0s\n        cdef double[:, :] x1 = x1s\n        cdef int[:] ls = levels\n\n        cdef int_t x0_step = x0.shape[0] > 1\n        cdef int_t x1_step = x1.shape[0] > 1\n        cdef int_t l_step = ls.shape[0] > 1\n        cdef int_t i_x0=0, i_x1=0, i_l=0\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef geom.Box box\n        cdef int l\n        cdef int max_level = self.max_level\n        for i in range(n_boxes):\n            box = geom.Box(self._dim, &x0[i_x0, 0], &x1[i_x1, 0])\n            l = _wrap_levels(ls[i_l], max_level)\n            with self._tree_modify_lock:\n                self.tree.refine_geom(box, l, diag_balance)\n\n            i_x0 += x0_step\n            i_x1 += x1_step\n            i_l += l_step\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def refine_line(self, path, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Refine the :class:`~discretize.TreeMesh` along the line segment to the desired level.\n\n        Refines the TreeMesh by determining if a cell intersects the given line segment(s)\n        to the prescribed level(s).\n\n        Parameters\n        ----------\n        path : (N+1, dim) array_like\n            The nodes of the line segment(s).\n            i.e. `[[x0, y0, z0], [x1, y1, z1], [x2, y2, z2]]` would be two segments.\n        levels : int or (N) array_like of int\n            The level to refine intersecting cells to.\n        finalize : bool, optional\n            Whether to finalize after refining\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        We create a simple mesh and refine the TreeMesh such that all cells that\n        intersect the line segment path are at the given levels.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> tree_mesh = discretize.TreeMesh([32, 32])\n        >>> tree_mesh.max_level\n        5\n\n        Next we define the points along the line and the level we want to refine to,\n        and refine the mesh.\n\n        >>> segments = np.array([[0.1, 0.3], [0.3, 0.9], [0.8, 0.9]])\n        >>> levels = 5\n        >>> tree_mesh.refine_line(segments, levels)\n\n        Now lets look at the mesh, and overlay the line on it to ensure it refined\n        where we wanted it to.\n\n        >>> ax = tree_mesh.plot_grid()\n        >>> ax.plot(*segments.T, color='C1')\n        >>> plt.show()\n\n        \"\"\"\n        path = self._require_ndarray_with_dim('path', path, ndim=2, dtype=np.float64)\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n        cdef int_t n_segments = _check_first_dim_broadcast(path=path[:-1], levels=levels)\n        cdef double[:, :] line_nodes = path\n        cdef int[:] ls = levels\n\n        cdef int_t line_step = line_nodes.shape[0] > 2\n        cdef int_t l_step = levels.shape[0] > 1\n        cdef int_t i_line=0, i_l=0\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef geom.Line line\n\n        cdef int l\n        cdef int max_level = self.max_level\n        cdef int i\n        for i in range(n_segments):\n            line = geom.Line(self._dim, &line_nodes[i_line, 0], &line_nodes[i_line+1, 0])\n            l = _wrap_levels(ls[i_l], max_level)\n            with self._tree_modify_lock:\n                self.tree.refine_geom(line, l, diag_balance)\n\n            i_line += line_step\n            i_l += l_step\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def refine_plane(self, origins, normals, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Refine the :class:`~discretize.TreeMesh` along a plane to the desired level.\n\n        Refines the TreeMesh by determining if a cell intersects the given plane(s)\n        to the prescribed level(s).\n\n        Parameters\n        ----------\n        origins : (dim) or (N, dim) array_like of float\n            The origin of the planes.\n        normals : (dim) or (N, dim) array_like of float\n            The normals to the planes.\n        levels : int or (N) array_like of int\n            The level to refine intersecting cells to.\n        finalize : bool, optional\n            Whether to finalize after refining.\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        We create a simple mesh and refine the TreeMesh such that all cells that\n        intersect the plane path are at the given levels. (In 2D, the plane is also a\n        line.)\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> tree_mesh = discretize.TreeMesh([32, 32])\n        >>> tree_mesh.max_level\n        5\n\n        Next we define the origin and normal of the plane, and the level we want\n        to refine to.\n\n        >>> origin = [0, 0.25]\n        >>> normal = [-1, -1]\n        >>> level = -1\n        >>> tree_mesh.refine_plane(origin, normal, level)\n\n        Now lets look at the mesh, and overlay the plane on it to ensure it refined\n        where we wanted it to.\n\n        >>> ax = tree_mesh.plot_grid()\n        >>> ax.axline(origin, slope=-normal[0]/normal[1], color='C1')\n        >>> plt.show()\n\n        \"\"\"\n        origins = self._require_ndarray_with_dim('origins', origins, ndim=2, dtype=np.float64)\n        normals = self._require_ndarray_with_dim('normals', normals, ndim=2, dtype=np.float64)\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n\n        cdef int n_planes = _check_first_dim_broadcast(origins=origins, normals=normals, levels=levels)\n\n        cdef double[:, :] x_0s = origins\n        cdef double[:, :] norms = normals\n        cdef int[:] ls = levels\n\n        cdef int_t origin_step = x_0s.shape[0] > 1\n        cdef int_t normal_step = norms.shape[0] > 1\n        cdef int_t level_step = ls.shape[0] > 1\n        cdef int_t i_o=0, i_n=0, i_l=0\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef geom.Plane plane\n\n        cdef int l\n        cdef int max_level = self.max_level\n        cdef int i_plane\n        for i in range(n_planes):\n            plane = geom.Plane(self._dim, &x_0s[i_o, 0], &norms[i_n, 0])\n            l = _wrap_levels(ls[i_l], max_level)\n            with self._tree_modify_lock:\n                self.tree.refine_geom(plane, l, diag_balance)\n\n            i_o += origin_step\n            i_n += normal_step\n            i_l += level_step\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def refine_triangle(self, triangle, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Refine the :class:`~discretize.TreeMesh` along the triangle to the desired level.\n\n        Refines the TreeMesh by determining if a cell intersects the given triangle(s)\n        to the prescribed level(s).\n\n        Parameters\n        ----------\n        triangle : (N, 3, dim) array_like\n            The nodes of the triangle(s).\n        levels : int or (N) array_like of int\n            The level to refine intersecting cells to.\n        finalize : bool, optional\n            Whether to finalize after refining\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        We create a simple mesh and refine the TreeMesh such that all cells that\n        intersect the line segment path are at the given levels.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> tree_mesh = discretize.TreeMesh([32, 32])\n        >>> tree_mesh.max_level\n        5\n\n        Next we define the points along the line and the level we want to refine to,\n        and refine the mesh.\n\n        >>> triangle = [[0.14, 0.31], [0.32, 0.96], [0.23, 0.87]]\n        >>> levels = 5\n        >>> tree_mesh.refine_triangle(triangle, levels)\n\n        Now lets look at the mesh, and overlay the line on it to ensure it refined\n        where we wanted it to.\n\n        >>> ax = tree_mesh.plot_grid()\n        >>> tri = patches.Polygon(triangle, fill=False)\n        >>> ax.add_patch(tri)\n        >>> plt.show()\n\n        \"\"\"\n        triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=3, dtype=np.float64)\n        if triangle.shape[-2] != 3:\n            raise ValueError(f\"triangle array must be (N, 3, {self.dim})\")\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n        cdef int n_triangles = _check_first_dim_broadcast(triangle=triangle, levels=levels)\n\n        cdef double[:, :, :] tris = triangle\n        cdef int[:] ls = levels\n\n        cdef int_t tri_step = tris.shape[0] > 1\n        cdef int_t l_step = ls.shape[0] > 1\n        cdef int_t i_tri=0, i_l=0\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef geom.Triangle triang\n        cdef int l\n        cdef int max_level = self.max_level\n        for i in range(n_triangles):\n            triang = geom.Triangle(self._dim, &tris[i_tri, 0, 0], &tris[i_tri, 1, 0], &tris[i_tri, 2, 0])\n            l = _wrap_levels(ls[i_l], max_level)\n            with self._tree_modify_lock:\n                self.tree.refine_geom(triang, l, diag_balance)\n\n            i_tri += tri_step\n            i_l += l_step\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def refine_vertical_trianglular_prism(self, triangle, h, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Refine the :class:`~discretize.TreeMesh` along the trianglular prism to the desired level.\n\n        Refines the TreeMesh by determining if a cell intersects the given trianglular prism(s)\n        to the prescribed level(s).\n\n        Parameters\n        ----------\n        triangle : (N, 3, dim) array_like\n            The nodes of the bottom triangle(s).\n        h : (N) array_like\n            The height of the prism(s).\n        levels : int or (N) array_like of int\n            The level to refine intersecting cells to.\n        finalize : bool, optional\n            Whether to finalize after refining\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        See Also\n        --------\n        refine_surface\n\n        Examples\n        --------\n        We create a simple mesh and refine the TreeMesh such that all cells that\n        intersect the line segment path are at the given levels.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> mesh = discretize.TreeMesh([32, 32, 32])\n        >>> mesh.max_level\n        5\n\n        Next we define the bottom points of the prism, its heights, and the level we\n        want to refine to, then refine the mesh.\n\n        >>> triangle = [[0.14, 0.31, 0.21], [0.32, 0.96, 0.34], [0.87, 0.23, 0.12]]\n        >>> height = 0.35\n        >>> levels = 5\n        >>> mesh.refine_vertical_trianglular_prism(triangle, height, levels)\n\n        Now lets look at the mesh.\n\n        >>> v = mesh.cell_levels_by_index(np.arange(mesh.n_cells))\n        >>> fig, axs = plt.subplots(1, 3, figsize=(12,4))\n        >>> mesh.plot_slice(v, ax=axs[0], normal='x', grid=True, clim=[2, 5])\n        >>> mesh.plot_slice(v, ax=axs[1], normal='y', grid=True, clim=[2, 5])\n        >>> mesh.plot_slice(v, ax=axs[2], normal='z', grid=True, clim=[2, 5])\n        >>> plt.show()\n\n        \"\"\"\n        if self.dim == 2:\n            raise NotImplementedError(\"refine_vertical_trianglular_prism only implemented in 3D.\")\n        triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=3, dtype=np.float64)\n        if triangle.shape[-2] != 3:\n            raise ValueError(f\"triangle array must be (N, 3, {self.dim})\")\n\n        h = np.require(np.atleast_1d(h), dtype=np.float64, requirements=\"C\")\n        if np.any(h < 0):\n            raise ValueError(\"All heights must be positive.\")\n\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n\n        cdef int_t n_triangles = _check_first_dim_broadcast(triangle=triangle, h=h, levels=levels)\n\n        cdef double[:, :, :] tris = triangle\n        cdef double[:] hs = h\n        cdef int[:] ls = levels\n\n        cdef int_t tri_step = tris.shape[0] > 1\n        cdef int_t h_step = hs.shape[0] > 1\n        cdef int_t l_step = ls.shape[0] > 1\n        cdef int_t i_tri=0, i_h=0, i_l=0\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef geom.VerticalTriangularPrism vert_prism\n        cdef int l\n        cdef int max_level = self.max_level\n        for i in range(n_triangles):\n            vert_prism = geom.VerticalTriangularPrism(self._dim, &tris[i_tri, 0, 0], &tris[i_tri, 1, 0], &tris[i_tri, 2, 0], hs[i_h])\n            l = _wrap_levels(ls[i_l], max_level)\n            with self._tree_modify_lock:\n                self.tree.refine_geom(vert_prism, l, diag_balance)\n\n            i_tri += tri_step\n            i_h += h_step\n            i_l += l_step\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def refine_tetrahedron(self, tetra, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Refine the :class:`~discretize.TreeMesh` along the tetrahedron to the desired level.\n\n        Refines the TreeMesh by determining if a cell intersects the given triangle(s)\n        to the prescribed level(s).\n\n        Parameters\n        ----------\n        tetra : (N, dim+1, dim) array_like\n            The nodes of the tetrahedron(s).\n        levels : int or (N) array_like of int\n            The level to refine intersecting cells to.\n        finalize : bool, optional\n            Whether to finalize after refining\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        We create a simple mesh and refine the TreeMesh such that all cells that\n        intersect the line segment path are at the given levels.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> tree_mesh = discretize.TreeMesh([32, 32, 32])\n        >>> tree_mesh.max_level\n        5\n\n        Next we define the points along the line and the level we want to refine to,\n        and refine the mesh.\n\n        >>> tetra = [\n        ...     [0.32, 0.21, 0.15],\n        ...     [0.82, 0.19, 0.34],\n        ...     [0.14, 0.82, 0.29],\n        ...     [0.32, 0.27, 0.83],\n        ... ]\n        >>> levels = 5\n        >>> tree_mesh.refine_tetrahedron(tetra, levels)\n\n        Now lets look at the mesh, checking how the refine function proceeded.\n\n        >>> levels = tree_mesh.cell_levels_by_index(np.arange(tree_mesh.n_cells))\n        >>> ax = plt.gca()\n        >>> tree_mesh.plot_slice(levels, normal='z', slice_loc=0.2, grid=True, ax=ax)\n        >>> plt.show()\n\n        \"\"\"\n        if self.dim == 2:\n            return self.refine_triangle(tetra, levels, finalize=finalize, diagonal_balance=diagonal_balance)\n        tetra = self._require_ndarray_with_dim('tetra', tetra, ndim=3, dtype=np.float64)\n        if tetra.shape[-2] != self.dim+1:\n            raise ValueError(f\"tetra array must be (N, {self.dim+1}, {self.dim})\")\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n\n        cdef int_t n_triangles = _check_first_dim_broadcast(tetra=tetra, levels=levels)\n\n        cdef double[:, :, :] tris = tetra\n        cdef int[:] ls = levels\n\n        cdef int_t tri_step = tris.shape[0] > 1\n        cdef int_t l_step = ls.shape[0] > 1\n        cdef int_t i_tri=0, i_l=0\n\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef geom.Tetrahedron tet\n        cdef int l\n        cdef int max_level = self.max_level\n        for i in range(n_triangles):\n            l = _wrap_levels(ls[i_l], max_level)\n            tet = geom.Tetrahedron(self._dim, &tris[i_tri, 0, 0], &tris[i_tri, 1, 0], &tris[i_tri, 2, 0], &tris[i_tri, 3, 0])\n            \n            with self._tree_modify_lock:\n                self.tree.refine_geom(tet, l, diag_balance)\n\n            i_tri += tri_step\n            i_l += l_step\n        if finalize:\n            self.finalize()\n\n    @cython.cdivision(True)\n    def insert_cells(self, points, levels, finalize=True, diagonal_balance=None):\n        \"\"\"Insert cells into the :class:`~discretize.TreeMesh` that contain given points.\n\n        Insert cell(s) into the :class:`~discretize.TreeMesh` that contain the given point(s) at the\n        assigned level(s).\n\n        Parameters\n        ----------\n        points : (N, dim) array_like\n        levels : (N) array_like of int\n        finalize : bool, optional\n            Whether to finalize after inserting point(s)\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        Examples\n        --------\n        >>> from discretize import TreeMesh\n        >>> mesh = TreeMesh([32,32])\n        >>> mesh.insert_cells([0.5, 0.5], mesh.max_level)\n        >>> mesh\n        QuadTreeMesh: 3.91%% filled\n        Level : Number of cells               Mesh Extent               Cell Widths\n        -----------------------           min     ,     max            min   ,   max\n          2   :       12             ---------------------------   --------------------\n          3   :       13          x:      0.0     ,     1.0         0.03125  ,    0.25\n          4   :       11          y:      0.0     ,     1.0         0.03125  ,    0.25\n          5   :        4\n        -----------------------\n        Total :       40\n        \"\"\"\n        points = self._require_ndarray_with_dim('points', points, ndim=2, dtype=np.float64)\n        levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C')\n        cdef int_t n_points = _check_first_dim_broadcast(points=points, levels=levels)\n\n        cdef double[:, :] cs = points\n        cdef int[:] ls = levels\n\n        cdef int l\n        cdef int max_level = self.max_level\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        cdef int_t p_step = cs.shape[0] > 1\n        cdef int_t l_step = ls.shape[0] > 1\n        cdef int_t i_p=0, i_l=0\n\n        for i in range(ls.shape[0]):\n            l = _wrap_levels(ls[i_l], max_level)\n            with self._tree_modify_lock:\n                self.tree.insert_cell(&cs[i_p, 0], l, diagonal_balance)\n\n            i_l += l_step\n            i_p += p_step\n        if finalize:\n            self.finalize()\n\n    def refine_image(self, image, finalize=True, diagonal_balance=None):\n        \"\"\"Refine using an ND image, ensuring that each cell contains exactly one unique value.\n\n        This function takes an N-dimensional image, defined on the underlying fine tensor mesh,\n        and recursively subdivides each cell if that cell contains more than 1 unique value in the\n        image. This is useful when using the `TreeMesh` to represent an exact compressed form of an input\n        model.\n\n        Parameters\n        ----------\n        image : (shape_cells) numpy.ndarray\n            Must have the same shape as the base tensor mesh (`TreeMesh.shape_cells`), as if every cell on this mesh was\n            refined to it's maximum level.\n        finalize : bool, optional\n            Whether to finalize after inserting point(s)\n        diagonal_balance : bool or None, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the `TreeMesh`.\n\n        \"\"\"\n        if diagonal_balance is None:\n            diagonal_balance = self._diagonal_balance\n        cdef bool diag_balance = diagonal_balance\n\n        image = np.require(image, dtype=np.float64, requirements=\"F\")\n        cdef size_t n_expected = np.prod(self.shape_cells)\n        if image.size != n_expected:\n            raise ValueError(\n                f\"image array size: {image.size} must match the total number of cells in the base tensor mesh: {n_expected}\"\n            )\n        if image.ndim == 1:\n            image = image.reshape(self.shape_cells, order=\"F\")\n\n        if image.shape != self.shape_cells:\n            raise ValueError(\n                f\"image array shape: {image.shape} must match the base cell shapes: {self.shape_cells}\"\n            )\n        if self.dim == 2:\n            image = image[..., None]\n\n        cdef double[::1,:,:] image_dat = image\n        \n        with self._tree_modify_lock:\n            self.tree.refine_image(&image_dat[0, 0, 0], diag_balance)\n        if finalize:\n            self.finalize()\n\n\n\n    def finalize(self):\n        \"\"\"Finalize the :class:`~discretize.TreeMesh`.\n\n        Called once a tree mesh has been finalized; i.e. no further mesh\n        refinement will be carried out. The tree mesh must be finalized\n        before it can be used to call most of its properties or construct\n        operators. When finalized, mesh refinement is no longer enabled.\n\n        \"\"\"\n        with self._tree_modify_lock:\n            if not self._finalized:\n                self.tree.finalize_lists()\n                self.tree.number()\n                self._finalized=True\n\n    @property\n    def finalized(self):\n        \"\"\"Whether tree mesh is finalized.\n\n        This property returns a boolean stating whether the tree mesh has\n        been finalized; i.e. no further mesh refinement will be carried out.\n        A tree mesh must be finalized before it can be used to call most\n        of its properties or construct operators.\n        When finalized, mesh refinement is no longer enabled.\n\n        Returns\n        -------\n        bool\n            Returns *True* if finalized, *False* otherwise\n        \"\"\"\n        with self._tree_modify_lock:\n            val = self._finalized\n        return val\n\n    @property\n    @cython.boundscheck(False)\n    def cell_bounds(self):\n        cell_bounds = np.empty((self.n_cells, self.dim, 2), dtype=np.float64)\n        cdef np.float64_t[:, :, ::1] cell_bounds_view = cell_bounds\n\n        for cell in self.tree.cells:\n            min_loc = cell.min_node().location\n            max_loc = cell.max_node().location\n\n            for i in range(self._dim):\n                cell_bounds_view[cell.index, i, 0] = min_loc[i]\n                cell_bounds_view[cell.index, i, 1] = max_loc[i]\n\n        return cell_bounds.reshape((self.n_cells, -1))\n\n    def number(self):\n        \"\"\"Number the cells, nodes, faces, and edges of the TreeMesh.\"\"\"\n        \n        with self._tree_modify_lock:\n            self.tree.number()\n\n    def get_containing_cells(self, points):\n        \"\"\"Return the cells containing the given points.\n\n        Parameters\n        ----------\n        points : (dim) or (n_point, dim) array_like\n            The locations to query for the containing cells\n\n        Returns\n        -------\n        int or (n_point) numpy.ndarray of int\n            The indexes of cells containing each point.\n\n        \"\"\"\n        cdef double[:,:] d_locs = self._require_ndarray_with_dim(\n            'locs', points, ndim=2, dtype=np.float64\n        )\n        cdef int_t n_locs = d_locs.shape[0]\n        cdef np.int64_t[:] indexes = np.empty(n_locs, dtype=np.int64)\n        cdef double x, y, z\n        for i in range(n_locs):\n            x = d_locs[i, 0]\n            y = d_locs[i, 1]\n            if self._dim == 3:\n                z = d_locs[i, 2]\n            else:\n                z = 0\n            indexes[i] = self.tree.containing_cell(x, y, z).index\n        if n_locs==1:\n            return indexes[0]\n        return np.array(indexes)\n\n    def get_cells_in_ball(self, center, double radius):\n        \"\"\"Find the indices of cells that intersect a ball\n\n        Parameters\n        ----------\n        center : (dim) array_like\n            center of the ball.\n        radius : float\n            radius of the ball\n\n        Returns\n        -------\n        numpy.ndarray of int\n            The indices of cells which overlap the ball.\n        \"\"\"\n        cdef double[:] a = self._require_ndarray_with_dim('center', center, dtype=np.float64)\n\n        cdef geom.Ball ball = geom.Ball(self._dim, &a[0], radius)\n        return np.array(self.tree.find_cells_geom(ball))\n\n    def get_cells_on_line(self, segment):\n        \"\"\"Find the cells intersecting a line segment.\n\n        Parameters\n        ----------\n        segment : (2, dim) array-like\n            Beginning and ending point of the line segment.\n\n        Returns\n        -------\n        numpy.ndarray of int\n            Indices for cells that intersect the line defined by the two input\n            points.\n        \"\"\"\n        segment = self._require_ndarray_with_dim('segment', segment, ndim=2, dtype=np.float64)\n        if segment.shape[0] != 2:\n            raise ValueError(f\"A line segment has two points, not {segment.shape[0]}\")\n        cdef double[:] start = segment[0]\n        cdef double[:] end = segment[1]\n\n        cdef geom.Line line = geom.Line(self._dim, &start[0], &end[0])\n        return np.array(self.tree.find_cells_geom(line))\n\n    def get_cells_in_aabb(self, x_min, x_max):\n        \"\"\"Find the indices of cells that intersect an axis aligned bounding box (aabb)\n\n        Parameters\n        ----------\n        x_min : (dim, ) array_like\n            Minimum extent of the box.\n        x_max : (dim, ) array_like\n            Maximum extent of the box.\n\n        Returns\n        -------\n        numpy.ndarray of int\n            The indices of cells which overlap the axis aligned bounding box.\n        \"\"\"\n        cdef double[:] a = self._require_ndarray_with_dim('x_min', x_min, dtype=np.float64)\n        cdef double[:] b = self._require_ndarray_with_dim('x_max', x_max, dtype=np.float64)\n\n        cdef geom.Box box = geom.Box(self._dim, &a[0], &b[0])\n        return np.array(self.tree.find_cells_geom(box))\n\n    def get_cells_on_plane(self, origin, normal):\n        \"\"\"Find the indices of cells that intersect a plane.\n\n        Parameters\n        ----------\n        origin : (dim) array_like\n        normal : (dim) array_like\n\n        Returns\n        -------\n        numpy.ndarray of int\n            The indices of cells which intersect the plane.\n        \"\"\"\n        cdef double[:] orig = self._require_ndarray_with_dim('origin', origin, dtype=np.float64)\n        cdef double[:] norm = self._require_ndarray_with_dim('normal', normal, dtype=np.float64)\n\n        cdef geom.Plane plane = geom.Plane(self._dim, &orig[0], &norm[0])\n        return np.array(self.tree.find_cells_geom(plane))\n\n    def get_cells_in_triangle(self, triangle):\n        \"\"\"Find the indices of cells that intersect a triangle.\n\n        Parameters\n        ----------\n        triangle : (3, dim) array_like\n            The three points of the triangle.\n\n        Returns\n        -------\n        numpy.ndarray of int\n            The indices of cells which overlap the triangle.\n        \"\"\"\n        triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=2, dtype=np.float64)\n        if triangle.shape[0] != 3:\n            raise ValueError(f\"Triangle array must have three points, saw {triangle.shape[0]}\")\n        cdef double[:, :] tri = triangle\n\n        cdef geom.Triangle poly = geom.Triangle(self._dim, &tri[0, 0], &tri[1, 0], &tri[2, 0])\n        return np.array(self.tree.find_cells_geom(poly))\n\n    def get_cells_in_vertical_trianglular_prism(self, triangle, double h):\n        \"\"\"Find the indices of cells that intersect a vertical triangular prism.\n\n        Parameters\n        ----------\n        triangle : (3, dim) array_like\n            The three points of the triangle, assumes the top and bottom\n            faces are parallel.\n        h : float\n            The height of the prism.\n\n        Returns\n        -------\n        numpy.ndarray of int\n            The indices of cells which overlap the vertical triangular prism.\n        \"\"\"\n        if self.dim == 2:\n            raise NotImplementedError(\"vertical_trianglular_prism only implemented in 3D.\")\n        triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=2, dtype=np.float64)\n        if triangle.shape[0] != 3:\n            raise ValueError(f\"Triangle array must have three points, saw {triangle.shape[0]}\")\n        cdef double[:, :] tri = triangle\n\n        cdef geom.VerticalTriangularPrism vert = geom.VerticalTriangularPrism(self._dim, &tri[0, 0], &tri[1, 0], &tri[2, 0], h)\n        return np.array(self.tree.find_cells_geom(vert))\n\n    def get_cells_in_tetrahedron(self, tetra):\n        \"\"\"Find the indices of cells that intersect a tetrahedron.\n\n        Parameters\n        ----------\n        tetra : (dim+1, dim) array_like\n            The points of the tetrahedron(s).\n\n        Returns\n        -------\n        numpy.ndarray of int\n            The indices of cells which overlap the triangle.\n        \"\"\"\n        if self.dim == 2:\n            return self.get_cells_in_triangle(tetra)\n        tetra = self._require_ndarray_with_dim('tetra', tetra, ndim=2, dtype=np.float64)\n        if tetra.shape[0] != 4:\n            raise ValueError(f\"A tetrahedron is defined by 4 points in 3D, not {tetra.shape[0]}.\")\n        cdef double[:, :] tet = tetra\n\n        cdef geom.Tetrahedron poly = geom.Tetrahedron(self._dim, &tet[0, 0], &tet[1, 0], &tet[2, 0], &tet[3, 0])\n        return np.array(self.tree.find_cells_geom(poly))\n\n    def _set_origin(self, origin):\n        if not isinstance(origin, (list, tuple, np.ndarray)):\n            raise ValueError('origin must be a list, tuple or numpy array')\n        self._origin = np.asarray(origin, dtype=np.float64)\n        cdef int_t dim = self._origin.shape[0]\n        cdef double[:] shift\n        #cdef c_Cell *cell\n        cdef Node *node\n        cdef Edge *edge\n        cdef Face *face\n        if self.tree.n_dim > 0: # Will only happen if __init__ has been called\n            shift = np.empty(dim, dtype=np.float64)\n\n            shift[0] = self._origin[0] - self._xs[0]\n            shift[1] = self._origin[1] - self._ys[0]\n            if dim == 3:\n                shift[2] = self._origin[2] - self._zs[0]\n\n            with self._tree_modify_lock:\n                for i in range(self._xs.shape[0]):\n                    self._xs[i] += shift[0]\n                for i in range(self._ys.shape[0]):\n                    self._ys[i] += shift[1]\n                if dim == 3:\n                    for i in range(self._zs.shape[0]):\n                        self._zs[i] += shift[2]\n\n                #update the locations of all of the items\n                self.tree.shift_cell_centers(&shift[0])\n\n                for itN in self.tree.nodes:\n                    node = itN.second\n                    for i in range(dim):\n                        node.location[i] += shift[i]\n\n                for itE in self.tree.edges_x:\n                    edge = itE.second\n                    for i in range(dim):\n                        edge.location[i] += shift[i]\n\n                for itE in self.tree.edges_y:\n                    edge = itE.second\n                    for i in range(dim):\n                        edge.location[i] += shift[i]\n\n                if dim == 3:\n                    for itE in self.tree.edges_z:\n                        edge = itE.second\n                        for i in range(dim):\n                            edge.location[i] += shift[i]\n\n                    for itF in self.tree.faces_x:\n                        face = itF.second\n                        for i in range(dim):\n                            face.location[i] += shift[i]\n\n                    for itF in self.tree.faces_y:\n                        face = itF.second\n                        for i in range(dim):\n                            face.location[i] += shift[i]\n\n                    for itF in self.tree.faces_z:\n                        face = itF.second\n                        for i in range(dim):\n                            face.location[i] += shift[i]\n            #clear out all cached grids\n            self._cell_centers = None\n            self._nodes = None\n            self._hanging_nodes = None\n            self._edges_x = None\n            self._hanging_edges_x = None\n            self._edges_y = None\n            self._hanging_edges_y = None\n            self._edges_z = None\n            self._hanging_edges_z = None\n            self._faces_x = None\n            self._hanging_faces_x = None\n            self._faces_y = None\n            self._hanging_faces_y = None\n            self._faces_z = None\n            self._hanging_faces_z = None\n\n    @property\n    def fill(self):\n        \"\"\"How 'filled' the tree mesh is compared to the underlying tensor mesh.\n\n        This property outputs the ratio between the number of cells in the\n        tree mesh and the number of cells in the underlying tensor mesh;\n        where the underlying tensor mesh is equivalent to the uniform tensor\n        mesh that uses the smallest cell size. Thus the output is a number between 0 and 1.\n\n        Returns\n        -------\n        float\n            A fractional percent denoting how 'filled' the tree mesh is\n        \"\"\"\n        #Tensor mesh cells:\n        cdef int_t nxc, nyc, nzc;\n        nxc = (self._xs.shape[0]-1)//2\n        nyc = (self._ys.shape[0]-1)//2\n        nzc = (self._zs.shape[0]-1)//2 if self._dim==3 else 1\n        return float(self.n_cells)/(nxc * nyc * nzc)\n\n    @property\n    def max_used_level(self):\n        \"\"\"Maximum refinement level used.\n\n        Returns the maximum refinement level used to construct the\n        tree mesh. The maximum used level is equal or less than the\n        maximum allowable level; see :py:attr:`.~TreeMesh.max_level`.\n\n        Returns\n        -------\n        int\n            Maximum level used when refining the mesh\n        \"\"\"\n        cdef int level = 0\n        for cell in self.tree.cells:\n            level = max(level, cell.level)\n        return level\n\n    @property\n    def max_level(self):\n        r\"\"\"Maximum allowable refinement level for the mesh.\n\n        The maximum refinement level for a tree mesh depends on\n        the number of underlying tensor mesh cells along each axis\n        direction; which are always powers of 2. Where *N* is the\n        number of underlying tensor mesh cells along a given axis, then the\n        maximum allowable level of refinement :math:`k_{max}` is given by:\n\n        .. math::\n            k_{max} = \\log_2(N)\n\n        Returns\n        -------\n        int\n            Maximum allowable refinement level for the mesh\n\n        \"\"\"\n        return self.tree.max_level\n\n    @property\n    def n_cells(self):\n        \"\"\"Total number of cells in the mesh.\n\n        Returns\n        -------\n        int\n            Number of cells in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nC**\n        \"\"\"\n        return self.tree.cells.size()\n\n    @property\n    def n_nodes(self):\n        \"\"\"Total number of nodes in the mesh.\n\n        Returns\n        -------\n        int\n            Number of nodes in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nN**\n        \"\"\"\n        return self.n_total_nodes - self.n_hanging_nodes\n\n    @property\n    def n_total_nodes(self):\n        \"\"\"Number of hanging and non-hanging nodes.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging nodes\n        \"\"\"\n        return self.tree.nodes.size()\n\n    @property\n    def n_hanging_nodes(self):\n        \"\"\"Number of hanging nodes.\n\n        Returns\n        -------\n        int\n            Number of hanging nodes\n        \"\"\"\n        return self.tree.hanging_nodes.size()\n\n    @property\n    def n_edges(self):\n        \"\"\"Total number of edges in the mesh.\n\n        Returns\n        -------\n        int\n            Total number of edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nE**\n        \"\"\"\n        return self.n_edges_x + self.n_edges_y + self.n_edges_z\n\n    @property\n    def n_hanging_edges(self):\n        \"\"\"Total number of hanging edges in all dimensions.\n\n        Returns\n        -------\n        int\n            Number of hanging edges in all dimensions\n        \"\"\"\n        return self.n_hanging_edges_x + self.n_hanging_edges_y + self.n_hanging_edges_z\n\n    @property\n    def n_total_edges(self):\n        \"\"\"Total number of hanging and non-hanging edges in all dimensions.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging edges in all dimensions\n        \"\"\"\n        return self.n_edges + self.n_hanging_edges\n\n    @property\n    def n_edges_x(self):\n        \"\"\"Number of x-edges in the mesh.\n\n        This property returns the number of edges that\n        are parallel to the x-axis; i.e. x-edges.\n\n        Returns\n        -------\n        int\n            Number of x-edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nEx**\n        \"\"\"\n        return self.n_total_edges_x - self.n_hanging_edges_x\n\n    @property\n    def n_edges_y(self):\n        \"\"\"Number of y-edges in the mesh.\n\n        This property returns the number of edges that\n        are parallel to the y-axis; i.e. y-edges.\n\n        Returns\n        -------\n        int\n            Number of y-edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nEy**\n        \"\"\"\n        return self.n_total_edges_y - self.n_hanging_edges_y\n\n    @property\n    def n_edges_z(self):\n        \"\"\"Number of z-edges in the mesh.\n\n        This property returns the number of edges that\n        are parallel to the z-axis; i.e. z-edges.\n\n        Returns\n        -------\n        int\n            Number of z-edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nEz**\n        \"\"\"\n        return self.n_total_edges_z - self.n_hanging_edges_z\n\n    @property\n    def n_total_edges_x(self):\n        \"\"\"Number of hanging and non-hanging x-edges in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging x-edges in the mesh\n        \"\"\"\n        return self.tree.edges_x.size()\n\n    @property\n    def n_total_edges_y(self):\n        \"\"\"Number of hanging and non-hanging y-edges in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging y-edges in the mesh\n        \"\"\"\n        return self.tree.edges_y.size()\n\n    @property\n    def n_total_edges_z(self):\n        \"\"\"Number of hanging and non-hanging z-edges in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging z-edges in the mesh\n        \"\"\"\n        return self.tree.edges_z.size()\n\n    @property\n    def n_hanging_edges_x(self):\n        \"\"\"Number of hanging x-edges in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging x-edges in the mesh\n        \"\"\"\n        return self.tree.hanging_edges_x.size()\n\n    @property\n    def n_hanging_edges_y(self):\n        \"\"\"Number of hanging y-edges in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging y-edges in the mesh\n        \"\"\"\n        return self.tree.hanging_edges_y.size()\n\n    @property\n    def n_hanging_edges_z(self):\n        \"\"\"Number of hanging z-edges in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging z-edges in the mesh\n        \"\"\"\n        return self.tree.hanging_edges_z.size()\n\n    @property\n    def n_faces(self):\n        \"\"\"Total number of faces in the mesh.\n\n        Returns\n        -------\n        int\n            Total number of faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nF**\n        \"\"\"\n        return self.n_faces_x + self.n_faces_y + self.n_faces_z\n\n    @property\n    def n_hanging_faces(self):\n        \"\"\"Total number of hanging faces in the mesh.\n\n        Returns\n        -------\n        int\n            Total number of non-hanging faces in the mesh\n        \"\"\"\n        return self.n_hanging_faces_x + self.n_hanging_faces_y + self.n_hanging_faces_z\n\n    @property\n    def n_total_faces(self):\n        \"\"\"Total number of hanging and non-hanging faces in the mesh.\n\n        Returns\n        -------\n        int\n            Total number of non-hanging faces in the mesh\n        \"\"\"\n        return self.n_faces + self.n_hanging_faces\n\n    @property\n    def n_faces_x(self):\n        \"\"\"Number of x-faces in the mesh.\n\n        This property returns the number of faces whose normal\n        vector is parallel to the x-axis; i.e. x-faces.\n\n        Returns\n        -------\n        int\n            Number of x-faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nFx**\n        \"\"\"\n        return self.n_total_faces_x - self.n_hanging_faces_x\n\n    @property\n    def n_faces_y(self):\n        \"\"\"Number of y-faces in the mesh.\n\n        This property returns the number of faces whose normal\n        vector is parallel to the y-axis; i.e. y-faces.\n\n        Returns\n        -------\n        int\n            Number of y-faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nFy**\n        \"\"\"\n        return self.n_total_faces_y - self.n_hanging_faces_y\n\n    @property\n    def n_faces_z(self):\n        \"\"\"Number of z-faces in the mesh.\n\n        This property returns the number of faces whose normal\n        vector is parallel to the z-axis; i.e. z-faces.\n\n        Returns\n        -------\n        int\n            Number of z-faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nFz**\n        \"\"\"\n        return self.n_total_faces_z - self.n_hanging_faces_z\n\n    @property\n    def n_total_faces_x(self):\n        \"\"\"Number of hanging and non-hanging x-faces in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging x-faces in the mesh\n        \"\"\"\n        if(self._dim == 2): return self.n_total_edges_y\n        return self.tree.faces_x.size()\n\n    @property\n    def n_total_faces_y(self):\n        \"\"\"Number of hanging and non-hanging y-faces in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging y-faces in the mesh\n        \"\"\"\n        if(self._dim == 2): return self.n_total_edges_x\n        return self.tree.faces_y.size()\n\n    @property\n    def n_total_faces_z(self):\n        \"\"\"Number of hanging and non-hanging z-faces in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging and non-hanging z-faces in the mesh\n        \"\"\"\n        if(self._dim == 2): return 0\n        return self.tree.faces_z.size()\n\n    @property\n    def n_hanging_faces_x(self):\n        \"\"\"Number of hanging x-faces in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging x-faces in the mesh\n        \"\"\"\n        if(self._dim == 2): return self.n_hanging_edges_y\n        return self.tree.hanging_faces_x.size()\n\n    @property\n    def n_hanging_faces_y(self):\n        \"\"\"Number of hanging y-faces in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging y-faces in the mesh\n        \"\"\"\n        if(self._dim == 2): return self.n_hanging_edges_x\n        return self.tree.hanging_faces_y.size()\n\n    @property\n    def n_hanging_faces_z(self):\n        \"\"\"Number of hanging z-faces in the mesh.\n\n        Returns\n        -------\n        int\n            Number of hanging z-faces in the mesh\n        \"\"\"\n        if(self._dim == 2): return 0\n        return self.tree.hanging_faces_z.size()\n\n    @property\n    def cell_centers(self):\n        \"\"\"Gridded cell center locations.\n\n        This property returns a numpy array of shape (n_cells, dim)\n        containing gridded cell center locations for all cells in the\n        mesh.\n\n        Returns\n        -------\n        (n_cells, dim) numpy.ndarray of float\n            Gridded cell center locations\n        \"\"\"\n        self._error_if_not_finalized(\"cell_centers\")\n        cdef np.float64_t[:, :] gridCC\n        cdef np.int64_t ii, ind, dim\n        if self._cell_centers is None:\n            dim = self._dim\n            self._cell_centers = np.empty((self.n_cells, self._dim), dtype=np.float64)\n            gridCC = self._cell_centers\n            for cell in self.tree.cells:\n                ind = cell.index\n                for ii in range(dim):\n                    gridCC[ind, ii] = cell.location[ii]\n        return self._cell_centers\n\n    @property\n    def nodes(self):\n        \"\"\"Gridded non-hanging nodes locations.\n\n        This property returns a numpy array of shape (n_nodes, dim)\n        containing gridded locations for all non-hanging nodes in the\n        mesh.\n\n        Returns\n        -------\n        (n_nodes, dim) numpy.ndarray of float\n            Gridded non-hanging node locations\n        \"\"\"\n        self._error_if_not_finalized(\"nodes\")\n        cdef np.float64_t[:, :] gridN\n        cdef Node *node\n        cdef np.int64_t ii, ind, dim\n        if self._nodes is None:\n            dim = self._dim\n            self._nodes = np.empty((self.n_nodes, dim) ,dtype=np.float64)\n            gridN = self._nodes\n            for it in self.tree.nodes:\n                node = it.second\n                if not node.hanging:\n                    ind = node.index\n                    for ii in range(dim):\n                        gridN[ind, ii] = node.location[ii]\n        return self._nodes\n\n    @property\n    def hanging_nodes(self):\n        \"\"\"Gridded hanging node locations.\n\n        This property returns a numpy array of shape (n_hanging_nodes, dim)\n        containing gridded locations for all hanging nodes in the\n        mesh.\n\n        Returns\n        -------\n        (n_hanging_nodes, dim) numpy.ndarray of float\n            Gridded hanging node locations\n        \"\"\"\n        self._error_if_not_finalized(\"hanging_nodes\")\n        cdef np.float64_t[:, :] gridN\n        cdef Node *node\n        cdef np.int64_t ii, ind, dim\n        if self._hanging_nodes is None:\n            dim = self._dim\n            self._hanging_nodes = np.empty((self.n_hanging_nodes, dim), dtype=np.float64)\n            gridhN = self._hanging_nodes\n            for node in self.tree.hanging_nodes:\n                ind = node.index-self.n_nodes\n                for ii in range(dim):\n                    gridhN[ind, ii] = node.location[ii]\n        return self._hanging_nodes\n\n    @property\n    def boundary_nodes(self):\n        \"\"\"Gridded boundary node locations.\n\n        This property returns a numpy array of shape\n        (n_boundary_nodes, dim) containing the gridded locations\n        of the nodes on the boundary of the mesh.\n\n        Returns\n        -------\n        (n_boundary_nodes, dim) numpy.ndarray of float\n            Gridded boundary node locations\n        \"\"\"\n        self._error_if_not_finalized(\"boundary_nodes\")\n        nodes = self.nodes\n        x0, xF = self._xs[0], self._xs[-1]\n        y0, yF = self._ys[0], self._ys[-1]\n        is_boundary = (\n            (nodes[:, 0] == x0)\n            | (nodes[:, 0] == xF)\n            | (nodes[:, 1] == y0)\n            | (nodes[:, 1] == yF)\n        )\n        if self.dim > 2:\n            z0, zF = self._zs[0], self._zs[-1]\n            is_boundary |= (\n                (nodes[:, 2] == z0)\n                | (nodes[:, 2] == zF)\n            )\n        return nodes[is_boundary]\n\n    @property\n    def h_gridded(self):\n        \"\"\"Gridded cell dimensions.\n\n        This property returns a numpy array of shape (n_cells, dim)\n        containing the dimensions of the cells along each axis\n        direction in order. E.g. the columns of *h_gridded* for a\n        3D tree mesh would be ordered [hx,hy,hz].\n\n        Returns\n        -------\n        (n_cells, dim) numpy.ndarray of float\n            Gridded cell dimensions\n        \"\"\"\n        self._error_if_not_finalized(\"h_gridded\")\n        if self._h_gridded is not None:\n            return self._h_gridded\n        cdef np.float64_t[:, :] gridCH\n        cdef np.int64_t ii, ind, dim\n        cdef np.float64_t len\n        cdef int epc = 4 if self._dim==3 else 2\n        dim = self._dim\n        self._h_gridded = np.empty((self.n_cells, dim), dtype=np.float64)\n        gridCH = self._h_gridded\n        for cell in self.tree.cells:\n            ind = cell.index\n            for ii in range(dim):\n                gridCH[ind, ii] = cell.edges[ii*epc].length\n\n        return self._h_gridded\n\n    @property\n    def edges_x(self):\n        \"\"\"Gridded locations of non-hanging x-edges.\n\n        This property returns a numpy array of shape (n_edges_x, dim)\n        containing gridded locations for all non-hanging x-edges.\n\n        Returns\n        -------\n        (n_edges_x, dim) numpy.ndarray of float\n            Gridded locations of all non-hanging x-edges\n        \"\"\"\n        self._error_if_not_finalized(\"edges_x\")\n        cdef np.float64_t[:, :] gridEx\n        cdef Edge *edge\n        cdef np.int64_t ii, ind, dim\n        if self._edges_x is None:\n            dim = self._dim\n            self._edges_x = np.empty((self.n_edges_x, dim), dtype=np.float64)\n            gridEx = self._edges_x\n            for it in self.tree.edges_x:\n                edge = it.second\n                if not edge.hanging:\n                    ind = edge.index\n                    for ii in range(dim):\n                        gridEx[ind, ii] = edge.location[ii]\n        return self._edges_x\n\n    @property\n    def hanging_edges_x(self):\n        \"\"\"Gridded locations of hanging x-edges.\n\n        This property returns a numpy array of shape (n_hanging_edges_x, dim)\n        containing gridded locations for all hanging x-edges.\n\n        Returns\n        -------\n        (n_hanging_edges_x, dim) numpy.ndarray of float\n            Gridded locations of all hanging x-edges\n        \"\"\"\n        self._error_if_not_finalized(\"hanging_edges_x\")\n        cdef np.float64_t[:, :] gridhEx\n        cdef Edge *edge\n        cdef np.int64_t ii, ind, dim\n        if self._hanging_edges_x is None:\n            dim = self._dim\n            self._hanging_edges_x = np.empty((self.n_hanging_edges_x, dim), dtype=np.float64)\n            gridhEx = self._hanging_edges_x\n            for edge in self.tree.hanging_edges_x:\n                ind = edge.index-self.n_edges_x\n                for ii in range(dim):\n                    gridhEx[ind, ii] = edge.location[ii]\n        return self._hanging_edges_x\n\n    @property\n    def edges_y(self):\n        \"\"\"Gridded locations of non-hanging y-edges.\n\n        This property returns a numpy array of shape (n_edges_y, dim)\n        containing gridded locations for all non-hanging y-edges.\n\n        Returns\n        -------\n        (n_edges_y, dim) numpy.ndarray of float\n            Gridded locations of all non-hanging y-edges\n        \"\"\"\n        self._error_if_not_finalized(\"edges_y\")\n        cdef np.float64_t[:, :] gridEy\n        cdef Edge *edge\n        cdef np.int64_t ii, ind, dim\n        if self._edges_y is None:\n            dim = self._dim\n            self._edges_y = np.empty((self.n_edges_y, dim), dtype=np.float64)\n            gridEy = self._edges_y\n            for it in self.tree.edges_y:\n                edge = it.second\n                if not edge.hanging:\n                    ind = edge.index\n                    for ii in range(dim):\n                        gridEy[ind, ii] = edge.location[ii]\n        return self._edges_y\n\n    @property\n    def hanging_edges_y(self):\n        \"\"\"Gridded locations of hanging y-edges.\n\n        This property returns a numpy array of shape (n_haning_edges_y, dim)\n        containing gridded locations for all hanging y-edges.\n\n        Returns\n        -------\n        (n_haning_edges_y, dim) numpy.ndarray of float\n            Gridded locations of all hanging y-edges\n        \"\"\"\n        self._error_if_not_finalized(\"hanging_edges_y\")\n        cdef np.float64_t[:, :] gridhEy\n        cdef Edge *edge\n        cdef np.int64_t ii, ind, dim\n        if self._hanging_edges_y is None:\n            dim = self._dim\n            self._hanging_edges_y = np.empty((self.n_hanging_edges_y, dim), dtype=np.float64)\n            gridhEy = self._hanging_edges_y\n            for edge in self.tree.hanging_edges_y:\n                ind = edge.index-self.n_edges_y\n                for ii in range(dim):\n                    gridhEy[ind, ii] = edge.location[ii]\n        return self._hanging_edges_y\n\n    @property\n    def edges_z(self):\n        \"\"\"Gridded locations of non-hanging z-edges.\n\n        This property returns a numpy array of shape (n_edges_z, dim)\n        containing gridded locations for all non-hanging z-edges.\n\n        Returns\n        -------\n        (n_edges_z, dim) numpy.ndarray of float\n            Gridded locations of all non-hanging z-edges\n        \"\"\"\n        self._error_if_not_finalized(\"edges_z\")\n        cdef np.float64_t[:, :] gridEz\n        cdef Edge *edge\n        cdef np.int64_t ii, ind, dim\n        if self._edges_z is None:\n            dim = self._dim\n            self._edges_z = np.empty((self.n_edges_z, dim), dtype=np.float64)\n            gridEz = self._edges_z\n            for it in self.tree.edges_z:\n                edge = it.second\n                if not edge.hanging:\n                    ind = edge.index\n                    for ii in range(dim):\n                        gridEz[ind, ii] = edge.location[ii]\n        return self._edges_z\n\n    @property\n    def hanging_edges_z(self):\n        \"\"\"Gridded locations of hanging z-edges.\n\n        This property returns a numpy array of shape (n_hanging_edges_z, dim)\n        containing gridded locations for all hanging z-edges.\n\n        Returns\n        -------\n        (n_hanging_edges_z, dim) numpy.ndarray of float\n            Gridded locations of all hanging z-edges\n        \"\"\"\n        self._error_if_not_finalized(\"hanging_edges_z\")\n        cdef np.float64_t[:, :] gridhEz\n        cdef Edge *edge\n        cdef np.int64_t ii, ind, dim\n        if self._hanging_edges_z is None:\n            dim = self._dim\n            self._hanging_edges_z = np.empty((self.n_hanging_edges_z, dim), dtype=np.float64)\n            gridhEz = self._hanging_edges_z\n            for edge in self.tree.hanging_edges_z:\n                ind = edge.index-self.n_edges_z\n                for ii in range(dim):\n                    gridhEz[ind, ii] = edge.location[ii]\n        return self._hanging_edges_z\n\n    @property\n    def boundary_edges(self):\n        \"\"\"Gridded boundary edge locations.\n\n        This property returns a numpy array of shape\n        (n_boundary_edges, dim) containing the gridded locations\n        of the edges on the boundary of the mesh. The returned\n        quantity is organized *np.r_[edges_x, edges_y, edges_z]* .\n\n        Returns\n        -------\n        (n_boundary_edges, dim) numpy.ndarray of float\n            Gridded boundary edge locations\n        \"\"\"\n        self._error_if_not_finalized(\"boundary_edges\")\n        edges_x = self.edges_x\n        edges_y = self.edges_y\n        x0, xF = self._xs[0], self._xs[-1]\n        y0, yF = self._ys[0], self._ys[-1]\n        is_boundary_x = (edges_x[:, 1] == y0) | (edges_x[:, 1] == yF)\n        is_boundary_y = (edges_y[:, 0] == x0) | (edges_y[:, 0] == xF)\n\n        if self.dim > 2:\n            z0, zF = self._zs[0], self._zs[-1]\n            edges_z = self.edges_z\n\n            is_boundary_x |= (edges_x[:, 2] == z0) | (edges_x[:, 2] == zF)\n            is_boundary_y |= (edges_y[:, 2] == z0) | (edges_y[:, 2] == zF)\n            is_boundary_z = (\n                (edges_z[:, 0] == x0)\n                | (edges_z[:, 0] == xF)\n                | (edges_z[:, 1] == y0)\n                | (edges_z[:, 1] == yF)\n            )\n            return np.r_[\n                edges_x[is_boundary_x],\n                edges_y[is_boundary_y],\n                edges_z[is_boundary_z]\n            ]\n        else:\n            return np.r_[edges_x[is_boundary_x], edges_y[is_boundary_y]]\n\n    @property\n    def faces_x(self):\n        \"\"\"Gridded locations of non-hanging x-faces.\n\n        This property returns a numpy array of shape (n_faces_x, dim)\n        containing gridded locations for all non-hanging x-faces.\n\n        Returns\n        -------\n        (n_faces_x, dim) numpy.ndarray of float\n            Gridded locations of all non-hanging x-faces\n        \"\"\"\n        self._error_if_not_finalized(\"faces_x\")\n        if(self._dim == 2): return self.edges_y\n\n        cdef np.float64_t[:, :] gridFx\n        cdef Face *face\n        cdef np.int64_t ii, ind, dim\n        if self._faces_x is None:\n            dim = self._dim\n            self._faces_x = np.empty((self.n_faces_x, dim), dtype=np.float64)\n            gridFx = self._faces_x\n            for it in self.tree.faces_x:\n                face = it.second\n                if not face.hanging:\n                    ind = face.index\n                    for ii in range(dim):\n                        gridFx[ind, ii] = face.location[ii]\n        return self._faces_x\n\n    @property\n    def faces_y(self):\n        \"\"\"Gridded locations of non-hanging y-faces.\n\n        This property returns a numpy array of shape (n_faces_y, dim)\n        containing gridded locations for all non-hanging y-faces.\n\n        Returns\n        -------\n        (n_faces_y, dim) numpy.ndarray of float\n            Gridded locations of all non-hanging y-faces\n        \"\"\"\n        self._error_if_not_finalized(\"faces_y\")\n        if(self._dim == 2): return self.edges_x\n        cdef np.float64_t[:, :] gridFy\n        cdef Face *face\n        cdef np.int64_t ii, ind, dim\n        if self._faces_y is None:\n            dim = self._dim\n            self._faces_y = np.empty((self.n_faces_y, dim), dtype=np.float64)\n            gridFy = self._faces_y\n            for it in self.tree.faces_y:\n                face = it.second\n                if not face.hanging:\n                    ind = face.index\n                    for ii in range(dim):\n                        gridFy[ind, ii] = face.location[ii]\n        return self._faces_y\n\n    @property\n    def faces_z(self):\n        \"\"\"Gridded locations of non-hanging z-faces.\n\n        This property returns a numpy array of shape (n_faces_z, dim)\n        containing gridded locations for all non-hanging z-faces.\n\n        Returns\n        -------\n        (n_faces_z, dim) numpy.ndarray of float\n            Gridded locations of all non-hanging z-faces\n        \"\"\"\n        self._error_if_not_finalized(\"faces_z\")\n        if(self._dim == 2): return self.cell_centers\n\n        cdef np.float64_t[:, :] gridFz\n        cdef Face *face\n        cdef np.int64_t ii, ind, dim\n        if self._faces_z is None:\n            dim = self._dim\n            self._faces_z = np.empty((self.n_faces_z, dim), dtype=np.float64)\n            gridFz = self._faces_z\n            for it in self.tree.faces_z:\n                face = it.second\n                if not face.hanging:\n                    ind = face.index\n                    for ii in range(dim):\n                        gridFz[ind, ii] = face.location[ii]\n        return self._faces_z\n\n    @property\n    def hanging_faces_x(self):\n        \"\"\"Gridded locations of hanging x-faces.\n\n        This property returns a numpy array of shape (n_hanging_faces_x, dim)\n        containing gridded locations for all hanging x-faces.\n\n        Returns\n        -------\n        (n_hanging_faces_x, dim) numpy.ndarray of float\n            Gridded locations of all hanging x-faces\n        \"\"\"\n        self._error_if_not_finalized(\"hanging_faces_x\")\n        if(self._dim == 2): return self.hanging_edges_y\n\n        cdef np.float64_t[:, :] gridFx\n        cdef Face *face\n        cdef np.int64_t ii, ind, dim\n        if self._hanging_faces_x is None:\n            dim = self._dim\n            self._hanging_faces_x = np.empty((self.n_hanging_faces_x, dim), dtype=np.float64)\n            gridhFx = self._hanging_faces_x\n            for face in self.tree.hanging_faces_x:\n                ind = face.index-self.n_faces_x\n                for ii in range(dim):\n                    gridhFx[ind, ii] = face.location[ii]\n        return self._hanging_faces_x\n\n    @property\n    def hanging_faces_y(self):\n        \"\"\"Gridded locations of hanging y-faces.\n\n        This property returns a numpy array of shape (n_hanging_faces_y, dim)\n        containing gridded locations for all hanging y-faces.\n\n        Returns\n        -------\n        (n_hanging_faces_y, dim) numpy.ndarray of float\n            Gridded locations of all hanging y-faces\n        \"\"\"\n        self._error_if_not_finalized(\"hanging_faces_y\")\n        if(self._dim == 2): return self.hanging_edges_x\n\n        cdef np.float64_t[:, :] gridhFy\n        cdef Face *face\n        cdef np.int64_t ii, ind, dim\n        if self._hanging_faces_y is None:\n            dim = self._dim\n            self._hanging_faces_y = np.empty((self.n_hanging_faces_y, dim), dtype=np.float64)\n            gridhFy = self._hanging_faces_y\n            for face in self.tree.hanging_faces_y:\n                ind = face.index-self.n_faces_y\n                for ii in range(dim):\n                    gridhFy[ind, ii] = face.location[ii]\n        return self._hanging_faces_y\n\n    @property\n    def hanging_faces_z(self):\n        \"\"\"Gridded locations of hanging z-faces.\n\n        This property returns a numpy array of shape (n_hanging_faces_z, dim)\n        containing gridded locations for all hanging z-faces.\n\n        Returns\n        -------\n        (n_hanging_faces_z, dim) numpy.ndarray of float\n            Gridded locations of all hanging z-faces\n        \"\"\"\n        self._error_if_not_finalized(\"hanging_faces_z\")\n        if(self._dim == 2): return np.array([])\n\n        cdef np.float64_t[:, :] gridhFz\n        cdef Face *face\n        cdef np.int64_t ii, ind, dim\n        if self._hanging_faces_z is None:\n            dim = self._dim\n            self._hanging_faces_z = np.empty((self.n_hanging_faces_z, dim), dtype=np.float64)\n            gridhFz = self._hanging_faces_z\n            for face in self.tree.hanging_faces_z:\n                ind = face.index-self.n_faces_z\n                for ii in range(dim):\n                    gridhFz[ind, ii] = face.location[ii]\n        return self._hanging_faces_z\n\n    @property\n    def boundary_faces(self):\n        \"\"\"Gridded boundary face locations.\n\n        This property returns a numpy array of shape\n        (n_boundary_faces, dim) containing the gridded locations\n        of the faces on the boundary of the mesh. The returned\n        quantity is organized *np.r_[faces_x, faces_y, faces_z]* .\n\n        Returns\n        -------\n        (n_boundary_faces, dim) numpy.ndarray of float\n            Gridded boundary face locations\n        \"\"\"\n        self._error_if_not_finalized(\"boundary_faces\")\n        faces_x = self.faces_x\n        faces_y = self.faces_y\n        x0, xF = self._xs[0], self._xs[-1]\n        y0, yF = self._ys[0], self._ys[-1]\n        is_boundary_x = (faces_x[:, 0] == x0) | (faces_x[:, 0] == xF)\n        is_boundary_y = (faces_y[:, 1] == y0) | (faces_y[:, 1] == yF)\n\n        boundary_faces = np.r_[faces_x[is_boundary_x], faces_y[is_boundary_y]]\n\n        if self.dim > 2:\n            z0, zF = self._zs[0], self._zs[-1]\n            faces_z = self.faces_z\n\n            is_boundary_z = (faces_z[:, 2] == z0) | (faces_z[:, 2] == zF)\n            boundary_faces = np.r_[boundary_faces, faces_z[is_boundary_z]]\n        return boundary_faces\n\n    @property\n    def boundary_face_outward_normals(self):\n        \"\"\"Outward normals of boundary faces.\n\n        For all boundary faces in the mesh, this property returns\n        the unit vectors denoting the outward normals to the boundary.\n\n        Returns\n        -------\n        (n_boundary_faces, dim) numpy.ndarray of float\n            Outward normals of boundary faces\n        \"\"\"\n        self._error_if_not_finalized(\"boundary_face_outward_normals\")\n        faces_x = self.faces_x\n        faces_y = self.faces_y\n        x0, xF = self._xs[0], self._xs[-1]\n        y0, yF = self._ys[0], self._ys[-1]\n        is_bxm = faces_x[:, 0] == x0\n        is_boundary_x = is_bxm | (faces_x[:, 0] == xF)\n        is_bym = faces_y[:, 1] == y0\n        is_boundary_y = is_bym | (faces_y[:, 1] == yF)\n\n        is_boundary = np.r_[is_boundary_x, is_boundary_y]\n        switch = np.r_[is_bxm, is_bym]\n\n        if self.dim > 2:\n            z0, zF = self._zs[0], self._zs[-1]\n            faces_z = self.faces_z\n            is_bzm = faces_z[:, 2] == z0\n            is_boundary_z = is_bzm | (faces_z[:, 2] == zF)\n\n            is_boundary = np.r_[is_boundary, is_boundary_z]\n            switch = np.r_[switch, is_bzm]\n\n\n        face_normals = self.face_normals.copy()\n        face_normals[switch] *= -1\n        return face_normals[is_boundary]\n\n    @property\n    def cell_volumes(self):\n        \"\"\"Return cell volumes.\n\n        Calling this property will compute and return a 1D array\n        containing the volumes of mesh cells.\n\n        Returns\n        -------\n        (n_cells) numpy.ndarray\n            The quantity returned depends on the dimensions of the mesh:\n\n              - *2D:* Returns the cell areas\n              - *3D:* Returns the cell volumes\n\n        \"\"\"\n        self._error_if_not_finalized(\"cell_volumes\")\n        cdef np.float64_t[:] vol\n        if self._cell_volumes is None:\n            self._cell_volumes = np.empty(self.n_cells, dtype=np.float64)\n            vol = self._cell_volumes\n            for cell in self.tree.cells:\n                vol[cell.index] = cell.volume\n        return self._cell_volumes\n\n    @property\n    def face_areas(self):\n        \"\"\"Returns the areas of all cell faces.\n\n        Calling this property will compute and return the areas of all\n        mesh faces as a 1D numpy array.\n\n        Returns\n        -------\n        (n_faces) numpy.ndarray\n            The length of the quantity returned depends on the dimensions of the mesh:\n\n            - *2D:* returns the x-face and y-face areas; i.e. y-edge and x-edge lengths,\n              respectively\n            - *3D:* returns the x, y and z-face areas in order\n        \"\"\"\n        self._error_if_not_finalized(\"face_areas\")\n        if self._dim == 2 and self._face_areas is None:\n            self._face_areas = np.r_[self.edge_lengths[self.n_edges_x:], self.edge_lengths[:self.n_edges_x]]\n        cdef np.float64_t[:] area\n        cdef int_t ind, offset = 0\n        cdef Face *face\n        if self._face_areas is None:\n            self._face_areas = np.empty(self.n_faces, dtype=np.float64)\n            area = self._face_areas\n\n            for it in self.tree.faces_x:\n                face = it.second\n                if face.hanging: continue\n                area[face.index] = face.area\n\n            offset = self.n_faces_x\n            for it in self.tree.faces_y:\n                face = it.second\n                if face.hanging: continue\n                area[face.index + offset] = face.area\n\n            offset = self.n_faces_x + self.n_faces_y\n            for it in self.tree.faces_z:\n                face = it.second\n                if face.hanging: continue\n                area[face.index + offset] = face.area\n        return self._face_areas\n\n    @property\n    def edge_lengths(self):\n        \"\"\"Returns the lengths of all edges in the mesh.\n\n        Calling this property will compute and return the lengths of all\n        edges in the mesh.\n\n        Returns\n        -------\n        (n_edges) numpy.ndarray\n            The length of the quantity returned depends on the dimensions of the mesh:\n\n            - *2D:* returns the x-edge and y-edge lengths in order\n            - *3D:* returns the x, y and z-edge lengths in order\n        \"\"\"\n        self._error_if_not_finalized(\"edge_lengths\")\n        cdef np.float64_t[:] edge_l\n        cdef Edge *edge\n        cdef int_t ind, offset\n        if self._edge_lengths is None:\n            self._edge_lengths = np.empty(self.n_edges, dtype=np.float64)\n            edge_l = self._edge_lengths\n\n            for it in self.tree.edges_x:\n                edge = it.second\n                if edge.hanging: continue\n                edge_l[edge.index] = edge.length\n\n            offset = self.n_edges_x\n            for it in self.tree.edges_y:\n                edge = it.second\n                if edge.hanging: continue\n                edge_l[edge.index + offset] = edge.length\n\n            if self._dim > 2:\n                offset = self.n_edges_x + self.n_edges_y\n                for it in self.tree.edges_z:\n                    edge = it.second\n                    if edge.hanging: continue\n                    edge_l[edge.index + offset] = edge.length\n        return self._edge_lengths\n\n    @property\n    def cell_boundary_indices(self):\n        \"\"\"Returns the indices of the x, y (and z) boundary cells.\n\n        This property returns the indices of the cells on the x, y (and z)\n        boundaries, respectively. Note that each axis direction will\n        have both a lower and upper boundary. The property will\n        return the indices corresponding to the lower and upper\n        boundaries separately.\n\n        E.g. for a 2D domain, there are 2 x-boundaries and 2 y-boundaries (4 in total).\n        In this case, the return is a list of length 4 organized\n        [ind_Bx1, ind_Bx2, ind_By1, ind_By2]::\n\n                       By2\n                + ------------- +\n                |               |\n                |               |\n            Bx1 |               | Bx2\n                |               |\n                |               |\n                + ------------- +\n                       By1\n\n\n        Returns\n        -------\n        ind_bx1 ind_bx2, ind_by1, ind_by2 : numpy.ndarray of int\n            The length of each array in the list is equal to the number of faces\n            on that particular boundary.\n        ind_bz1, ind_bz2 : numpy.ndarray of int, optional\n            Returned if `dim` is 3.\n\n        Examples\n        --------\n        Here, we construct a small 2D tree mesh and return the indices\n        of the x and y-boundary cells.\n\n        >>> from discretize import TreeMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> hx = np.ones(16)\n        >>> hy = np.ones(16)\n        >>> mesh = TreeMesh([hx, hy])\n        >>> mesh.refine_ball([4.0,4.0], [4.0], [4])\n        >>> ind_Bx1, ind_Bx2, ind_By1, ind_By2 = mesh.cell_boundary_indices\n\n        >>> ax = plt.subplot(111)\n        >>> mesh.plot_grid(ax=ax)\n        >>> ax.scatter(*mesh.cell_centers[ind_Bx1].T)\n        >>> plt.show()\n        \"\"\"\n        cdef np.int64_t[:] indxu, indxd, indyu, indyd, indzu, indzd\n        indxu = np.empty(self.n_cells, dtype=np.int64)\n        indxd = np.empty(self.n_cells, dtype=np.int64)\n        indyu = np.empty(self.n_cells, dtype=np.int64)\n        indyd = np.empty(self.n_cells, dtype=np.int64)\n        if self._dim == 3:\n            indzu = np.empty(self.n_cells, dtype=np.int64)\n            indzd = np.empty(self.n_cells, dtype=np.int64)\n        cdef int_t nxu, nxd, nyu, nyd, nzu, nzd\n        nxu = 0\n        nxd = 0\n        nyu = 0\n        nyd = 0\n        nzu = 0\n        nzd = 0\n        for cell in self.tree.cells:\n            if cell.neighbors[0] == NULL:\n                indxd[nxd] = cell.index\n                nxd += 1\n            if cell.neighbors[1] == NULL:\n                indxu[nxu] = cell.index\n                nxu += 1\n            if cell.neighbors[2] == NULL:\n                indyd[nyd] = cell.index\n                nyd += 1\n            if cell.neighbors[3] == NULL:\n                indyu[nyu] = cell.index\n                nyu += 1\n            if self._dim == 3:\n                if cell.neighbors[4] == NULL:\n                    indzd[nzd] = cell.index\n                    nzd += 1\n                if cell.neighbors[5] == NULL:\n                    indzu[nzu] = cell.index\n                    nzu += 1\n        ixd = np.array(indxd)[:nxd]\n        ixu = np.array(indxu)[:nxu]\n        iyd = np.array(indyd)[:nyd]\n        iyu = np.array(indyu)[:nyu]\n        if self._dim == 3:\n            izd = np.array(indzd)[:nzd]\n            izu = np.array(indzu)[:nzu]\n            return ixd, ixu, iyd, iyu, izd, izu\n        else:\n            return ixd, ixu, iyd, iyu\n\n    @property\n    def face_boundary_indices(self):\n        \"\"\"Returns the indices of the x, y (and z) boundary faces.\n\n        This property returns the indices of the faces on the x, y (and z)\n        boundaries, respectively. Note that each axis direction will\n        have both a lower and upper boundary. The property will\n        return the indices corresponding to the lower and upper\n        boundaries separately.\n\n        E.g. for a 2D domain, there are 2 x-boundaries and 2 y-boundaries (4 in total).\n        In this case, the return is a list of length 4 organized\n        [ind_Bx1, ind_Bx2, ind_By1, ind_By2]::\n\n                       By2\n                + ------------- +\n                |               |\n                |               |\n            Bx1 |               | Bx2\n                |               |\n                |               |\n                + ------------- +\n                       By1\n\n\n        Returns\n        -------\n        ind_bx1 ind_bx2, ind_by1, ind_by2 : numpy.ndarray of int\n            The length of each array in the list is equal to the number of faces\n            on that particular boundary.\n        ind_bz1, ind_bz2 : numpy.ndarray of int, optional\n\n        Examples\n        --------\n        Here, we construct a small 2D tree mesh and return the indices\n        of the x and y-boundary faces.\n\n        >>> from discretize import TreeMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> hx = np.ones(16)\n        >>> hy = np.ones(16)\n        >>> mesh = TreeMesh([hx, hy])\n        >>> mesh.refine_ball([4.0,4.0], [4.0], [4])\n        >>> ind_Bx1, ind_Bx2, ind_By1, ind_By2 = mesh.face_boundary_indices\n\n        >>> ax = plt.subplot(111)\n        >>> mesh.plot_grid(ax=ax)\n        >>> ax.scatter(*mesh.faces_x[ind_Bx1].T)\n        >>> plt.show()\n        \"\"\"\n        cell_boundary_inds = self.cell_boundary_indices\n        cdef np.int64_t[:] c_indxu, c_indxd, c_indyu, c_indyd, c_indzu, c_indzd\n        cdef np.int64_t[:] f_indxu, f_indxd, f_indyu, f_indyd, f_indzu, f_indzd\n        if self._dim == 2:\n            c_indxd, c_indxu, c_indyd, c_indyu = cell_boundary_inds\n        else:\n            c_indxd, c_indxu, c_indyd, c_indyu, c_indzd, c_indzu = cell_boundary_inds\n\n        f_indxd = np.empty(c_indxd.shape[0], dtype=np.int64)\n        f_indxu = np.empty(c_indxu.shape[0], dtype=np.int64)\n        f_indyd = np.empty(c_indyd.shape[0], dtype=np.int64)\n        f_indyu = np.empty(c_indyu.shape[0], dtype=np.int64)\n        if self._dim == 2:\n            for i in range(f_indxd.shape[0]):\n                f_indxd[i] = self.tree.cells[c_indxd[i]].edges[2].index\n            for i in range(f_indxu.shape[0]):\n                f_indxu[i] = self.tree.cells[c_indxu[i]].edges[3].index\n            for i in range(f_indyd.shape[0]):\n                f_indyd[i] = self.tree.cells[c_indyd[i]].edges[0].index\n            for i in range(f_indyu.shape[0]):\n                f_indyu[i] = self.tree.cells[c_indyu[i]].edges[1].index\n        if self._dim == 3:\n            f_indzd = np.empty(c_indzd.shape[0], dtype=np.int64)\n            f_indzu = np.empty(c_indzu.shape[0], dtype=np.int64)\n\n            for i in range(f_indxd.shape[0]):\n                f_indxd[i] = self.tree.cells[c_indxd[i]].faces[0].index\n            for i in range(f_indxu.shape[0]):\n                f_indxu[i] = self.tree.cells[c_indxu[i]].faces[1].index\n            for i in range(f_indyd.shape[0]):\n                f_indyd[i] = self.tree.cells[c_indyd[i]].faces[2].index\n            for i in range(f_indyu.shape[0]):\n                f_indyu[i] = self.tree.cells[c_indyu[i]].faces[3].index\n            for i in range(f_indzd.shape[0]):\n                f_indzd[i] = self.tree.cells[c_indzd[i]].faces[4].index\n            for i in range(f_indzu.shape[0]):\n                f_indzu[i] = self.tree.cells[c_indzu[i]].faces[5].index\n        ixd = np.array(f_indxd)\n        ixu = np.array(f_indxu)\n        iyd = np.array(f_indyd)\n        iyu = np.array(f_indyu)\n        if self._dim == 3:\n            izd = np.array(f_indzd)\n            izu = np.array(f_indzu)\n            return ixd, ixu, iyd, iyu, izd, izu\n        else:\n            return ixd, ixu, iyd, iyu\n\n    def get_boundary_cells(self, active_ind=None, direction='zu'):\n        \"\"\"Return the indices of boundary cells in a given direction given an active index array.\n\n        Parameters\n        ----------\n        active_ind : array_like of bool, optional\n            If not None, then this must show which cells are active\n        direction: {'zu', 'zd', 'xu', 'xd', 'yu', 'yd'}\n            The requested direction to return\n\n        Returns\n        -------\n        numpy.ndarray of int\n            Array of indices for the boundary cells in the requested direction\n        \"\"\"\n\n        direction = direction.lower()\n        if direction[0] == 'z' and self._dim == 2:\n            dir_str = 'y'+direction[1]\n        else:\n            dir_str = direction\n        cdef int_t dir_ind = {'xd':0, 'xu':1, 'yd':2, 'yu':3, 'zd':4, 'zu':5}[dir_str]\n        if active_ind is None:\n            return self.cell_boundary_indices[dir_ind]\n\n        active_ind = np.require(active_ind, dtype=np.int8, requirements='C')\n        cdef np.int8_t[:] act = active_ind\n        cdef np.int8_t[:] is_on_boundary = np.zeros(self.n_cells, dtype=np.int8)\n\n        cdef c_Cell *cell\n        cdef c_Cell *neighbor\n\n        for cell in self.tree.cells:\n            if not act[cell.index]:\n                continue\n            is_bound = 0\n            neighbor = cell.neighbors[dir_ind]\n            if neighbor is NULL:\n                is_bound = 1\n            elif neighbor.is_leaf():\n                is_bound = not act[neighbor.index]\n            else:\n                if dir_ind == 1 or dir_ind == 3 or dir_ind == 5:\n                    is_bound = is_bound or (not act[neighbor.children[0].index])\n                if dir_ind == 0 or dir_ind == 3 or dir_ind == 5:\n                    is_bound = is_bound or (not act[neighbor.children[1].index])\n                if dir_ind == 1 or dir_ind == 2 or dir_ind == 5:\n                    is_bound = is_bound or (not act[neighbor.children[2].index])\n                if dir_ind == 0 or dir_ind == 2 or dir_ind == 5:\n                    is_bound = is_bound or (not act[neighbor.children[3].index])\n\n                if self._dim == 3:\n                    if dir_ind == 1 or dir_ind == 3 or dir_ind == 4:\n                        is_bound = is_bound or (not act[neighbor.children[4].index])\n                    if dir_ind == 0 or dir_ind == 3 or dir_ind == 4:\n                        is_bound = is_bound or (not act[neighbor.children[5].index])\n                    if dir_ind == 1 or dir_ind == 2 or dir_ind == 4:\n                        is_bound = is_bound or (not act[neighbor.children[6].index])\n                    if dir_ind == 0 or dir_ind == 2 or dir_ind == 4:\n                        is_bound = is_bound or (not act[neighbor.children[7].index])\n\n            is_on_boundary[cell.index] = is_bound\n\n        return np.where(is_on_boundary)\n\n    @cython.cdivision(True)\n    def get_cells_along_line(self, x0, x1):\n        \"\"\"Find the cells in order along a line segment.\n\n        Parameters\n        ----------\n        x0,x1 : (dim) array_like\n            Begining and ending point of the line segment.\n\n        Returns\n        -------\n        list of int\n            Indices for cells that contain the a line defined by the two input\n            points, ordered in the direction of the line.\n        \"\"\"\n        cdef np.float64_t ax, ay, az, bx, by, bz\n\n        cdef int dim = self.dim\n        ax = x0[0]\n        ay = x0[1]\n        az = x0[2] if dim==3 else 0\n\n        bx = x1[0]\n        by = x1[1]\n        bz = x1[2] if dim==3 else 0\n\n        cdef vector[long long int] cell_indexes;\n\n        #find initial cell\n        cdef c_Cell *cur_cell = self.tree.containing_cell(ax, ay, az)\n        cell_indexes.push_back(cur_cell.index)\n        #find last cell\n        cdef c_Cell *last_cell = self.tree.containing_cell(bx, by, bz)\n        cdef c_Cell *next_cell\n        cdef int ix, iy, iz\n        cdef double tx, ty, tz, ipx, ipy, ipz\n\n        if dim==3:\n            last_point = 7\n        else:\n            last_point = 3\n\n        cdef int iter = 0\n\n        while cur_cell.index != last_cell.index:\n            #find which direction to look:\n            p0 = cur_cell.points[0].location\n            pF = cur_cell.points[last_point].location\n\n            if ax>bx:\n                tx = (p0[0]-ax)/(bx-ax)\n            elif ax<bx:\n                tx = (pF[0]-ax)/(bx-ax)\n            else:\n                tx = INFINITY\n\n            if ay>by:\n                ty = (p0[1]-ay)/(by-ay)\n            elif ay<by:\n                ty = (pF[1]-ay)/(by-ay)\n            else:\n                ty = INFINITY\n\n            if az>bz:\n                tz = (p0[2]-az)/(bz-az)\n            elif az<bz:\n                tz = (pF[2]-az)/(bz-az)\n            else:\n                tz = INFINITY\n\n            t = min(tx,ty,tz)\n            if t >= 1:\n                # then the segment ended in the current cell.\n                # do not bother checking anymore.\n                break\n\n            #intersection point\n            ipx = (bx-ax)*t+ax\n            ipy = (by-ay)*t+ay\n            ipz = (bz-az)*t+az\n\n            next_cell = cur_cell\n            if t == tx:\n                # step in x direction\n                if ax>bx: # go -x\n                    next_cell = next_cell.neighbors[0]\n                else: # go +x\n                    next_cell = next_cell.neighbors[1]\n            if next_cell is NULL:\n                break\n            if t == ty:\n                # step in y direction\n                if ay>by: # go -y\n                    next_cell = next_cell.neighbors[2]\n                else: # go +y\n                    next_cell = next_cell.neighbors[3]\n            if next_cell is NULL:\n                break\n            if dim==3 and t == tz:\n                # step in z direction\n                if az>bz: # go -z\n                    next_cell = next_cell.neighbors[4]\n                else: # go +z\n                    next_cell = next_cell.neighbors[5]\n            if next_cell is NULL:\n                break\n\n            # check if next_cell is not a leaf\n            # (if so need to traverse down the children and find the closest leaf cell)\n            while not next_cell.is_leaf():\n                # should be able to use cp to check which cell to go to\n                cp = next_cell.children[0].points[last_point].location\n                # this basically finds the child cell closest to the intersection point\n                ix = ipx>cp[0] or (ipx==cp[0] and ax<bx)\n                iy = ipy>cp[1] or (ipy==cp[1] and ay<by)\n                iz = dim==3 and (ipz>cp[2] or  (ipz==cp[2] and az<bz))\n                next_cell = next_cell.children[ix + 2*iy + 4*iz]\n\n            #this now should have stepped appropriately across diagonals and such\n\n            cur_cell = next_cell\n            cell_indexes.push_back(cur_cell.index)\n            if cur_cell.index == -1:\n                raise Exception('Path not found')\n        return cell_indexes\n\n    @property\n    def face_divergence(self):\n        r\"\"\"Face divergence operator (faces to cell-centres).\n\n        This property constructs the 2nd order numerical divergence operator\n        that maps from faces to cell centers. The operator is a sparse matrix\n        :math:`\\mathbf{D_f}` that can be applied as a matrix-vector product to\n        a discrete vector :math:`\\mathbf{u}` that lives on mesh faces; i.e.::\n\n            div_u = Df @ u\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n        *See notes for additional details.*\n\n        Returns\n        -------\n        (n_cells, n_faces) scipy.sparse.csr_matrix\n            The numerical divergence operator from faces to cell centers\n\n        Notes\n        -----\n        In continuous space, the divergence operator is defined as:\n\n        .. math::\n            \\phi = \\nabla \\cdot \\vec{u} = \\frac{\\partial u_x}{\\partial x}\n            + \\frac{\\partial u_y}{\\partial y} + \\frac{\\partial u_z}{\\partial z}\n\n        Where :math:`\\mathbf{u}` is the discrete representation of the continuous variable\n        :math:`\\vec{u}` on cell faces and :math:`\\boldsymbol{\\phi}` is the discrete\n        representation of :math:`\\phi` at cell centers, **face_divergence** constructs a\n        discrete linear operator :math:`\\mathbf{D_f}` such that:\n\n        .. math::\n            \\boldsymbol{\\phi} = \\mathbf{D_f \\, u}\n\n        For each cell, the computation of the face divergence can be expressed\n        according to the integral form below. For cell :math:`i` whose corresponding\n        faces are indexed as a subset :math:`K` from the set of all mesh faces:\n\n        .. math::\n            \\phi_i = \\frac{1}{V_i} \\sum_{k \\in K} A_k \\, \\vec{u}_k \\cdot \\hat{n}_k\n\n        where :math:`V_i` is the volume of cell :math:`i`, :math:`A_k` is\n        the surface area of face *k*, :math:`\\vec{u}_k` is the value of\n        :math:`\\vec{u}` on face *k*, and :math:`\\hat{n}_k`\n        represents the outward normal vector of face *k* for cell *i*.\n        \"\"\"\n        if self._face_divergence is not None:\n            return self._face_divergence\n        if self._dim == 2:\n            D = self._face_divergence_2D() # Because it uses edges instead of faces\n        else:\n            D = self._face_divergence_3D()\n        R = self._deflate_faces()\n        self._face_divergence = D*R\n        return self._face_divergence\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    def _face_divergence_2D(self):\n        cdef np.int64_t[:] I = np.empty(self.n_cells*4, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(self.n_cells*4, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(self.n_cells*4, dtype=np.float64)\n\n        cdef np.int64_t i = 0\n        cdef Edge *edges[4]\n        cdef np.int64_t offset = self.tree.edges_y.size()\n        cdef double volume\n\n        for cell in self.tree.cells:\n            edges = cell.edges\n            i = cell.index\n            I[i*4 : i*4 + 4] = i\n            J[i*4    ] = edges[0].index + offset #x edge, y face (add offset)\n            J[i*4 + 1] = edges[1].index + offset #x edge, y face (add offset)\n            J[i*4 + 2] = edges[2].index #y edge, x face\n            J[i*4 + 3] = edges[3].index #y edge, x face\n\n            volume = cell.volume\n            V[i*4    ] = -edges[0].length/volume\n            V[i*4 + 1] =  edges[1].length/volume\n            V[i*4 + 2] = -edges[2].length/volume\n            V[i*4 + 3] =  edges[3].length/volume\n        return sp.csr_matrix((V, (I, J)))\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    def _face_divergence_3D(self):\n        cdef:\n            np.int64_t[:] I = np.empty(self.n_cells*6, dtype=np.int64)\n            np.int64_t[:] J = np.empty(self.n_cells*6, dtype=np.int64)\n            np.float64_t[:] V = np.empty(self.n_cells*6, dtype=np.float64)\n\n            np.int64_t i = 0\n            Face *faces[6]\n            np.int64_t offset1 = self.tree.faces_x.size()\n            np.int64_t offset2 = offset1 + self.tree.faces_y.size()\n            double volume, fx_area, fy_area, fz_area\n\n        for cell in self.tree.cells:\n            faces = cell.faces\n            i = cell.index\n            I[i*6 : i*6 + 6] = i\n            J[i*6    ] = faces[0].index #x1 face\n            J[i*6 + 1] = faces[1].index #x2 face\n            J[i*6 + 2] = faces[2].index + offset1 #y face (add offset1)\n            J[i*6 + 3] = faces[3].index + offset1 #y face (add offset1)\n            J[i*6 + 4] = faces[4].index + offset2 #z face (add offset2)\n            J[i*6 + 5] = faces[5].index + offset2 #z face (add offset2)\n\n            volume = cell.volume\n            fx_area = faces[0].area\n            fy_area = faces[2].area\n            fz_area = faces[4].area\n            V[i*6    ] = -fx_area/volume\n            V[i*6 + 1] =  fx_area/volume\n            V[i*6 + 2] = -fy_area/volume\n            V[i*6 + 3] =  fy_area/volume\n            V[i*6 + 4] = -fz_area/volume\n            V[i*6 + 5] =  fz_area/volume\n        return sp.csr_matrix((V, (I, J)))\n\n    @property\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    def edge_curl(self):\n        r\"\"\"Edge curl operator (edges to faces)\n\n        This property constructs the 2nd order numerical curl operator\n        that maps from edges to faces. The operator is a sparse matrix\n        :math:`\\mathbf{C_e}` that can be applied as a matrix-vector product\n        to a discrete vector quantity **u** that lives\n        on the edges; i.e.::\n\n            curl_u = Ce @ u\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n\n        Returns\n        -------\n        (n_faces, n_edges) scipy.sparse.csr_matrix\n            The numerical curl operator from edges to faces\n\n        Notes\n        -----\n        In continuous space, the curl operator is defined as:\n\n        .. math::\n            \\vec{w} = \\nabla \\times \\vec{u} =\n            \\begin{vmatrix}\n            \\hat{x} & \\hat{y} & \\hat{z} \\\\\n            \\partial_x & \\partial_y & \\partial_z \\\\\n            u_x & u_y & u_z\n            \\end{vmatrix}\n\n        Where :math:`\\mathbf{u}` is the discrete representation of the continuous variable\n        :math:`\\vec{u}` on cell edges and :math:`\\mathbf{w}` is the discrete\n        representation of the curl on the faces, **edge_curl** constructs a\n        discrete linear operator :math:`\\mathbf{C_e}` such that:\n\n        .. math::\n            \\mathbf{w} = \\mathbf{C_e \\, u}\n\n        The computation of the curl on mesh faces can be expressed\n        according to the integral form below. For face :math:`i` bordered by\n        a set of edges indexed by subset :math:`K`:\n\n        .. math::\n            w_i = \\frac{1}{A_i} \\sum_{k \\in K} \\vec{u}_k \\cdot \\vec{\\ell}_k\n\n        where :math:`A_i` is the surface area of face *i*,\n        :math:`u_k` is the value of :math:`\\vec{u}` on face *k*,\n        and \\vec{\\ell}_k is the path along edge *k*.\n        \"\"\"\n        if self._edge_curl is not None:\n            return self._edge_curl\n        cdef:\n            int_t dim = self._dim\n            int_t n_faces = self.n_faces if dim==3 else self.n_cells\n            np.int64_t[:] I = np.empty(4*n_faces, dtype=np.int64)\n            np.int64_t[:] J = np.empty(4*n_faces, dtype=np.int64)\n            np.float64_t[:] V = np.empty(4*n_faces, dtype=np.float64)\n            Face *face\n            int_t ii\n            int_t face_offset_y = self.n_faces_x\n            int_t face_offset_z = self.n_faces_x + self.n_faces_y if dim==3 else 0\n            int_t edge_offset_y = self.n_total_edges_x\n            int_t edge_offset_z = self.n_total_edges_x + self.n_total_edges_y\n            double area\n\n        if dim == 3:\n            for it in self.tree.faces_x:\n                face = it.second\n                if face.hanging:\n                    continue\n                ii = face.index\n                I[4*ii : 4*ii + 4] = ii\n                J[4*ii    ] = face.edges[0].index + edge_offset_z\n                J[4*ii + 1] = face.edges[1].index + edge_offset_y\n                J[4*ii + 2] = face.edges[2].index + edge_offset_z\n                J[4*ii + 3] = face.edges[3].index + edge_offset_y\n\n                area = face.area\n                V[4*ii    ] = -face.edges[0].length/area\n                V[4*ii + 1] = -face.edges[1].length/area\n                V[4*ii + 2] =  face.edges[2].length/area\n                V[4*ii + 3] =  face.edges[3].length/area\n\n            for it in self.tree.faces_y:\n                face = it.second\n                if face.hanging:\n                    continue\n                ii = face.index + face_offset_y\n                I[4*ii : 4*ii + 4] = ii\n                J[4*ii    ] = face.edges[0].index + edge_offset_z\n                J[4*ii + 1] = face.edges[1].index\n                J[4*ii + 2] = face.edges[2].index + edge_offset_z\n                J[4*ii + 3] = face.edges[3].index\n\n                area = face.area\n                V[4*ii    ] =  face.edges[0].length/area\n                V[4*ii + 1] =  face.edges[1].length/area\n                V[4*ii + 2] = -face.edges[2].length/area\n                V[4*ii + 3] = -face.edges[3].length/area\n\n        for it in self.tree.faces_z:\n            face = it.second\n            if face.hanging:\n                continue\n            ii = face.index + face_offset_z\n            I[4*ii : 4*ii + 4] = ii\n            J[4*ii    ] = face.edges[0].index + edge_offset_y\n            J[4*ii + 1] = face.edges[1].index\n            J[4*ii + 2] = face.edges[2].index + edge_offset_y\n            J[4*ii + 3] = face.edges[3].index\n\n            area = face.area\n            V[4*ii    ] = -face.edges[0].length/area\n            V[4*ii + 1] = -face.edges[1].length/area\n            V[4*ii + 2] =  face.edges[2].length/area\n            V[4*ii + 3] =  face.edges[3].length/area\n\n        C = sp.csr_matrix((V, (I, J)),shape=(n_faces, self.n_total_edges))\n        R = self._deflate_edges()\n        self._edge_curl = C*R\n        return self._edge_curl\n\n    @property\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    def nodal_gradient(self):\n        r\"\"\"Nodal gradient operator (nodes to edges)\n\n        This property constructs the 2nd order numerical gradient operator\n        that maps from nodes to edges. The operator is a sparse matrix\n        :math:`\\mathbf{G_n}` that can be applied as a matrix-vector product\n        to a discrete scalar quantity :math:`\\boldsymbol{\\phi}` that\n        lives on the nodes, i.e.::\n\n            grad_phi = Gn @ phi\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n\n        Returns\n        -------\n        (n_edges, n_nodes) scipy.sparse.csr_matrix\n            The numerical gradient operator from nodes to edges\n\n        Notes\n        -----\n        In continuous space, the gradient operator is defined as:\n\n        .. math::\n            \\vec{u} = \\nabla \\phi = \\frac{\\partial \\phi}{\\partial x}\\hat{x}\n            + \\frac{\\partial \\phi}{\\partial y}\\hat{y}\n            + \\frac{\\partial \\phi}{\\partial z}\\hat{z}\n\n        Where :math:`\\boldsymbol{\\phi}` is the discrete representation of the continuous variable\n        :math:`\\phi` on the nodes and :math:`\\mathbf{u}` is the discrete\n        representation of :math:`\\vec{u}` on the edges, **nodal_gradient** constructs a\n        discrete linear operator :math:`\\mathbf{G_n}` such that:\n\n        .. math::\n            \\mathbf{u} = \\mathbf{G_n} \\, \\boldsymbol{\\phi}\n\n        The Cartesian components of :math:`\\vec{u}` are defined on their corresponding\n        edges (x, y or z) as follows; e.g. the x-component of the gradient is defined\n        on x-edges. For edge :math:`i` which defines a straight path\n        of length :math:`h_i` between adjacent nodes :math:`n_1` and :math:`n_2`:\n\n        .. math::\n            u_i = \\frac{\\phi_{n_2} - \\phi_{n_1}}{h_i}\n\n        Note that :math:`u_i \\in \\mathbf{u}` may correspond to a value on an\n        x, y or z edge. See the example below.\n        \"\"\"\n        if self._nodal_gradient is not None:\n            return self._nodal_gradient\n        cdef:\n            int_t dim = self._dim\n            np.int64_t[:] I = np.empty(2*self.n_edges, dtype=np.int64)\n            np.int64_t[:] J = np.empty(2*self.n_edges, dtype=np.int64)\n            np.float64_t[:] V = np.empty(2*self.n_edges, dtype=np.float64)\n            Edge *edge\n            double length\n            int_t ii\n            np.int64_t offset1 = self.n_edges_x\n            np.int64_t offset2 = offset1 + self.n_edges_y\n\n        for it in self.tree.edges_x:\n            edge = it.second\n            if edge.hanging: continue\n            ii = edge.index\n            I[ii*2 : ii*2 + 2] = ii\n            J[ii*2    ] = edge.points[0].index\n            J[ii*2 + 1] = edge.points[1].index\n\n            length = edge.length\n            V[ii*2    ] = -1.0/length\n            V[ii*2 + 1] =  1.0/length\n\n        for it in self.tree.edges_y:\n            edge = it.second\n            if edge.hanging: continue\n            ii = edge.index + offset1\n            I[ii*2 : ii*2 + 2] = ii\n            J[ii*2    ] = edge.points[0].index\n            J[ii*2 + 1] = edge.points[1].index\n\n            length = edge.length\n            V[ii*2    ] = -1.0/length\n            V[ii*2 + 1] =  1.0/length\n\n        if(dim>2):\n            for it in self.tree.edges_z:\n                edge = it.second\n                if edge.hanging: continue\n                ii = edge.index + offset2\n                I[ii*2 : ii*2 + 2] = ii\n                J[ii*2    ] = edge.points[0].index\n                J[ii*2 + 1] = edge.points[1].index\n\n                length = edge.length\n                V[ii*2    ] = -1.0/length\n                V[ii*2 + 1] =  1.0/length\n\n\n        Rn = self._deflate_nodes()\n        G = sp.csr_matrix((V, (I, J)), shape=(self.n_edges, self.n_total_nodes))\n        self._nodal_gradient = G*Rn\n        return self._nodal_gradient\n\n    @property\n    def nodal_laplacian(self):\n        \"\"\"Not implemented on the TreeMesh.\"\"\"\n        raise NotImplementedError('Nodal Laplacian has not been implemented for TreeMesh')\n\n    @cython.boundscheck(False)\n    def average_cell_to_total_face_x(self):\n        \"\"\"Average matrix for cell center to total (including hanging) x faces.\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_total_faces_x, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from faces to cell centers\n        \"\"\"\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_x, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_x, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_x, dtype=np.float64)\n        cdef int dim = self._dim\n        cdef int_t ind\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[1]\n            if next_cell == NULL:\n                continue\n            if dim == 2:\n                if next_cell.is_leaf():\n                    ind = cell.edges[3].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = 0.5\n                    V[2*ind + 1] = 0.5\n                else:\n                    for i in range(2): # two neighbors in +x direction\n                        ind = next_cell.children[2*i].edges[2].index\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[2*i].index\n                        V[2*ind    ] = 0.5\n                        V[2*ind + 1] = 0.5\n            else:\n                if cell.neighbors[1].is_leaf():\n                    ind = cell.faces[1].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = 0.5\n                    V[2*ind + 1] = 0.5\n                else:\n                    for i in range(4): # four neighbors in +x direction\n                        ind = next_cell.children[2*i].faces[0].index\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[2*i].index\n                        V[2*ind    ] = 0.5\n                        V[2*ind + 1] = 0.5\n\n        return sp.csr_matrix((V, (I,J)), shape=(self.n_total_faces_x, self.n_cells))\n\n    @cython.boundscheck(False)\n    def average_cell_to_total_face_y(self):\n        \"\"\"Average matrix for cell center to total (including hanging) y faces.\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_total_faces_y, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from faces to cell centers\n        \"\"\"\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_y, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_y, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_y, dtype=np.float64)\n        cdef int dim = self._dim\n        cdef int_t ind\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[3]\n            if next_cell==NULL:\n                continue\n            if dim==2:\n                if next_cell.is_leaf():\n                    ind = cell.edges[1].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = 0.5\n                    V[2*ind + 1] = 0.5\n                else:\n                    for i in range(2): # two neighbors in +y direction\n                        ind = next_cell.children[i].edges[0].index\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[i].index\n                        V[2*ind    ] = 0.5\n                        V[2*ind + 1] = 0.5\n            else:\n                if next_cell.is_leaf():\n                    ind = cell.faces[3].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = 0.5\n                    V[2*ind + 1] = 0.5\n                else:\n                    for i in range(4): # four neighbors in +y direction\n                        ind = next_cell.children[(i>>1)*4 + i%2].faces[2].index\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[(i>>1)*4 + i%2].index\n                        V[2*ind    ] = 0.5\n                        V[2*ind + 1] = 0.5\n        return sp.csr_matrix((V, (I,J)), shape=(self.n_total_faces_y, self.n_cells))\n\n    @cython.boundscheck(False)\n    def average_cell_to_total_face_z(self):\n        \"\"\"Average matrix for cell center to total (including hanging) z faces.\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_total_faces_z, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from faces to cell centers\n        \"\"\"\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_z, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_z, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_z, dtype=np.float64)\n        cdef int_t ind\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[5]\n            if next_cell==NULL:\n                continue\n            if next_cell.is_leaf():\n                ind = cell.faces[5].index\n                I[2*ind    ] = ind\n                I[2*ind + 1] = ind\n                J[2*ind    ] = cell.index\n                J[2*ind + 1] = next_cell.index\n                V[2*ind    ] = 0.5\n                V[2*ind + 1] = 0.5\n            else:\n                for i in range(4): # four neighbors in +z direction\n                    ind = next_cell.children[i].faces[4].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.children[i].index\n                    V[2*ind    ] = 0.5\n                    V[2*ind + 1] = 0.5\n\n        return sp.csr_matrix((V, (I,J)), shape=(self.n_total_faces_z, self.n_cells))\n\n    @property\n    @cython.boundscheck(False)\n    def stencil_cell_gradient_x(self):\n        r\"\"\"Differencing operator along x-direction to total (including hanging) x faces.\n\n        This property constructs a differencing operator along the x-axis\n        that acts on cell centered quantities; i.e. the stencil for the\n        x-component of the cell gradient. The operator computes the\n        differences between the values at adjacent cell centers along the\n        x-direction, and places the result on the x-faces. The operator is a sparse\n        matrix :math:`\\mathbf{G_x}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            diff_phi_x = Gx @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity.\n\n        Returns\n        -------\n        (n_total_faces_x, n_cells) scipy.sparse.csr_matrix\n            The stencil for the x-component of the cell gradient\n        \"\"\"\n        self._error_if_not_finalized(\"stencil_cell_gradient_x\")\n        if getattr(self, '_stencil_cell_gradient_x', None) is not None:\n            return self._stencil_cell_gradient_x\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_x, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_x, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_x, dtype=np.float64)\n        cdef int dim = self._dim\n        cdef int_t ind\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[1]\n            if next_cell == NULL:\n                continue\n            if dim == 2:\n                if next_cell.is_leaf():\n                    ind = cell.edges[3].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = -1.0\n                    V[2*ind + 1] =  1.0\n                else:\n                    for i in range(2): # two neighbors in +x direction\n                        ind = next_cell.children[2*i].edges[2].index\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[2*i].index\n                        V[2*ind    ] = -1.0\n                        V[2*ind + 1] =  1.0\n            else:\n                if cell.neighbors[1].is_leaf():\n                    ind = cell.faces[1].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = -1.0\n                    V[2*ind + 1] =  1.0\n                else:\n                    for i in range(4): # four neighbors in +x direction\n                        ind = next_cell.children[2*i].faces[0].index #0 2 4 6\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[2*i].index\n                        V[2*ind    ] = -1.0\n                        V[2*ind + 1] =  1.0\n\n        self._stencil_cell_gradient_x = (\n            sp.csr_matrix((V, (I,J)), shape=(self.n_total_faces_x, self.n_cells))\n        )\n        return self._stencil_cell_gradient_x\n\n    @property\n    @cython.boundscheck(False)\n    def stencil_cell_gradient_y(self):\n        r\"\"\"Differencing operator along y-direction to total (including hanging) y faces.\n\n        This property constructs a differencing operator along the y-axis\n        that acts on cell centered quantities; i.e. the stencil for the\n        y-component of the cell gradient. The operator computes the\n        differences between the values at adjacent cell centers along the\n        y-direction, and places the result on the y-faces. The operator is a sparse\n        matrix :math:`\\mathbf{G_y}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            diff_phi_y = Gy @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity.\n\n        Returns\n        -------\n        (n_total_faces_y, n_cells) scipy.sparse.csr_matrix\n            The stencil for the y-component of the cell gradient\n        \"\"\"\n        self._error_if_not_finalized(\"stencil_cell_gradient_y\")\n        if getattr(self, '_stencil_cell_gradient_y', None) is not None:\n            return self._stencil_cell_gradient_y\n\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_y, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_y, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_y, dtype=np.float64)\n        cdef int dim = self._dim\n        cdef int_t ind\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[3]\n            if next_cell == NULL:\n                continue\n            if dim==2:\n                if next_cell.is_leaf():\n                    ind = cell.edges[1].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = -1.0\n                    V[2*ind + 1] =  1.0\n                else:\n                    for i in range(2): # two neighbors in +y direction\n                        ind = next_cell.children[i].edges[0].index\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[i].index\n                        V[2*ind    ] = -1.0\n                        V[2*ind + 1] =  1.0\n            else:\n                if next_cell.is_leaf():\n                    ind = cell.faces[3].index\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.index\n                    V[2*ind    ] = -1.0\n                    V[2*ind + 1] =  1.0\n                else:\n                    for i in range(4): # four neighbors in +y direction\n                        ind = next_cell.children[(i>>1)*4 + i%2].faces[2].index #0, 1, 4, 5\n                        I[2*ind    ] = ind\n                        I[2*ind + 1] = ind\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = next_cell.children[(i>>1)*4 + i%2].index\n                        V[2*ind    ] = -1.0\n                        V[2*ind + 1] = 1.0\n\n        self._stencil_cell_gradient_y = (\n            sp.csr_matrix((V, (I,J)), shape=(self.n_total_faces_y, self.n_cells))\n        )\n        return self._stencil_cell_gradient_y\n\n    @property\n    @cython.boundscheck(False)\n    def stencil_cell_gradient_z(self):\n        r\"\"\"Differencing operator along z-direction to total (including hanging) z faces.\n\n        This property constructs a differencing operator along the z-axis\n        that acts on cell centered quantities; i.e. the stencil for the\n        z-component of the cell gradient. The operator computes the\n        differences between the values at adjacent cell centers along the\n        z-direction, and places the result on the z-faces. The operator is a sparse\n        matrix :math:`\\mathbf{G_z}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            diff_phi_z = Gz @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity.\n\n        Returns\n        -------\n        (n_total_faces_z, n_cells) scipy.sparse.csr_matrix\n            The stencil for the z-component of the cell gradient\n        \"\"\"\n        self._error_if_not_finalized(\"stencil_cell_gradient_z\")\n        if getattr(self, '_stencil_cell_gradient_z', None) is not None:\n            return self._stencil_cell_gradient_z\n\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_z, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_z, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_z, dtype=np.float64)\n        cdef int_t ind\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[5]\n            if next_cell==NULL:\n                continue\n            if next_cell.is_leaf():\n                ind = cell.faces[5].index\n                I[2*ind    ] = ind\n                I[2*ind + 1] = ind\n                J[2*ind    ] = cell.index\n                J[2*ind + 1] = next_cell.index\n                V[2*ind    ] = -1.0\n                V[2*ind + 1] =  1.0\n            else:\n                for i in range(4): # four neighbors in +z direction\n                    ind = next_cell.children[i].faces[4].index #0, 1, 2, 3\n                    I[2*ind    ] = ind\n                    I[2*ind + 1] = ind\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = next_cell.children[i].index\n                    V[2*ind    ] = -1.0\n                    V[2*ind + 1] =  1.0\n\n        self._stencil_cell_gradient_z = (\n            sp.csr_matrix((V, (I,J)), shape=(self.n_total_faces_z, self.n_cells))\n        )\n        return self._stencil_cell_gradient_z\n\n    @cython.boundscheck(False)\n    def _deflate_edges_x(self):\n        #I is output index (with hanging)\n        #J is input index (without hanging)\n        cdef np.int64_t[:] I = np.empty(2*self.n_total_edges_x, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(2*self.n_total_edges_x, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(2*self.n_total_edges_x, dtype=np.float64)\n        cdef Edge *edge\n        cdef np.int64_t ii\n        #x edges:\n        for it in self.tree.edges_x:\n            edge = it.second\n            ii = edge.index\n            I[2*ii    ] = ii\n            I[2*ii + 1] = ii\n            if edge.hanging:\n                J[2*ii    ] = edge.parents[0].index\n                J[2*ii + 1] = edge.parents[1].index\n            else:\n                J[2*ii    ] = ii\n                J[2*ii + 1] = ii\n            V[2*ii    ] = 0.5\n            V[2*ii + 1] = 0.5\n        Rh = sp.csr_matrix((V, (I, J)), shape=(self.n_total_edges_x, self.n_total_edges_x))\n        # Test if it needs to be deflated again, (if any parents were also hanging)\n        last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_edges_x)\n        while(last_ind > self.n_edges_x):\n            Rh = Rh*Rh\n            last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_edges_x)\n        Rh = Rh[:, : last_ind]\n        return Rh\n\n    @cython.boundscheck(False)\n    def _deflate_edges_y(self):\n        #I is output index (with hanging)\n        #J is input index (without hanging)\n        cdef int_t dim = self._dim\n        cdef np.int64_t[:] I = np.empty(2*self.n_total_edges_y, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(2*self.n_total_edges_y, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(2*self.n_total_edges_y, dtype=np.float64)\n        cdef Edge *edge\n        cdef np.int64_t ii\n        #x edges:\n        for it in self.tree.edges_y:\n            edge = it.second\n            ii = edge.index\n            I[2*ii    ] = ii\n            I[2*ii + 1] = ii\n            if edge.hanging:\n                J[2*ii    ] = edge.parents[0].index\n                J[2*ii + 1] = edge.parents[1].index\n            else:\n                J[2*ii    ] = ii\n                J[2*ii + 1] = ii\n            V[2*ii    ] = 0.5\n            V[2*ii + 1] = 0.5\n        Rh = sp.csr_matrix((V, (I, J)), shape=(self.n_total_edges_y, self.n_total_edges_y))\n        # Test if it needs to be deflated again, (if any parents were also hanging)\n        last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_edges_y)\n        while(last_ind > self.n_edges_y):\n            Rh = Rh*Rh\n            last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_edges_y)\n        Rh = Rh[:, : last_ind]\n        return Rh\n\n    @cython.boundscheck(False)\n    def _deflate_edges_z(self):\n        #I is output index (with hanging)\n        #J is input index (without hanging)\n        cdef int_t dim = self._dim\n        cdef np.int64_t[:] I = np.empty(2*self.n_total_edges_z, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(2*self.n_total_edges_z, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(2*self.n_total_edges_z, dtype=np.float64)\n        cdef Edge *edge\n        cdef np.int64_t ii\n        #x edges:\n        for it in self.tree.edges_z:\n            edge = it.second\n            ii = edge.index\n            I[2*ii    ] = ii\n            I[2*ii + 1] = ii\n            if edge.hanging:\n                J[2*ii    ] = edge.parents[0].index\n                J[2*ii + 1] = edge.parents[1].index\n            else:\n                J[2*ii    ] = ii\n                J[2*ii + 1] = ii\n            V[2*ii    ] = 0.5\n            V[2*ii + 1] = 0.5\n        Rh = sp.csr_matrix((V, (I, J)), shape=(self.n_total_edges_z, self.n_total_edges_z))\n        # Test if it needs to be deflated again, (if any parents were also hanging)\n        last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_edges_z)\n        while(last_ind > self.n_edges_z):\n            Rh = Rh*Rh\n            last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_edges_z)\n        Rh = Rh[:, : last_ind]\n        return Rh\n\n    def _deflate_edges(self):\n        \"\"\"Return a matrix to remove hanging edges.\n\n        A hanging edge can either have 1 or 2 parents.\n        If a hanging edge has a single parent, it's value is the same as the parent\n        If a hanging edge has 2 parents, it's an average of the two parents\n        \"\"\"\n        if self._dim == 2:\n            Rx = self._deflate_edges_x()\n            Ry = self._deflate_edges_y()\n            return sp.block_diag((Rx, Ry))\n        else:\n            Rx = self._deflate_edges_x()\n            Ry = self._deflate_edges_y()\n            Rz = self._deflate_edges_z()\n            return sp.block_diag((Rx, Ry, Rz))\n\n    def _deflate_faces(self):\n        \"\"\"Return a matrix that removes hanging faces.\n\n        The operation assigns the hanging face the value of its parent.\n        A hanging face will only ever have 1 parent.\n        \"\"\"\n        if(self._dim == 2):\n            Rx = self._deflate_edges_x()\n            Ry = self._deflate_edges_y()\n            return sp.block_diag((Ry, Rx))\n        else:\n            Rx = self._deflate_faces_x()\n            Ry = self._deflate_faces_y()\n            Rz = self._deflate_faces_z()\n            return sp.block_diag((Rx, Ry, Rz))\n\n    @cython.boundscheck(False)\n    def _deflate_faces_x(self):\n        #I is output index (with hanging)\n        #J is input index (without hanging)\n        cdef np.int64_t[:] I = np.empty(self.n_total_faces_x, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(self.n_total_faces_x, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(self.n_total_faces_x, dtype=np.float64)\n        cdef Face *face\n        cdef np.int64_t ii;\n\n        for it in self.tree.faces_x:\n            face = it.second\n            ii = face.index\n            I[ii] = ii\n            if face.hanging:\n                J[ii] = face.parent.index\n            else:\n                J[ii] = ii\n            V[ii] = 1.0\n        return sp.csr_matrix((V, (I, J)))\n\n    @cython.boundscheck(False)\n    def _deflate_faces_y(self):\n        #I is output index (with hanging)\n        #J is input index (without hanging)\n        cdef np.int64_t[:] I = np.empty(self.n_total_faces_y, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(self.n_total_faces_y, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(self.n_total_faces_y, dtype=np.float64)\n        cdef Face *face\n        cdef np.int64_t ii;\n\n        for it in self.tree.faces_y:\n            face = it.second\n            ii = face.index\n            I[ii] = ii\n            if face.hanging:\n                J[ii] = face.parent.index\n            else:\n                J[ii] = ii\n            V[ii] = 1.0\n        return sp.csr_matrix((V, (I, J)))\n\n    @cython.boundscheck(False)\n    def _deflate_faces_z(self):\n        #I is output index (with hanging)\n        #J is input index (without hanging)\n        cdef np.int64_t[:] I = np.empty(self.n_total_faces_z, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(self.n_total_faces_z, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(self.n_total_faces_z, dtype=np.float64)\n        cdef Face *face\n        cdef np.int64_t ii;\n\n        for it in self.tree.faces_z:\n            face = it.second\n            ii = face.index\n            I[ii] = ii\n            if face.hanging:\n                J[ii] = face.parent.index\n            else:\n                J[ii] = ii\n            V[ii] = 1.0\n        return sp.csr_matrix((V, (I, J)))\n\n    @cython.boundscheck(False)\n    def _deflate_nodes(self):\n        \"\"\"Return a matrix that removes hanging faces.\n\n        A hanging node will have 2 parents in 2D or 2 or 4 parents in 3D.\n        This matrix assigns the hanging node the average value of its parents.\n        \"\"\"\n        cdef np.int64_t[:] I = np.empty(4*self.n_total_nodes, dtype=np.int64)\n        cdef np.int64_t[:] J = np.empty(4*self.n_total_nodes, dtype=np.int64)\n        cdef np.float64_t[:] V = np.empty(4*self.n_total_nodes, dtype=np.float64)\n\n        # I is output index\n        # J is input index\n        cdef Node *node\n        cdef np.int64_t ii, i, offset\n        offset = self.n_nodes\n        cdef double[4] weights\n\n        for it in self.tree.nodes:\n            node = it.second\n            ii = node.index\n            I[4*ii:4*ii + 4] = ii\n            if node.hanging:\n                J[4*ii    ] = node.parents[0].index\n                J[4*ii + 1] = node.parents[1].index\n                J[4*ii + 2] = node.parents[2].index\n                J[4*ii + 3] = node.parents[3].index\n            else:\n                J[4*ii : 4*ii + 4] = ii\n            V[4*ii : 4*ii + 4] = 0.25;\n\n        Rh = sp.csr_matrix((V, (I, J)), shape=(self.n_total_nodes, self.n_total_nodes))\n        # Test if it needs to be deflated again, (if any parents were also hanging)\n        last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_nodes)\n        while(last_ind > self.n_nodes):\n            Rh = Rh*Rh\n            last_ind = max(np.nonzero(Rh.getnnz(0)>0)[0][-1], self.n_nodes)\n        Rh = Rh[:, : last_ind]\n        return Rh\n\n    @property\n    @cython.boundscheck(False)\n    def average_edge_x_to_cell(self):\n        r\"\"\"Averaging operator from x-edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from x-edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on x-edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges_x) scipy.sparse.csr_matrix\n            The scalar averaging operator from x-edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_x}` be a discrete scalar quantity that\n        lives on x-edges. **average_edge_x_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{xc}}` that projects\n        :math:`\\boldsymbol{\\phi_x}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{xc}} \\, \\boldsymbol{\\phi_x}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its x-edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Axc @ phi_x\n        \"\"\"\n        if self._average_edge_x_to_cell is not None:\n            return self._average_edge_x_to_cell\n        cdef np.int64_t[:] I,J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ind, ii, n_epc\n        cdef double scale\n\n        n_epc = 2*(self._dim-1)\n        I = np.empty(self.n_cells*n_epc, dtype=np.int64)\n        J = np.empty(self.n_cells*n_epc, dtype=np.int64)\n        V = np.empty(self.n_cells*n_epc, dtype=np.float64)\n        scale = 1.0/n_epc\n        for cell in self.tree.cells:\n            ind = cell.index\n            for ii in range(n_epc):\n                I[ind*n_epc + ii] = ind\n                J[ind*n_epc + ii] = cell.edges[ii].index\n                V[ind*n_epc + ii] = scale\n\n        Rex = self._deflate_edges_x()\n        self._average_edge_x_to_cell = sp.csr_matrix((V, (I, J)))*Rex\n        return self._average_edge_x_to_cell\n\n    @property\n    @cython.boundscheck(False)\n    def average_edge_y_to_cell(self):\n        r\"\"\"Averaging operator from y-edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from y-edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on y-edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges_y) scipy.sparse.csr_matrix\n            The scalar averaging operator from y-edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_y}` be a discrete scalar quantity that\n        lives on y-edges. **average_edge_y_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{yc}}` that projects\n        :math:`\\boldsymbol{\\phi_y}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{yc}} \\, \\boldsymbol{\\phi_y}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its y-edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Ayc @ phi_y\n        \"\"\"\n        if self._average_edge_y_to_cell is not None:\n            return self._average_edge_y_to_cell\n        cdef np.int64_t[:] I,J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ind, ii, n_epc\n        cdef double scale\n\n        n_epc = 2*(self._dim-1)\n        I = np.empty(self.n_cells*n_epc, dtype=np.int64)\n        J = np.empty(self.n_cells*n_epc, dtype=np.int64)\n        V = np.empty(self.n_cells*n_epc, dtype=np.float64)\n        scale = 1.0/n_epc\n        for cell in self.tree.cells:\n            ind = cell.index\n            for ii in range(n_epc):\n                I[ind*n_epc + ii] = ind\n                J[ind*n_epc + ii] = cell.edges[n_epc + ii].index #y edges\n                V[ind*n_epc + ii] = scale\n\n        Rey = self._deflate_edges_y()\n        self._average_edge_y_to_cell = sp.csr_matrix((V, (I, J)))*Rey\n        return self._average_edge_y_to_cell\n\n    @property\n    @cython.boundscheck(False)\n    def average_edge_z_to_cell(self):\n        r\"\"\"Averaging operator from z-edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from z-edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on z-edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges_z) scipy.sparse.csr_matrix\n            The scalar averaging operator from z-edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_z}` be a discrete scalar quantity that\n        lives on z-edges. **average_edge_z_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{zc}}` that projects\n        :math:`\\boldsymbol{\\phi_z}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{zc}} \\, \\boldsymbol{\\phi_z}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its z-edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Azc @ phi_z\n        \"\"\"\n        if self._average_edge_z_to_cell is not None:\n            return self._average_edge_z_to_cell\n        if self._dim == 2:\n            raise Exception('There are no z-edges in 2D')\n        cdef np.int64_t[:] I,J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ind, ii, n_epc\n        cdef double scale\n\n        n_epc = 2*(self._dim-1)\n        I = np.empty(self.n_cells*n_epc, dtype=np.int64)\n        J = np.empty(self.n_cells*n_epc, dtype=np.int64)\n        V = np.empty(self.n_cells*n_epc, dtype=np.float64)\n        scale = 1.0/n_epc\n        for cell in self.tree.cells:\n            ind = cell.index\n            for ii in range(n_epc):\n                I[ind*n_epc + ii] = ind\n                J[ind*n_epc + ii] = cell.edges[ii + 2*n_epc].index\n                V[ind*n_epc + ii] = scale\n\n        Rez = self._deflate_edges_z()\n        self._average_edge_z_to_cell = sp.csr_matrix((V, (I, J)))*Rez\n        return self._average_edge_z_to_cell\n\n    @property\n    def average_edge_to_cell(self):\n        r\"\"\"Averaging operator from edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on mesh edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges) scipy.sparse.csr_matrix\n            The scalar averaging operator from edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_e}` be a discrete scalar quantity that\n        lives on mesh edges. **average_edge_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{ec}}` that projects\n        :math:`\\boldsymbol{\\phi_e}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{ec}} \\, \\boldsymbol{\\phi_e}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Aec @ phi_e\n        \"\"\"\n        if self._average_edge_to_cell is None:\n            stacks = [self.average_edge_x_to_cell, self.average_edge_y_to_cell]\n            if self._dim == 3:\n                stacks += [self.average_edge_z_to_cell]\n            self._average_edge_to_cell = 1.0/self._dim * sp.hstack(stacks).tocsr()\n        return self._average_edge_to_cell\n\n    @property\n    def average_edge_to_cell_vector(self):\n        r\"\"\"Averaging operator from edges to cell centers (vector quantities).\n\n        This property constructs the averaging operator that independently maps the\n        Cartesian components of vector quantities from edges to cell centers.\n        This averaging operators is used when a discrete vector quantity defined on mesh edges\n        must be approximated at cell centers. Once constructed, the operator is\n        stored permanently as a property of the mesh.\n\n        Be aware that the Cartesian components of the original vector\n        are defined on their respective edges; e.g. the x-component lives\n        on x-edges. However, the x, y and z components are being averaged\n        separately to cell centers. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_c = Aec @ u_e\n\n        Returns\n        -------\n        (dim * n_cells, n_edges) scipy.sparse.csr_matrix\n            The vector averaging operator from edges to cell centers. Since we\n            are averaging a vector quantity to cell centers, the first dimension\n            of the operator is the mesh dimension times the number of cells.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_e}` be the discrete representation of a vector\n        quantity whose Cartesian components are defined on their respective edges;\n        e.g. :math:`u_x` is defined on x-edges.\n        **average_edge_to_cell_vector** constructs a discrete linear operator\n        :math:`\\mathbf{A_{ec}}` that projects each Cartesian component of\n        :math:`\\mathbf{u_e}` independently to cell centers, i.e.:\n\n        .. math::\n            \\mathbf{u_c} = \\mathbf{A_{ec}} \\, \\mathbf{u_e}\n\n        where :math:`\\mathbf{u_c}` is a discrete vector quantity whose Cartesian\n        components defined at the cell centers and organized into a 1D array of\n        the form np.r_[ux, uy, uz]. For each cell, and for each Cartesian component,\n        we are simply taking the average of the values\n        defined on the cell's corresponding edges and placing the result at\n        the cell's center.\n        \"\"\"\n        if self._average_edge_to_cell_vector is None:\n            stacks = [self.average_edge_x_to_cell, self.average_edge_y_to_cell]\n            if self._dim == 3:\n                stacks += [self.average_edge_z_to_cell]\n            self._average_edge_to_cell_vector = sp.block_diag(stacks).tocsr()\n        return self._average_edge_to_cell_vector\n\n    @property\n    def average_edge_to_face(self):\n        r\"\"\"Averaging operator from edges to faces.\n\n        This property constructs the averaging operator that maps uantities from edges to faces.\n        This averaging operators is used when a discrete quantity defined on mesh edges\n        must be approximated at faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_f = Aef @ u_e\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n\n        Returns\n        -------\n        (n_faces, n_edges) scipy.sparse.csr_matrix\n            The averaging operator from edges to faces.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_e}` be the discrete representation of aquantity whose\n        that is defined on the edges. **average_edge_to_face**\n        constructs a discrete linear operator :math:`\\mathbf{A_{ef}}` that\n        projects :math:`\\mathbf{u_e}` to its corresponding face, i.e.:\n\n        .. math::\n            \\mathbf{u_f} = \\mathbf{A_{ef}} \\, \\mathbf{u_e}\n\n        where :math:`\\mathbf{u_f}` is a quantity defined on the respective faces.\n        \"\"\"\n        if self.dim == 2:\n            return sp.diags(\n                [1, 1],\n                [-self.n_faces_x, self.n_faces_y],\n                shape=(self.n_faces, self.n_edges)\n            )\n\n        if self._average_edge_to_face is not None:\n            return self._average_edge_to_face\n        cdef:\n            int_t dim = self._dim\n            np.int64_t[:] I = np.empty(4*self.n_faces, dtype=np.int64)\n            np.int64_t[:] J = np.empty(4*self.n_faces, dtype=np.int64)\n            np.float64_t[:] V = np.full(4*self.n_faces, 0.25, dtype=np.float64)\n            Face *face\n            int_t ii\n            int_t face_offset_y = self.n_faces_x\n            int_t face_offset_z = self.n_faces_x + self.n_faces_y\n            int_t edge_offset_y = self.n_total_edges_x\n            int_t edge_offset_z = self.n_total_edges_x + self.n_total_edges_y\n            double area\n\n        for it in self.tree.faces_x:\n            face = it.second\n            if face.hanging:\n                continue\n            ii = face.index\n            I[4*ii : 4*ii + 4] = ii\n            J[4*ii    ] = face.edges[0].index + edge_offset_z\n            J[4*ii + 1] = face.edges[1].index + edge_offset_y\n            J[4*ii + 2] = face.edges[2].index + edge_offset_z\n            J[4*ii + 3] = face.edges[3].index + edge_offset_y\n\n        for it in self.tree.faces_y:\n            face = it.second\n            if face.hanging:\n                continue\n            ii = face.index + face_offset_y\n            I[4*ii : 4*ii + 4] = ii\n            J[4*ii    ] = face.edges[0].index + edge_offset_z\n            J[4*ii + 1] = face.edges[1].index\n            J[4*ii + 2] = face.edges[2].index + edge_offset_z\n            J[4*ii + 3] = face.edges[3].index\n\n        for it in self.tree.faces_z:\n            face = it.second\n            if face.hanging:\n                continue\n            ii = face.index + face_offset_z\n            I[4*ii : 4*ii + 4] = ii\n            J[4*ii    ] = face.edges[0].index + edge_offset_y\n            J[4*ii + 1] = face.edges[1].index\n            J[4*ii + 2] = face.edges[2].index + edge_offset_y\n            J[4*ii + 3] = face.edges[3].index\n\n        Av = sp.csr_matrix((V, (I, J)),shape=(self.n_faces, self.n_total_edges))\n        R = self._deflate_edges()\n\n        self._average_edge_to_face = Av @ R\n        return self._average_edge_to_face\n\n    @property\n    @cython.boundscheck(False)\n    def average_face_x_to_cell(self):\n        r\"\"\"Averaging operator from x-faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from x-faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on x-faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh.\n\n        Returns\n        -------\n        (n_cells, n_faces_x) scipy.sparse.csr_matrix\n            The scalar averaging operator from x-faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_x}` be a discrete scalar quantity that\n        lives on x-faces. **average_face_x_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{xc}}` that projects\n        :math:`\\boldsymbol{\\phi_x}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{xc}} \\, \\boldsymbol{\\phi_x}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its x-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Axc @ phi_x\n        \"\"\"\n        if self._average_face_x_to_cell is not None:\n            return self._average_face_x_to_cell\n        if self._dim == 2:\n            return self.average_edge_y_to_cell\n\n        cdef np.int64_t[:] I,J\n        cdef np.float64_t[:] V\n        cdef Face *face1\n        cdef Face *face2\n        cdef np.int64_t ii\n        I = np.empty(self.n_cells*2, dtype=np.int64)\n        J = np.empty(self.n_cells*2, dtype=np.int64)\n        V = np.empty(self.n_cells*2, dtype=np.float64)\n\n        for cell in self.tree.cells:\n            face1 = cell.faces[0] # x face\n            face2 = cell.faces[1] # x face\n            ii = cell.index\n            I[ii*2 : ii*2 + 2] = ii\n            J[ii*2    ] = face1.index\n            J[ii*2 + 1] = face2.index\n            V[ii*2 : ii*2 + 2] = 0.5\n\n        Rfx = self._deflate_faces_x()\n        self._average_face_x_to_cell = sp.csr_matrix((V, (I, J)))*Rfx\n        return self._average_face_x_to_cell\n\n    @property\n    @cython.boundscheck(False)\n    def average_face_y_to_cell(self):\n        r\"\"\"Averaging operator from y-faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from y-faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on x-faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_faces_y) scipy.sparse.csr_matrix\n            The scalar averaging operator from y-faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_y}` be a discrete scalar quantity that\n        lives on y-faces. **average_face_y_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{yc}}` that projects\n        :math:`\\boldsymbol{\\phi_y}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{yc}} \\, \\boldsymbol{\\phi_y}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its y-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Ayc @ phi_y\n        \"\"\"\n        if self._average_face_y_to_cell is not None:\n            return self._average_face_y_to_cell\n        if self._dim == 2:\n            return self.average_edge_x_to_cell\n\n        cdef np.int64_t[:] I,J\n        cdef np.float64_t[:] V\n        cdef Face *face1\n        cdef Face *face2\n        cdef np.int64_t ii\n        I = np.empty(self.n_cells*2, dtype=np.int64)\n        J = np.empty(self.n_cells*2, dtype=np.int64)\n        V = np.empty(self.n_cells*2, dtype=np.float64)\n\n        for cell in self.tree.cells:\n            face1 = cell.faces[2] # y face\n            face2 = cell.faces[3] # y face\n            ii = cell.index\n            I[ii*2 : ii*2 + 2] = ii\n            J[ii*2    ] = face1.index\n            J[ii*2 + 1] = face2.index\n            V[ii*2 : ii*2 + 2] = 0.5\n\n        Rfy = self._deflate_faces_y()\n        self._average_face_y_to_cell = sp.csr_matrix((V, (I, J)))*Rfy\n        return self._average_face_y_to_cell\n\n    @property\n    @cython.boundscheck(False)\n    def average_face_z_to_cell(self):\n        r\"\"\"Averaging operator from z-faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from z-faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on z-faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_faces_z) scipy.sparse.csr_matrix\n            The scalar averaging operator from z-faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_z}` be a discrete scalar quantity that\n        lives on z-faces. **average_face_z_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{zc}}` that projects\n        :math:`\\boldsymbol{\\phi_z}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{zc}} \\, \\boldsymbol{\\phi_z}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its z-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Azc @ phi_z\n        \"\"\"\n        if self._average_face_z_to_cell is not None:\n            return self._average_face_z_to_cell\n        if self._dim == 2:\n            raise Exception('There are no z-faces in 2D')\n        cdef np.int64_t[:] I,J\n        cdef np.float64_t[:] V\n        cdef Face *face1\n        cdef Face *face2\n        cdef np.int64_t ii\n        I = np.empty(self.n_cells*2, dtype=np.int64)\n        J = np.empty(self.n_cells*2, dtype=np.int64)\n        V = np.empty(self.n_cells*2, dtype=np.float64)\n\n        for cell in self.tree.cells:\n            face1 = cell.faces[4]\n            face2 = cell.faces[5]\n            ii = cell.index\n            I[ii*2 : ii*2 + 2] = ii\n            J[ii*2    ] = face1.index\n            J[ii*2 + 1] = face2.index\n            V[ii*2 : ii*2 + 2] = 0.5\n\n        Rfy = self._deflate_faces_z()\n        self._average_face_z_to_cell = sp.csr_matrix((V, (I, J)))*Rfy\n        return self._average_face_z_to_cell\n\n    @property\n    def average_face_to_cell(self):\n        r\"\"\"Averaging operator from faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on mesh faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_faces) scipy.sparse.csr_matrix\n            The scalar averaging operator from faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_f}` be a discrete scalar quantity that\n        lives on mesh faces. **average_face_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{fc}}` that projects\n        :math:`\\boldsymbol{\\phi_f}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{fc}} \\, \\boldsymbol{\\phi_f}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Afc @ phi_f\n        \"\"\"\n        if self._average_face_to_cell is None:\n            stacks = [self.average_face_x_to_cell, self.aveFy2CC]\n            if self._dim == 3:\n                stacks += [self.average_face_z_to_cell]\n            self._average_face_to_cell = 1./self._dim*sp.hstack(stacks).tocsr()\n        return self._average_face_to_cell\n\n    @property\n    def average_face_to_cell_vector(self):\n        r\"\"\"Averaging operator from faces to cell centers (vector quantities).\n\n        This property constructs the averaging operator that independently maps the\n        Cartesian components of vector quantities from faces to cell centers.\n        This averaging operators is used when a discrete vector quantity defined on mesh faces\n        must be approximated at cell centers. Once constructed, the operator is\n        stored permanently as a property of the mesh.\n\n        Be aware that the Cartesian components of the original vector\n        are defined on their respective faces; e.g. the x-component lives\n        on x-faces. However, the x, y and z components are being averaged\n        separately to cell centers. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_c = Afc @ u_f\n\n        Returns\n        -------\n        (dim * n_cells, n_faces) scipy.sparse.csr_matrix\n            The vector averaging operator from faces to cell centers. Since we\n            are averaging a vector quantity to cell centers, the first dimension\n            of the operator is the mesh dimension times the number of cells.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_f}` be the discrete representation of a vector\n        quantity whose Cartesian components are defined on their respective faces;\n        e.g. :math:`u_x` is defined on x-faces.\n        **average_face_to_cell_vector** constructs a discrete linear operator\n        :math:`\\mathbf{A_{fc}}` that projects each Cartesian component of\n        :math:`\\mathbf{u_f}` independently to cell centers, i.e.:\n\n        .. math::\n            \\mathbf{u_c} = \\mathbf{A_{fc}} \\, \\mathbf{u_f}\n\n        where :math:`\\mathbf{u_c}` is a discrete vector quantity whose Cartesian\n        components defined at the cell centers and organized into a 1D array of\n        the form np.r_[ux, uy, uz]. For each cell, and for each Cartesian component,\n        we are simply taking the average of the values\n        defined on the cell's corresponding faces and placing the result at\n        the cell's center.\n        \"\"\"\n        if self._average_face_to_cell_vector is None:\n            stacks = [self.average_face_x_to_cell, self.aveFy2CC]\n            if self._dim == 3:\n                stacks += [self.average_face_z_to_cell]\n            self._average_face_to_cell_vector = sp.block_diag(stacks).tocsr()\n        return self._average_face_to_cell_vector\n\n    @property\n    @cython.boundscheck(False)\n    def average_node_to_cell(self):\n        r\"\"\"Averaging operator from nodes to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_n}` be a discrete scalar quantity that\n        lives on mesh nodes. **average_node_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{nc}}` that projects\n        :math:`\\boldsymbol{\\phi_f}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{nc}} \\, \\boldsymbol{\\phi_n}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its nodes. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Anc @ phi_n\n        \"\"\"\n        cdef np.int64_t[:] I, J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ii, id, n_ppc\n        cdef double scale\n        if self._average_node_to_cell is None:\n            n_ppc = 1<<self._dim\n            scale = 1.0/n_ppc\n            I = np.empty(self.n_cells*n_ppc, dtype=np.int64)\n            J = np.empty(self.n_cells*n_ppc, dtype=np.int64)\n            V = np.empty(self.n_cells*n_ppc, dtype=np.float64)\n\n            for cell in self.tree.cells:\n                ii = cell.index\n                for id in range(n_ppc):\n                    I[ii*n_ppc + id] = ii\n                    J[ii*n_ppc + id] = cell.points[id].index\n                    V[ii*n_ppc + id] = scale\n\n            Rn = self._deflate_nodes()\n            self._average_node_to_cell = sp.csr_matrix((V, (I, J)), shape=(self.n_cells, self.n_total_nodes))*Rn\n        return self._average_node_to_cell\n\n    @property\n    def average_node_to_edge_x(self):\n        \"\"\"Averaging operator from nodes to x edges (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to edges.\n\n        Returns\n        -------\n        (n_edges_x, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to edges\n        \"\"\"\n        if self._average_node_to_edge_x is not None:\n            return self._average_node_to_edge_x\n        cdef np.int64_t[:] I, J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ii, id\n        I = np.empty(self.n_edges_x*2, dtype=np.int64)\n        J = np.empty(self.n_edges_x*2, dtype=np.int64)\n        V = np.empty(self.n_edges_x*2, dtype=np.float64)\n\n        for it in self.tree.edges_x:\n            edge = it.second\n            if edge.hanging:\n                continue\n            ii = edge.index\n            for id in range(2):\n                I[ii*2 + id] = ii\n                J[ii*2 + id] = edge.points[id].index\n                V[ii*2 + id] = 0.5\n\n        Rn = self._deflate_nodes()\n        self._average_node_to_edge_x = sp.csr_matrix((V, (I, J)), shape=(self.n_edges_x, self.n_total_nodes))*Rn\n        return self._average_node_to_edge_x\n\n    @property\n    def average_node_to_edge_y(self):\n        \"\"\"Averaging operator from nodes to y edges (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to edges.\n\n        Returns\n        -------\n        (n_edges_y, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to edges\n        \"\"\"\n        if self._average_node_to_edge_y is not None:\n            return self._average_node_to_edge_y\n        cdef np.int64_t[:] I, J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ii, id\n        I = np.empty(self.n_edges_y*2, dtype=np.int64)\n        J = np.empty(self.n_edges_y*2, dtype=np.int64)\n        V = np.empty(self.n_edges_y*2, dtype=np.float64)\n\n        for it in self.tree.edges_y:\n            edge = it.second\n            if edge.hanging:\n                continue\n            ii = edge.index\n            for id in range(2):\n                I[ii*2 + id] = ii\n                J[ii*2 + id] = edge.points[id].index\n                V[ii*2 + id] = 0.5\n\n        Rn = self._deflate_nodes()\n        self._average_node_to_edge_y = sp.csr_matrix((V, (I, J)), shape=(self.n_edges_y, self.n_total_nodes))*Rn\n        return self._average_node_to_edge_y\n\n    @property\n    def average_node_to_edge_z(self):\n        \"\"\"Averaging operator from nodes to z edges (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to edges.\n\n        Returns\n        -------\n        (n_edges_z, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to edges\n        \"\"\"\n        if self._dim == 2:\n            raise Exception('TreeMesh has no z-edges in 2D')\n        if self._average_node_to_edge_z is not None:\n            return self._average_node_to_edge_z\n        cdef np.int64_t[:] I, J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ii, id\n        I = np.empty(self.n_edges_z*2, dtype=np.int64)\n        J = np.empty(self.n_edges_z*2, dtype=np.int64)\n        V = np.empty(self.n_edges_z*2, dtype=np.float64)\n\n        for it in self.tree.edges_z:\n            edge = it.second\n            if edge.hanging:\n                continue\n            ii = edge.index\n            for id in range(2):\n                I[ii*2 + id] = ii\n                J[ii*2 + id] = edge.points[id].index\n                V[ii*2 + id] = 0.5\n\n        Rn = self._deflate_nodes()\n        self._average_node_to_edge_z = sp.csr_matrix((V, (I, J)), shape=(self.n_edges_z, self.n_total_nodes))*Rn\n        return self._average_node_to_edge_z\n\n    @property\n    def average_node_to_edge(self):\n        r\"\"\"Averaging operator from nodes to edges (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges; scalar at edges is organized in a 1D numpy.array\n        of the form [x-edges, y-edges, z-edges]. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to edges. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_edges, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to edges\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_n}` be a discrete scalar quantity that\n        lives on mesh nodes. **average_node_to_edge** constructs a discrete\n        linear operator :math:`\\mathbf{A_{ne}}` that projects\n        :math:`\\boldsymbol{\\phi_n}` to edges, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_e} = \\mathbf{A_{ne}} \\, \\boldsymbol{\\phi_n}\n\n        where :math:`\\boldsymbol{\\phi_e}` approximates the value of the scalar\n        quantity at edges. For each edge, we are simply averaging\n        the values defined on the nodes it connects. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_e = Ane @ phi_n\n        \"\"\"\n        if self._average_node_to_edge is not None:\n            return self._average_node_to_edge\n\n        stacks = [self.average_node_to_edge_x, self.average_node_to_edge_y]\n        if self._dim == 3:\n            stacks += [self.average_node_to_edge_z]\n        self._average_node_to_edge = sp.vstack(stacks).tocsr()\n        return self._average_node_to_edge\n\n    @property\n    def average_node_to_face_x(self):\n        \"\"\"Averaging operator from nodes to x faces (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_faces_x, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to faces\n        \"\"\"\n        if self._dim == 2:\n            return self.average_node_to_edge_y\n        if self._average_node_to_face_x is not None:\n            return self._average_node_to_face_x\n        cdef np.int64_t[:] I, J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ii, id\n        I = np.empty(self.n_faces_x*4, dtype=np.int64)\n        J = np.empty(self.n_faces_x*4, dtype=np.int64)\n        V = np.empty(self.n_faces_x*4, dtype=np.float64)\n\n        for it in self.tree.faces_x:\n            face = it.second\n            if face.hanging:\n                continue\n            ii = face.index\n            for id in range(4):\n                I[ii*4 + id] = ii\n                J[ii*4 + id] = face.points[id].index\n                V[ii*4 + id] = 0.25\n\n        Rn = self._deflate_nodes()\n        self._average_node_to_face_x = sp.csr_matrix((V, (I, J)), shape=(self.n_faces_x, self.n_total_nodes))*Rn\n        return self._average_node_to_face_x\n\n    @property\n    def average_node_to_face_y(self):\n        \"\"\"Averaging operator from nodes to y faces (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_faces_y, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to faces\n        \"\"\"\n        if self._dim == 2:\n            return self.average_node_to_edge_x\n        if self._average_node_to_face_y is not None:\n            return self._average_node_to_face_y\n        cdef np.int64_t[:] I, J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ii, id\n\n        I = np.empty(self.n_faces_y*4, dtype=np.int64)\n        J = np.empty(self.n_faces_y*4, dtype=np.int64)\n        V = np.empty(self.n_faces_y*4, dtype=np.float64)\n\n        for it in self.tree.faces_y:\n            face = it.second\n            if face.hanging:\n                continue\n            ii = face.index\n            for id in range(4):\n                I[ii*4 + id] = ii\n                J[ii*4 + id] = face.points[id].index\n                V[ii*4 + id] = 0.25\n\n        Rn = self._deflate_nodes()\n        self._average_node_to_face_y = sp.csr_matrix((V, (I, J)), shape=(self.n_faces_y, self.n_total_nodes))*Rn\n        return self._average_node_to_face_y\n\n    @property\n    def average_node_to_face_z(self):\n        \"\"\"Averaging operator from nodes to z faces (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_faces_z, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to faces\n        \"\"\"\n        if self._dim == 2:\n            raise Exception('TreeMesh has no z faces in 2D')\n        cdef np.int64_t[:] I, J\n        cdef np.float64_t[:] V\n        cdef np.int64_t ii, id\n        if self._average_node_to_face_z is not None:\n            return self._average_node_to_face_z\n\n        I = np.empty(self.n_faces_z*4, dtype=np.int64)\n        J = np.empty(self.n_faces_z*4, dtype=np.int64)\n        V = np.empty(self.n_faces_z*4, dtype=np.float64)\n\n        for it in self.tree.faces_z:\n            face = it.second\n            if face.hanging:\n                continue\n            ii = face.index\n            for id in range(4):\n                I[ii*4 + id] = ii\n                J[ii*4 + id] = face.points[id].index\n                V[ii*4 + id] = 0.25\n\n        Rn = self._deflate_nodes()\n        self._average_node_to_face_z = sp.csr_matrix((V, (I, J)), shape=(self.n_faces_z, self.n_total_nodes))*Rn\n        return self._average_node_to_face_z\n\n    @property\n    def average_node_to_face(self):\n        r\"\"\"Averaging operator from nodes to faces (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges; scalar at faces is organized in a 1D numpy.array\n        of the form [x-faces, y-faces, z-faces]. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to faces. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_faces, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to faces\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_n}` be a discrete scalar quantity that\n        lives on mesh nodes. **average_node_to_face** constructs a discrete\n        linear operator :math:`\\mathbf{A_{nf}}` that projects\n        :math:`\\boldsymbol{\\phi_n}` to faces, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_f} = \\mathbf{A_{nf}} \\, \\boldsymbol{\\phi_n}\n\n        where :math:`\\boldsymbol{\\phi_f}` approximates the value of the scalar\n        quantity at faces. For each face, we are simply averaging the values at\n        the nodes which outline the face. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_f = Anf @ phi_n\n        \"\"\"\n        if self._average_node_to_face is not None:\n            return self._average_node_to_face\n\n        stacks = [self.average_node_to_face_x, self.average_node_to_face_y]\n        if self._dim == 3:\n            stacks += [self.average_node_to_face_z]\n        self._average_node_to_face = sp.vstack(stacks).tocsr()\n        return self._average_node_to_face\n\n    @property\n    def average_cell_to_face(self):\n        r\"\"\"Averaging operator from cell centers to faces (scalar quantities).\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_faces, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from cell centers to faces\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_c}` be a discrete scalar quantity that\n        lives at cell centers. **average_cell_to_face** constructs a discrete\n        linear operator :math:`\\mathbf{A_{cf}}` that projects\n        :math:`\\boldsymbol{\\phi_c}` to faces, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_f} = \\mathbf{A_{cf}} \\, \\boldsymbol{\\phi_c}\n\n        where :math:`\\boldsymbol{\\phi_f}` approximates the value of the scalar\n        quantity at the faces. For each face, we are performing a weighted average\n        between the values at adjacent cell centers. In 1D, where adjacent cells\n        :math:`i` and :math:`i+1` have widths :math:`h_i` and :math:`h_{i+1}`,\n        :math:`\\phi` on face is approximated by:\n\n        .. math::\n            \\phi_{i \\! + \\! 1/2} \\approx \\frac{h_{i+1} \\phi_i + h_i \\phi_{i+1}}{h_i + h_{i+1}}\n\n        On boundary faces, nearest neighbour is used to extrapolate the value\n        from the nearest cell center. Once the operator is construct, the averaging\n        is implemented as a matrix vector product, i.e.::\n\n            phi_f = Acf @ phi_c\n        \"\"\"\n        self._error_if_not_finalized(\"average_cell_to_face\")\n        if self._average_cell_to_face is not None:\n            return self._average_cell_to_face\n        stacks = [self.average_cell_to_face_x, self.average_cell_to_face_y]\n        if self._dim == 3:\n            stacks.append(self.average_cell_to_face_z)\n\n        self._average_cell_to_face = sp.vstack(stacks).tocsr()\n        return self._average_cell_to_face\n\n    @property\n    def average_cell_vector_to_face(self):\n        r\"\"\"Averaging operator from cell centers to faces (vector quantities).\n\n        This property constructs the averaging operator that independently maps the\n        Cartesian components of vector quantities from cell centers to faces.\n        This averaging operators is used when a discrete vector quantity defined at\n        cell centers must be approximated on the faces. Once constructed, the operator is\n        stored permanently as a property of the mesh.\n\n        Be aware that the Cartesian components of the original vector\n        are defined seperately at cell centers in a 1D numpy.array organized [ux, uy, uz].\n        Once projected to faces, the Cartesian components are defined on their respective\n        faces; e.g. the x-component lives on x-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_f = Acf @ u_c\n\n        Returns\n        -------\n        (n_faces, dim * n_cells) scipy.sparse.csr_matrix\n            The vector averaging operator from cell centers to faces. Since we\n            are averaging a vector quantity from cell centers, the second dimension\n            of the operator is the mesh dimension times the number of cells.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_c}` be the discrete representation of a vector\n        quantity whose Cartesian components are defined separately at cell centers.\n        **average_cell_vector_to_face** constructs a discrete linear operator\n        :math:`\\mathbf{A_{cf}}` that projects each Cartesian component of\n        :math:`\\mathbf{u_c}` to the faces, i.e.:\n\n        .. math::\n            \\mathbf{u_f} = \\mathbf{A_{cf}} \\, \\mathbf{u_c}\n\n        where :math:`\\mathbf{u_f}` is the discrete vector quantity whose Cartesian\n        components are approximated on their respective cell faces; e.g. the x-component is\n        approximated on x-faces. For each face (x, y or z), we are simply taking a weighted average\n        between the values of the correct Cartesian component at the corresponding cell centers.\n\n        E.g. for the x-component, which is projected to x-faces, the weighted average on\n        a 2D mesh would be:\n\n        .. math::\n            u_x(i \\! + \\! 1/2, j) = \\frac{h_{i+1} u_x (i,j) + h_i u_x(i \\! + \\! 1,j)}{hx_i + hx_{i+1}}\n\n        where :math:`h_i` and :math:`h_{i+1}` represent the cell respective cell widths\n        in the x-direction. For boundary faces, nearest neighbor is used to extrapolate\n        the values.\n        \"\"\"\n        self._error_if_not_finalized(\"average_cell_vector_to_face\")\n        if self._average_cell_vector_to_face is not None:\n            return self._average_cell_vector_to_face\n        stacks = [self.average_cell_to_face_x, self.average_cell_to_face_y]\n        if self._dim == 3:\n            stacks.append(self.average_cell_to_face_z)\n\n        self._average_cell_vector_to_face = sp.block_diag(stacks).tocsr()\n        return self._average_cell_vector_to_face\n\n    @property\n    def average_cell_to_face_x(self):\n        \"\"\"Averaging operator from cell centers to x faces (scalar quantities).\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_faces_x, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from cell centers to x faces\n        \"\"\"\n        self._error_if_not_finalized(\"average_cell_to_face_x\")\n        if self._average_cell_to_face_x is not None:\n            return self._average_cell_to_face_x\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_x, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_x, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_x, dtype=np.float64)\n        cdef int dim = self._dim\n        cdef int_t ind, ind_parent\n        cdef int_t children_per_parent = (dim-1)*2\n        cdef c_Cell* child\n        cdef c_Cell* next_cell\n        cdef c_Cell* prev_cell\n        cdef double w\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[1]\n            prev_cell = cell.neighbors[0]\n            # handle extrapolation to boundary faces\n            if next_cell == NULL:\n                if dim == 2:\n                    ind = cell.edges[3].index # +x face\n                else:\n                    ind = cell.faces[1].index # +x face\n                I[2*ind  ] = ind\n                J[2*ind  ] = cell.index\n                V[2*ind  ] = 1.0\n                continue\n            if prev_cell == NULL:\n                if dim == 2:\n                    ind = cell.edges[2].index # -x face\n                else:\n                    ind = cell.faces[0].index # -x face\n                I[2*ind  ] = ind\n                J[2*ind  ] = cell.index\n                V[2*ind  ] = 1.0\n\n            if next_cell.is_leaf():\n                if next_cell.level == cell.level:\n                    #I am on the same level and easy to interpolate\n                    if dim == 2:\n                        ind = cell.edges[3].index\n                        w = (next_cell.location[0] - cell.edges[3].location[0])/(\n                             next_cell.location[0] - cell.location[0])\n                    else:\n                        ind = cell.faces[1].index\n                        w = (next_cell.location[0] - cell.faces[1].location[0])/(\n                             next_cell.location[0] - cell.location[0])\n                    I[2*ind  ] = ind\n                    I[2*ind+1] = ind\n                    J[2*ind  ] = cell.index\n                    J[2*ind+1] = next_cell.index\n                    V[2*ind  ] = w\n                    V[2*ind+1] = (1.0-w)\n                else:\n                    # if next cell is a level larger than i am, then i need to accumulate into w\n                    if dim == 2:\n                        ind = cell.edges[3].index\n                        ind_parent = cell.edges[3].parents[0].index\n                        w = (next_cell.location[0] - cell.edges[3].location[0])/(\n                             next_cell.location[0] - cell.location[0])\n                    else:\n                        ind = cell.faces[1].index\n                        ind_parent = cell.faces[1].parent.index\n                        w = (next_cell.location[0] - cell.faces[1].location[0])/(\n                             next_cell.location[0] - cell.location[0])\n                    I[2*ind  ] = ind_parent\n                    I[2*ind+1] = ind_parent\n                    J[2*ind  ] = cell.index\n                    J[2*ind+1] = next_cell.index\n                    V[2*ind  ] = w/children_per_parent\n                    V[2*ind+1] = (1.0-w)/children_per_parent\n            else:\n                #should mean next cell is not a leaf so need to loop over children\n                if dim == 2:\n                    ind_parent = cell.edges[3].index\n                    w = (next_cell.children[0].location[0] - cell.edges[3].location[0])/(\n                         next_cell.children[0].location[0] - cell.location[0])\n                    for i in range(2):\n                        child = next_cell.children[2*i]\n                        ind = child.edges[2].index\n                        I[2*ind    ] = ind_parent\n                        I[2*ind + 1] = ind_parent\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = child.index\n                        V[2*ind    ] = w/children_per_parent\n                        V[2*ind + 1] = (1.0-w)/children_per_parent\n                else:\n                    ind_parent = cell.faces[1].index\n                    w = (next_cell.children[0].location[0] - cell.faces[1].location[0])/(\n                         next_cell.children[0].location[0] - cell.location[0])\n                    for i in range(4): # four neighbors in +x direction\n                        child = next_cell.children[2*i] #0 2 4 6\n                        ind = child.faces[0].index\n                        I[2*ind    ] = ind_parent\n                        I[2*ind + 1] = ind_parent\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = child.index\n                        V[2*ind    ] = w/children_per_parent\n                        V[2*ind + 1] = (1.0-w)/children_per_parent\n\n        self._average_cell_to_face_x = sp.csr_matrix((V, (I, J)), shape=(self.n_faces_x, self.n_cells))\n        return self._average_cell_to_face_x\n\n    @property\n    def average_cell_to_face_y(self):\n        \"\"\"Averaging operator from cell centers to y faces (scalar quantities).\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_faces_y, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from cell centers to y faces\n        \"\"\"\n        self._error_if_not_finalized(\"average_cell_to_face_y\")\n        if self._average_cell_to_face_y is not None:\n            return self._average_cell_to_face_y\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_y, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_y, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_y, dtype=np.float64)\n        cdef int dim = self._dim\n        cdef int_t ind, ind_parent\n        cdef int_t children_per_parent = (dim-1)*2\n        cdef c_Cell* child\n        cdef c_Cell* next_cell\n        cdef c_Cell* prev_cell\n        cdef double w\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[3]\n            prev_cell = cell.neighbors[2]\n            # handle extrapolation to boundary faces\n            if next_cell == NULL:\n                if dim == 2:\n                    ind = cell.edges[1].index\n                else:\n                    ind = cell.faces[3].index\n                I[2*ind  ] = ind\n                J[2*ind  ] = cell.index\n                V[2*ind  ] = 1.0\n                continue\n            if prev_cell == NULL:\n                if dim == 2:\n                    ind = cell.edges[0].index\n                else:\n                    ind = cell.faces[2].index\n                I[2*ind  ] = ind\n                J[2*ind  ] = cell.index\n                V[2*ind  ] = 1.0\n\n            if next_cell.is_leaf():\n                if next_cell.level == cell.level:\n                    #I am on the same level and easy to interpolate\n                    if dim == 2:\n                        ind = cell.edges[1].index\n                        w = (next_cell.location[1] - cell.edges[1].location[1])/(\n                             next_cell.location[1] - cell.location[1])\n                    else:\n                        ind = cell.faces[3].index\n                        w = (next_cell.location[1] - cell.faces[3].location[1])/(\n                             next_cell.location[1] - cell.location[1])\n                    I[2*ind  ] = ind\n                    I[2*ind+1] = ind\n                    J[2*ind  ] = cell.index\n                    J[2*ind+1] = next_cell.index\n                    V[2*ind  ] = w\n                    V[2*ind+1] = (1.0-w)\n                else:\n                    # if next cell is a level larger than i am\n                    if dim == 2:\n                        ind = cell.edges[1].index\n                        ind_parent = cell.edges[1].parents[0].index\n                        w = (next_cell.location[1] - cell.edges[1].location[1])/(\n                             next_cell.location[1] - cell.location[1])\n                    else:\n                        ind = cell.faces[3].index\n                        ind_parent = cell.faces[3].parent.index\n                        w = (next_cell.location[1] - cell.faces[3].location[1])/(\n                             next_cell.location[1] - cell.location[1])\n                    I[2*ind  ] = ind_parent\n                    I[2*ind+1] = ind_parent\n                    J[2*ind  ] = cell.index\n                    J[2*ind+1] = next_cell.index\n                    V[2*ind  ] = w/children_per_parent\n                    V[2*ind+1] = (1.0-w)/children_per_parent\n            else:\n                #should mean next cell is not a leaf so need to loop over children\n                if dim == 2:\n                    ind_parent = cell.edges[1].index\n                    w = (next_cell.children[0].location[1] - cell.edges[1].location[1])/(\n                         next_cell.children[0].location[1] - cell.location[1])\n                    for i in range(2):\n                        child = next_cell.children[i]\n                        ind = child.edges[0].index\n                        I[2*ind    ] = ind_parent\n                        I[2*ind + 1] = ind_parent\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = child.index\n                        V[2*ind    ] = w/children_per_parent\n                        V[2*ind + 1] = (1.0-w)/children_per_parent\n                else:\n                    ind_parent = cell.faces[3].index\n                    w = (next_cell.children[0].location[1] - cell.faces[3].location[1])/(\n                         next_cell.children[0].location[1] - cell.location[1])\n                    for i in range(4): # four neighbors in +y direction\n                        child = next_cell.children[(i>>1)*4 + i%2] #0 1 4 5\n                        ind = child.faces[2].index\n                        I[2*ind    ] = ind_parent\n                        I[2*ind + 1] = ind_parent\n                        J[2*ind    ] = cell.index\n                        J[2*ind + 1] = child.index\n                        V[2*ind    ] = w/children_per_parent\n                        V[2*ind + 1] = (1.0-w)/children_per_parent\n\n        self._average_cell_to_face_y = sp.csr_matrix((V, (I,J)), shape=(self.n_faces_y, self.n_cells))\n        return self._average_cell_to_face_y\n\n    @property\n    def average_cell_to_face_z(self):\n        \"\"\"Averaging operator from cell centers to z faces (scalar quantities).\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces.\n\n        Returns\n        -------\n        (n_faces_z, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from cell centers to z faces\n        \"\"\"\n        self._error_if_not_finalized(\"average_cell_to_face_z\")\n        if self.dim == 2:\n            raise Exception('TreeMesh has no z-faces in 2D')\n        if self._average_cell_to_face_z is not None:\n            return self._average_cell_to_face_z\n        cdef np.int64_t[:] I = np.zeros(2*self.n_total_faces_z, dtype=np.int64)\n        cdef np.int64_t[:] J = np.zeros(2*self.n_total_faces_z, dtype=np.int64)\n        cdef np.float64_t[:] V = np.zeros(2*self.n_total_faces_z, dtype=np.float64)\n        cdef int dim = self._dim\n        cdef int_t ind, ind_parent\n        cdef int_t children_per_parent = (dim-1)*2\n        cdef c_Cell* child\n        cdef c_Cell* next_cell\n        cdef c_Cell* prev_cell\n        cdef double w\n\n        for cell in self.tree.cells :\n            next_cell = cell.neighbors[5]\n            prev_cell = cell.neighbors[4]\n            # handle extrapolation to boundary faces\n            if next_cell == NULL:\n                ind = cell.faces[5].index # +z face\n                I[2*ind  ] = ind\n                J[2*ind  ] = cell.index\n                V[2*ind  ] = 1.0\n                continue\n            if prev_cell == NULL:\n                ind = cell.faces[4].index # -z face\n                I[2*ind  ] = ind\n                J[2*ind  ] = cell.index\n                V[2*ind  ] = 1.0\n\n            if next_cell.is_leaf():\n                if next_cell.level == cell.level:\n                    #I am on the same level and easy to interpolate\n                    ind = cell.faces[5].index\n                    w = (next_cell.location[2] - cell.faces[5].location[2])/(\n                         next_cell.location[2] - cell.location[2])\n                    I[2*ind  ] = ind\n                    I[2*ind+1] = ind\n                    J[2*ind  ] = cell.index\n                    J[2*ind+1] = next_cell.index\n                    V[2*ind  ] = w\n                    V[2*ind+1] = (1.0-w)\n                else:\n                    # if next cell is a level larger than i am\n                    ind = cell.faces[5].index\n                    ind_parent = cell.faces[5].parent.index\n                    w = (next_cell.location[2] - cell.faces[5].location[2])/(\n                         next_cell.location[2] - cell.location[2])\n                    I[2*ind  ] = ind_parent\n                    I[2*ind+1] = ind_parent\n                    J[2*ind  ] = cell.index\n                    J[2*ind+1] = next_cell.index\n                    V[2*ind  ] = w/children_per_parent\n                    V[2*ind+1] = (1.0-w)/children_per_parent\n            else:\n                #should mean next cell is not a leaf so need to loop over children\n                ind_parent = cell.faces[5].index\n                w = (next_cell.children[0].location[2] - cell.faces[5].location[2])/(\n                     next_cell.children[0].location[2] - cell.location[2])\n                for i in range(4): # four neighbors in +x direction\n                    child = next_cell.children[i]\n                    ind = child.faces[4].index #0 1 2 3\n                    I[2*ind    ] = ind_parent\n                    I[2*ind + 1] = ind_parent\n                    J[2*ind    ] = cell.index\n                    J[2*ind + 1] = child.index\n                    V[2*ind    ] = w/children_per_parent\n                    V[2*ind + 1] = (1.0-w)/children_per_parent\n\n        self._average_cell_to_face_z = sp.csr_matrix((V, (I,J)), shape=(self.n_faces_z, self.n_cells))\n        return self._average_cell_to_face_z\n\n    @property\n    def project_face_to_boundary_face(self):\n        r\"\"\"Projection matrix from all faces to boundary faces.\n\n        Constructs and returns a matrix :math:`\\mathbf{P}` that projects from\n        all mesh faces to boundary faces. That is, for a discrete vector\n        :math:`\\mathbf{u}` that lives on the faces, the values on the boundary\n        faces :math:`\\mathbf{u_b}` can be extracted via the following\n        matrix-vector product::\n\n            ub = P @ u\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            (n_boundary_faces, n_faces) Projection matrix with shape\n        \"\"\"\n        self._error_if_not_finalized(\"project_face_to_boundary_face\")\n        faces_x = self.faces_x\n        faces_y = self.faces_y\n\n        x0, xF = self._xs[0], self._xs[-1]\n        y0, yF = self._ys[0], self._ys[-1]\n        is_b = np.r_[\n            (faces_x[:, 0] == x0) | (faces_x[:, 0] == xF),\n            (faces_y[:, 1] == y0) | (faces_y[:, 1] == yF)\n        ]\n        if self.dim == 3:\n            faces_z = self.faces_z\n            z0, zF = self._zs[0], self._zs[-1]\n            is_b = np.r_[\n                is_b,\n                (faces_z[:, 2] == z0) | (faces_z[:, 2] == zF)\n            ]\n        return sp.eye(self.n_faces, format='csr')[is_b]\n\n    @property\n    def project_edge_to_boundary_edge(self):\n        r\"\"\"Projection matrix from all edges to boundary edges.\n\n        Constructs and returns a matrix :math:`\\mathbf{P}` that projects from\n        all mesh edges to boundary edges. That is, for a discrete vector\n        :math:`\\mathbf{u}` that lives on the edges, the values on the boundary\n        edges :math:`\\mathbf{u_b}` can be extracted via the following\n        matrix-vector product::\n\n            ub = P @ u\n\n        Returns\n        -------\n        (n_boundary_edges, n_edges) scipy.sparse.csr_matrix\n            Projection matrix with shape\n        \"\"\"\n        self._error_if_not_finalized(\"project_edge_to_boundary_edge\")\n        edges_x = self.edges_x\n        edges_y = self.edges_y\n\n        x0, xF = self._xs[0], self._xs[-1]\n        y0, yF = self._ys[0], self._ys[-1]\n        is_bx = (edges_x[:, 1] == y0) | (edges_x[:, 1] == yF)\n        is_by = (edges_y[:, 0] == x0) | (edges_y[:, 0] == xF)\n        if self.dim == 3:\n            z0, zF = self._zs[0], self._zs[-1]\n            edges_z = self.edges_z\n\n            is_bx |= (edges_x[:, 2] == z0) | (edges_x[:, 2] == zF)\n            is_by |= (edges_y[:, 2] == z0) | (edges_y[:, 2] == zF)\n\n            is_bz = (\n                (edges_z[:, 0] == x0)\n                | (edges_z[:, 0] == xF)\n                | (edges_z[:, 1] == y0)\n                | (edges_z[:, 1] == yF)\n            )\n            is_b = np.r_[is_bx, is_by, is_bz]\n        else:\n            is_b = np.r_[is_bx, is_by]\n        return sp.eye(self.n_edges, format='csr')[is_b]\n\n    @property\n    def project_node_to_boundary_node(self):\n        r\"\"\"Projection matrix from all nodes to boundary nodes.\n\n        Constructs and returns a matrix :math:`\\mathbf{P}` that projects from\n        all mesh nodes to boundary nodes. That is, for a discrete scalar\n        :math:`\\mathbf{u}` that lives on the nodes, the values on the boundary\n        nodes :math:`\\mathbf{u_b}` can be extracted via the following\n        matrix-vector product::\n\n            ub = P @ u\n\n        Returns\n        -------\n        (n_boundary_nodes, n_nodes) scipy.sparse.csr_matrix\n            Projection matrix with shape\n        \"\"\"\n        self._error_if_not_finalized(\"project_node_to_boundary_node\")\n        nodes = self.nodes\n        x0, xF = self._xs[0], self._xs[-1]\n        y0, yF = self._ys[0], self._ys[-1]\n        is_b = (\n            (nodes[:, 0] == x0)\n            | (nodes[:, 0] == xF)\n            | (nodes[:, 1] == y0)\n            | (nodes[:, 1] == yF)\n        )\n        if self.dim > 2:\n            z0, zF = self._zs[0], self._zs[-1]\n            is_b |= (nodes[:, 2] == z0) | (nodes[:, 2] == zF)\n        return sp.eye(self.n_nodes, format='csr')[is_b]\n\n    def _count_cells_per_index(self):\n        cdef np.int64_t[:] counts = np.zeros(self.max_level+1, dtype=np.int64)\n        for cell in self.tree.cells:\n            counts[cell.level] += 1\n        return np.array(counts)\n\n    def _cell_levels_by_indexes(self, index=None):\n        cdef np.int64_t[:] inds\n        cdef bool do_all = index is None\n        cdef int_t n_cells\n        if not do_all:\n            index = np.require(np.atleast_1d(index), dtype=np.int64, requirements='C')\n            inds = index\n            n_cells = inds.shape[0]\n        else:\n            n_cells = self.n_cells\n\n        cdef np.int64_t[:] levels = np.empty(n_cells, dtype=np.int64)\n        cdef int_t ii\n        for i in range(n_cells):\n            ii = i if do_all else inds[i]\n            levels[i] = self.tree.cells[ii].level\n        if n_cells == 1:\n            return levels[0]\n        else:\n            return np.array(levels)\n\n    def _getFaceP(self, xFace, yFace, zFace):\n        cdef int dim = self._dim\n        cdef int_t ind, id\n\n        cdef np.int64_t[:] I, J, J1, J2, J3\n        cdef np.float64_t[:] V\n\n        J1 = np.empty(self.n_cells, dtype=np.int64)\n        J2 = np.empty(self.n_cells, dtype=np.int64)\n        if dim==3:\n            J3 = np.empty(self.n_cells, dtype=np.int64)\n\n        cdef int[3] faces\n        cdef np.int64_t[:] offsets = np.empty(self._dim, dtype=np.int64)\n        faces[0] = (xFace == 'fXp')\n        faces[1] = (yFace == 'fYp')\n        if dim == 3:\n            faces[2] = (zFace == 'fZp')\n\n        if dim == 2:\n            offsets[0] = 0\n            offsets[1] = self.n_total_faces_x\n        else:\n            offsets[0] = 0\n            offsets[1] = self.n_total_faces_x\n            offsets[2] = self.n_total_faces_x + self.n_total_faces_y\n\n        for cell in self.tree.cells:\n            ind = cell.index\n            if dim==2:\n                J1[ind] = cell.edges[2 + faces[0]].index\n                J2[ind] = cell.edges[    faces[1]].index + offsets[1]\n            else:\n                J1[ind] = cell.faces[    faces[0]].index\n                J2[ind] = cell.faces[2 + faces[1]].index + offsets[1]\n                J3[ind] = cell.faces[4 + faces[2]].index + offsets[2]\n\n        I = np.arange(dim*self.n_cells, dtype=np.int64)\n        if dim==2:\n            J = np.r_[J1, J2]\n        else:\n            J = np.r_[J1, J2, J3]\n        V = np.ones(self.n_cells*dim, dtype=np.float64)\n\n        P = sp.csr_matrix((V, (I, J)), shape=(self._dim*self.n_cells, self.n_total_faces))\n        Rf = self._deflate_faces()\n        return P*Rf\n\n    def _getFacePxx(self):\n        def Pxx(xFace, yFace):\n            return self._getFaceP(xFace, yFace, None)\n        return Pxx\n\n    def _getFacePxxx(self):\n        def Pxxx(xFace, yFace, zFace):\n            return self._getFaceP(xFace, yFace, zFace)\n        return Pxxx\n\n    def _getEdgeP(self, xEdge, yEdge, zEdge):\n        cdef int dim = self._dim\n        cdef int_t ind, id\n        cdef int epc = 1<<(dim-1) #edges per cell 2/4\n\n        cdef np.int64_t[:] I, J, J1, J2, J3\n        cdef np.float64_t[:] V\n\n        J1 = np.empty(self.n_cells, dtype=np.int64)\n        J2 = np.empty(self.n_cells, dtype=np.int64)\n        if dim == 3:\n            J3 = np.empty(self.n_cells, dtype=np.int64)\n\n        cdef int[3] edges\n        cdef np.int64_t[:] offsets = np.empty(self._dim, dtype=np.int64)\n        try:\n            edges[0] = int(xEdge[-1]) #0, 1, 2, 3\n            edges[1] = int(yEdge[-1]) #0, 1, 2, 3\n            if dim == 3:\n                edges[2] = int(zEdge[-1]) #0, 1, 2, 3\n        except ValueError:\n            raise Exception('Last character of edge string must be 0, 1, 2, or 3')\n\n        offsets[0] = 0\n        offsets[1] = self.n_total_edges_x\n        if dim==3:\n            offsets[2] = self.n_total_edges_x + self.n_total_edges_y\n\n        for cell in self.tree.cells:\n            ind = cell.index\n            J1[ind] = cell.edges[0*epc + edges[0]].index + offsets[0]\n            J2[ind] = cell.edges[1*epc + edges[1]].index + offsets[1]\n            if dim==3:\n                J3[ind] = cell.edges[2*epc + edges[2]].index + offsets[2]\n\n        I = np.arange(dim*self.n_cells, dtype=np.int64)\n        if dim==2:\n            J = np.r_[J1, J2]\n        else:\n            J = np.r_[J1, J2, J3]\n        V = np.ones(self.n_cells*dim, dtype=np.float64)\n\n        P = sp.csr_matrix((V, (I, J)), shape=(self._dim*self.n_cells, self.n_total_edges))\n        Rf = self._deflate_edges()\n        return P*Rf\n\n    def _getEdgePxx(self):\n        def Pxx(xEdge, yEdge):\n            return self._getEdgeP(xEdge, yEdge, None)\n        return Pxx\n\n    def _getEdgePxxx(self):\n        def Pxxx(xEdge, yEdge, zEdge):\n            return self._getEdgeP(xEdge, yEdge, zEdge)\n        return Pxxx\n\n    def _getEdgeIntMat(self, locs, zeros_outside, direction):\n        cdef:\n            double[:, :] locations = locs\n            int_t dir, dir1, dir2\n            int_t dim = self._dim\n            int_t n_loc = locs.shape[0]\n            int_t n_edges = 2 if self._dim == 2 else 4\n            np.int64_t[:] indptr = 2 * n_edges * np.arange(n_loc+1, dtype=np.int64)\n            np.int64_t[:] indices = np.empty(n_loc * 2 * n_edges, dtype=np.int64)\n            np.float64_t[:] data = np.empty(n_loc * 2 * n_edges, dtype=np.float64)\n\n            np.int64_t[:] row_inds\n            np.float64_t[:] row_data\n\n            int_t ii, i, j, offset\n            c_Cell *cell\n            c_Cell *i0\n            c_Cell *i1\n            Edge *i000\n            Edge *i001\n            Edge *i010\n            Edge *i011\n            Edge *i100\n            Edge *i101\n            Edge *i110\n            Edge *i111\n            double x, y, z\n            double w1, w2, w3\n            double eps = 100*np.finfo(float).eps\n            int zeros_out = zeros_outside\n\n        if direction == 'x':\n            dir, dir1, dir2 = 0, 1, 2\n            offset = 0\n        elif direction == 'y':\n            dir, dir1, dir2 = 1, 0, 2\n            offset = self.n_total_edges_x\n        elif direction == 'z':\n            dir, dir1, dir2 = 2, 0, 1\n            offset = self.n_total_edges_x + self.n_total_edges_y\n        else:\n            raise ValueError('Invalid direction, must be x, y, or z')\n\n        for i in range(n_loc):\n            x = locations[i, 0]\n            y = locations[i, 1]\n            z = locations[i, 2] if dim==3 else 0.0\n            # get containing (or closest) cell\n            cell = self.tree.containing_cell(x, y, z)\n            row_inds = indices[indptr[i]:indptr[i+1]]\n            row_data = data[indptr[i]:indptr[i+1]]\n            was_outside = False\n            if zeros_out:\n                if x < cell.points[0].location[0]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif x > cell.points[3].location[0]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif y < cell.points[0].location[1]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif y > cell.points[3].location[1]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif dim == 3 and z < cell.points[0].location[2]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif dim == 3 and z > cell.points[7].location[2]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n            if not was_outside:\n                # look + dir and - dir away\n                if (\n                    locations[i, dir] < cell.location[dir]\n                    and cell.neighbors[2*dir] != NULL\n                    and cell.neighbors[2*dir].is_leaf()\n                    and cell.neighbors[2*dir].level == cell.level\n                ):\n                    i0 = cell.neighbors[2*dir]\n                    i1 = cell\n                elif(\n                    locations[i, dir] > cell.location[dir]\n                    and cell.neighbors[2*dir + 1] != NULL\n                    and cell.neighbors[2*dir + 1].is_leaf()\n                    and cell.neighbors[2*dir + 1].level == cell.level\n                ):\n                    i0 = cell\n                    i1 = cell.neighbors[2*dir + 1]\n                else:\n                    i0 = cell\n                    i1 = cell\n\n                i000 = i0.edges[n_edges * dir]\n                i001 = i0.edges[n_edges * dir + 1]\n                w1 = ((i001.location[dir1] - locations[i, dir1])/\n                      (i001.location[dir1] - i000.location[dir1]))\n\n                i010 = i1.edges[n_edges*dir]\n                i011 = i1.edges[n_edges*dir + 1]\n                if i0.index != i1.index:\n                    w2 = ((i010.location[dir] - locations[i, dir])/\n                          (i010.location[dir] - i000.location[dir]))\n                else:\n                    w2 = 1.0\n\n                if dim == 3:\n                    i100 = i0.edges[n_edges * dir + 2]\n                    i101 = i0.edges[n_edges * dir + 3]\n                    i110 = i1.edges[n_edges * dir + 2]\n                    i111 = i1.edges[n_edges * dir + 3]\n\n                    w3 = ((i100.location[dir2] - locations[i, dir2])/\n                          (i100.location[dir2] - i000.location[dir2]))\n                else:\n                    w3 = 1.0\n\n                w1 = _clip01(w1)\n                w2 = _clip01(w2)\n                w3 = _clip01(w3)\n\n                row_data[0] = w1 * w2 * w3\n                row_data[1] = (1 - w1) * w2 * w3\n                row_data[2] = w1 * (1 - w2) * w3\n                row_data[3] = (1 - w1) * (1 - w2) * w3\n                row_inds[0] = i000.index + offset\n                row_inds[1] = i001.index + offset\n                row_inds[2] = i010.index + offset\n                row_inds[3] = i011.index + offset\n                if dim==3:\n                    row_data[4] = w1 * w2 * (1 - w3)\n                    row_data[5] = (1 - w1) * w2 * (1 - w3)\n                    row_data[6] = w1 * (1 - w2) * (1 - w3)\n                    row_data[7] = (1 - w1) * (1 - w2) * (1 - w3)\n                    row_inds[4] = i100.index + offset\n                    row_inds[5] = i101.index + offset\n                    row_inds[6] = i110.index + offset\n                    row_inds[7] = i111.index + offset\n\n        Re = self._deflate_edges()\n        A = sp.csr_matrix((data, indices, indptr), shape=(locs.shape[0], self.n_total_edges))\n        return A*Re\n\n    def _getFaceIntMat(self, locs, zeros_outside, direction):\n        cdef:\n            double[:, :] locations = locs\n            int_t dir, dir1, dir2, temp\n            int_t dim = self._dim\n            int_t n_loc = locs.shape[0]\n            int_t n_faces = 4 if dim == 2 else 8\n            np.int64_t[:] indptr = n_faces * np.arange(n_loc + 1, dtype=np.int64)\n            np.int64_t[:] indices = np.empty(n_loc*n_faces, dtype=np.int64)\n            np.float64_t[:] data = np.empty(n_loc*n_faces, dtype=np.float64)\n\n            int_t ii, i, offset\n            c_Cell *cell\n            c_Cell *i00\n            c_Cell *i01\n            c_Cell *i10\n            c_Cell *i11\n            Face *f000\n            Face *f001\n            Face *f010\n            Face *f011\n            Face *f100\n            Face *f101\n            Face *f110\n            Face *f111\n            Edge *e00\n            Edge *e01\n            Edge *e10\n            Edge *e11\n            double x, y, z\n            double w1, w2, w3\n            double eps = 100*np.finfo(float).eps\n            int zeros_out = zeros_outside\n\n        if direction == 'x':\n            dir = 0\n            dir1 = 1\n            dir2 = 2\n            offset = 0\n        elif direction == 'y':\n            dir = 1\n            dir1 = 0\n            dir2 = 2\n            offset = self.n_total_faces_x\n        elif direction == 'z':\n            dir = 2\n            dir1 = 0\n            dir2 = 1\n            offset = self.n_total_faces_x + self.n_total_faces_y\n        else:\n            raise ValueError('Invalid direction, must be x, y, or z')\n\n        for i in range(n_loc):\n            x = locations[i, 0]\n            y = locations[i, 1]\n            z = locations[i, 2] if dim==3 else 0.0\n            #get containing (or closest) cell\n            cell = self.tree.containing_cell(x, y, z)\n            row_inds = indices[indptr[i]:indptr[i+1]]\n            row_data = data[indptr[i]:indptr[i+1]]\n            was_outside = False\n            if zeros_out:\n                if x < cell.points[0].location[0]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif x > cell.points[3].location[0]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif y < cell.points[0].location[1]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif y > cell.points[3].location[1]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif dim == 3 and z < cell.points[0].location[2]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif dim == 3 and z > cell.points[7].location[2]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n            if not was_outside:\n              # Find containing cells\n              # Decide order to search based on which face it is closest to\n              if dim == 3:\n                  if (\n                      abs(locations[i, dir1] - cell.location[dir1]) <\n                      abs(locations[i, dir2] - cell.location[dir2])\n                  ):\n                    temp = dir1\n                    dir1 = dir2\n                    dir2 = temp\n              # look in dir1 direction\n              if (\n                  locations[i, dir1] < cell.location[dir1]\n                  and cell.neighbors[2*dir1] != NULL\n                  and cell.neighbors[2*dir1].is_leaf()\n                  and cell.neighbors[2*dir1].level == cell.level\n              ):\n                  i00 = cell.neighbors[2*dir1]\n                  i01 = cell\n              elif (\n                  locations[i, dir1] > cell.location[dir1]\n                  and cell.neighbors[2*dir1 + 1] != NULL\n                  and cell.neighbors[2*dir1 + 1].is_leaf()\n                  and cell.neighbors[2*dir1 + 1].level == cell.level\n              ):\n                  i00 = cell\n                  i01 = cell.neighbors[2*dir1 + 1]\n              else:\n                  i00 = i01 = cell\n\n              if dim == 2:\n                  e00 = i00.edges[2 * dir1]\n                  e01 = i00.edges[2 * dir1 + 1]\n                  e10 = i01.edges[2 * dir1]\n                  e11 = i01.edges[2 * dir1 + 1]\n                  w1 = ((e01.location[dir] - locations[i, dir])/\n                        (e01.location[dir] - e00.location[dir]))\n                  if i00.index != i01.index:\n                      w2 = ((e10.location[dir1] - locations[i, dir1])/\n                            (e10.location[dir1] - e00.location[dir1]))\n                  else:\n                      w2 = 1.0\n\n                  w1 = _clip01(w1)\n                  w2 = _clip01(w2)\n                  row_data[0] = w1 * w2\n                  row_data[1] = (1 - w1) * w2\n                  row_data[2] = w1 * (1 - w2)\n                  row_data[3] = (1 - w1) * (1 - w2)\n                  row_inds[0] = e00.index + offset\n                  row_inds[1] = e01.index + offset\n                  row_inds[2] = e10.index + offset\n                  row_inds[3] = e11.index + offset\n              else:\n                  # Look dir2 from previous two cells\n                  if (\n                      locations[i, dir2] < cell.location[dir2]\n                      and i00.neighbors[2*dir2] != NULL\n                      and i01.neighbors[2*dir2] != NULL\n                      and i00.neighbors[2*dir2].is_leaf()\n                      and i01.neighbors[2*dir2].is_leaf()\n                      and i00.neighbors[2*dir2].level == i00.level\n                      and i01.neighbors[2*dir2].level == i01.level\n                  ):\n                      i10 = i00\n                      i11 = i01\n                      i00 = i00.neighbors[2*dir2]\n                      i01 = i01.neighbors[2*dir2]\n                  elif (\n                      locations[i, dir2] > cell.location[dir2]\n                      and i00.neighbors[2*dir2 + 1] != NULL\n                      and i01.neighbors[2*dir2 + 1] != NULL\n                      and i00.neighbors[2*dir2 + 1].is_leaf()\n                      and i01.neighbors[2*dir2 + 1].is_leaf()\n                      and i00.neighbors[2*dir2 + 1].level == i00.level\n                      and i01.neighbors[2*dir2 + 1].level == i01.level\n                  ):\n                      i10 = i00.neighbors[2*dir2 + 1]\n                      i11 = i01.neighbors[2*dir2 + 1]\n                  else:\n                      i10 = i00\n                      i11 = i01\n\n                  f000 = i00.faces[dir * 2]\n                  f001 = i00.faces[dir * 2 + 1]\n                  f010 = i01.faces[dir * 2]\n                  f011 = i01.faces[dir * 2 + 1]\n                  f100 = i10.faces[dir * 2]\n                  f101 = i10.faces[dir * 2 + 1]\n                  f110 = i11.faces[dir * 2]\n                  f111 = i11.faces[dir * 2 + 1]\n\n                  w1 = ((f001.location[dir] - locations[i, dir])/\n                        (f001.location[dir] - f000.location[dir]))\n                  if i00.index != i01.index:\n                      w2 = ((f010.location[dir1] - locations[i, dir1])/\n                            (f010.location[dir1] - f000.location[dir1]))\n                  else:\n                      w2 = 1.0\n                  if i10.index != i00.index:\n                      w3 = ((f100.location[dir2] - locations[i, dir2])/\n                            (f100.location[dir2] - f000.location[dir2]))\n                  else:\n                      w3 = 1.0\n\n                  w1 = _clip01(w1)\n                  w2 = _clip01(w2)\n                  w3 = _clip01(w3)\n\n                  row_data[0] = w1 * w2 * w3\n                  row_data[1] = (1 - w1) * w2 * w3\n                  row_data[2] = w1 * (1 - w2) * w3\n                  row_data[3] = (1 - w1) * (1 - w2) * w3\n                  row_data[4] = w1 * w2 * (1 - w3)\n                  row_data[5] = (1 - w1) * w2 * (1 - w3)\n                  row_data[6] = w1 * (1 - w2) * (1 - w3)\n                  row_data[7] = (1 - w1) * (1 - w2) * (1 - w3)\n                  row_inds[0] = f000.index + offset\n                  row_inds[1] = f001.index + offset\n                  row_inds[2] = f010.index + offset\n                  row_inds[3] = f011.index + offset\n                  row_inds[4] = f100.index + offset\n                  row_inds[5] = f101.index + offset\n                  row_inds[6] = f110.index + offset\n                  row_inds[7] = f111.index + offset\n\n        Rf = self._deflate_faces()\n        return sp.csr_matrix((data, indices, indptr), shape=(locs.shape[0], self.n_total_faces))*Rf\n\n    def _getNodeIntMat(self, locs, zeros_outside):\n        cdef:\n            double[:, :] locations = locs\n            int_t dim = self._dim\n            int_t n_loc = locs.shape[0]\n            int_t n_nodes = 1<<dim\n            np.int64_t[:] I = np.empty(n_loc*n_nodes, dtype=np.int64)\n            np.int64_t[:] J = np.empty(n_loc*n_nodes, dtype=np.int64)\n            np.float64_t[:] V = np.empty(n_loc*n_nodes, dtype=np.float64)\n\n            int_t ii, i\n            c_Cell *cell\n            double x, y, z\n            double wx, wy, wz\n            double eps = 100*np.finfo(float).eps\n            int zeros_out = zeros_outside\n\n        for i in range(n_loc):\n            x = locations[i, 0]\n            y = locations[i, 1]\n            z = locations[i, 2] if dim==3 else 0.0\n            #get containing (or closest) cell\n            cell = self.tree.containing_cell(x, y, z)\n            #calculate weights\n            wx = ((cell.points[3].location[0] - x)/\n                  (cell.points[3].location[0] - cell.points[0].location[0]))\n            wy = ((cell.points[3].location[1] - y)/\n                  (cell.points[3].location[1] - cell.points[0].location[1]))\n            if dim == 3:\n                wz = ((cell.points[7].location[2] - z)/\n                      (cell.points[7].location[2] - cell.points[0].location[2]))\n            else:\n                wz = 1.0\n\n\n            I[n_nodes*i:n_nodes*i + n_nodes] = i\n\n\n            if zeros_out:\n                if (wx < -eps or wy < -eps or wz < -eps or\n                    wx>1 + eps or wy > 1 + eps or wz > 1 + eps):\n                    for ii in range(n_nodes):\n                        J[n_nodes*i + ii] = 0\n                        V[n_nodes*i + ii] = 0.0\n                    continue\n\n            wx = _clip01(wx)\n            wy = _clip01(wy)\n            wz = _clip01(wz)\n            for ii in range(n_nodes):\n                J[n_nodes*i + ii] = cell.points[ii].index\n\n            V[n_nodes*i    ] = wx*wy*wz\n            V[n_nodes*i + 1] = (1 - wx)*wy*wz\n            V[n_nodes*i + 2] = wx*(1 - wy)*wz\n            V[n_nodes*i + 3] = (1 - wx)*(1 - wy)*wz\n            if dim==3:\n                V[n_nodes*i + 4] = wx*wy*(1 - wz)\n                V[n_nodes*i + 5] = (1 - wx)*wy*(1 - wz)\n                V[n_nodes*i + 6] = wx*(1 - wy)*(1 - wz)\n                V[n_nodes*i + 7] = (1 - wx)*(1 - wy)*(1 - wz)\n\n        Rn = self._deflate_nodes()\n        return sp.csr_matrix((V, (I, J)), shape=(locs.shape[0],self.n_total_nodes))*Rn\n\n    def _getCellIntMat(self, locs, zeros_outside):\n        cdef:\n            double[:, :] locations = locs\n            int_t dim = self._dim\n            int_t dir0, dir1, dir2, temp\n            int_t n_loc = locations.shape[0]\n            int_t n_cells = 4 if dim == 2 else 8\n            np.int64_t[:] indptr = n_cells * np.arange(n_loc+1, dtype=np.int64)\n            np.int64_t[:] indices = np.empty(n_cells * n_loc, dtype=np.int64)\n            np.float64_t[:] data = np.ones(n_cells * n_loc, dtype=np.float64)\n\n            np.int64_t[:] row_inds\n            np.float64_t[:] row_data\n            np.float64_t w0, w1, w2\n\n            int_t ii, i\n            c_Cell *i000\n            c_Cell *i001\n            c_Cell *i010\n            c_Cell *i011\n            c_Cell *i100\n            c_Cell *i101\n            c_Cell *i110\n            c_Cell *i111\n            c_Cell *cell\n            double x, y, z\n            double eps = 100*np.finfo(float).eps\n            int zeros_out = zeros_outside\n\n        dir0 = 0\n        dir1 = 1\n        dir2 = 2\n\n        for i in range(n_loc):\n            x = locations[i, 0]\n            y = locations[i, 1]\n            z = locations[i, 2] if dim==3 else 0.0\n            # get containing (or closest) cell\n            cell = self.tree.containing_cell(x, y, z)\n            row_inds = indices[indptr[i]:indptr[i + 1]]\n            row_data = data[indptr[i]:indptr[i + 1]]\n            was_outside = False\n            if zeros_out:\n                if x < cell.points[0].location[0]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif x > cell.points[3].location[0]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif y < cell.points[0].location[1]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif y > cell.points[3].location[1]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif dim == 3 and z < cell.points[0].location[2]-eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n                elif dim == 3 and z > cell.points[7].location[2]+eps:\n                    row_data[:] = 0.0\n                    row_inds[:] = 0\n                    was_outside = True\n            if not was_outside:\n                # decide order to search based on distance to each faces\n                #\n                if (\n                    abs(locations[i, dir0] - cell.location[dir0]) <\n                    abs(locations[i, dir1] - cell.location[dir1])\n                ):\n                    temp = dir0\n                    dir0 = dir1\n                    dir1 = temp\n                if dim == 3:\n                    if (\n                        abs(locations[i, dir1] - cell.location[dir1]) <\n                        abs(locations[i, dir2] - cell.location[dir2])\n                    ):\n                        temp = dir1\n                        dir1 = dir2\n                        dir2 = temp\n                    if (\n                        abs(locations[i, dir0] - cell.location[dir0]) <\n                        abs(locations[i, dir1] - cell.location[dir1])\n                    ):\n                        temp = dir0\n                        dir0 = dir1\n                        dir0 = temp\n                # Look -dir0 and +dir0 from current cell\n                if (\n                    locations[i, dir0] < cell.location[dir0]\n                    and cell.neighbors[2 * dir0] != NULL\n                    and cell.neighbors[2 * dir0].is_leaf()\n                    and cell.neighbors[2 * dir0].level == cell.level\n                ):\n                    i000 = cell.neighbors[2 * dir0]\n                    i001 = cell\n                elif (\n                    locations[i, dir0] > cell.location[dir0]\n                    and cell.neighbors[2 * dir0 + 1] != NULL\n                    and cell.neighbors[2 * dir0 + 1].is_leaf()\n                    and cell.neighbors[2 * dir0 + 1].level == cell.level\n                ):\n                    i000 = cell\n                    i001 = cell.neighbors[2 * dir0 + 1]\n                else:\n                    i000 = i001 = cell\n                # Look -y and +y from previous two cells\n                if (\n                    locations[i, dir1] < cell.location[dir1]\n                    and i000.neighbors[2 * dir1] != NULL\n                    and i001.neighbors[2 * dir1] != NULL\n                    and i000.neighbors[2 * dir1].is_leaf()\n                    and i001.neighbors[2 * dir1].is_leaf()\n                    and i000.neighbors[2 * dir1].level == i000.level\n                    and i001.neighbors[2 * dir1].level == i001.level\n                ):\n                    i010 = i000\n                    i011 = i001\n                    i000 = i000.neighbors[2 * dir1]\n                    i001 = i001.neighbors[2 * dir1]\n                elif (\n                    locations[i, dir1] > cell.location[dir1]\n                    and i000.neighbors[2 * dir1 + 1] != NULL\n                    and i001.neighbors[2 * dir1 + 1] != NULL\n                    and i000.neighbors[2 * dir1 + 1].is_leaf()\n                    and i001.neighbors[2 * dir1 + 1].is_leaf()\n                    and i000.neighbors[2 * dir1 + 1].level == i000.level\n                    and i001.neighbors[2 * dir1 + 1].level == i001.level\n                ):\n                    i010 = i000.neighbors[2 * dir1 + 1]\n                    i011 = i001.neighbors[2 * dir1 + 1]\n                else:\n                    i010 = i000\n                    i011 = i001\n                    w1 = 1.0\n                # Look -z and +z from previous four cells\n                if (\n                    dim == 3\n                    and locations[i, dir2] < cell.location[dir2]\n                    and i000.neighbors[2 * dir2] != NULL\n                    and i001.neighbors[2 * dir2] != NULL\n                    and i010.neighbors[2 * dir2] != NULL\n                    and i011.neighbors[2 * dir2] != NULL\n                    and i000.neighbors[2 * dir2].is_leaf()\n                    and i001.neighbors[2 * dir2].is_leaf()\n                    and i010.neighbors[2 * dir2].is_leaf()\n                    and i011.neighbors[2 * dir2].is_leaf()\n                    and i000.neighbors[2 * dir2].level == i000.level\n                    and i001.neighbors[2 * dir2].level == i001.level\n                    and i010.neighbors[2 * dir2].level == i010.level\n                    and i011.neighbors[2 * dir2].level == i011.level\n                ):\n                    i100 = i000\n                    i101 = i001\n                    i110 = i010\n                    i111 = i011\n                    i000 = i000.neighbors[2 * dir2]\n                    i001 = i001.neighbors[2 * dir2]\n                    i010 = i010.neighbors[2 * dir2]\n                    i011 = i011.neighbors[2 * dir2]\n                elif (\n                    dim == 3\n                    and locations[i, dir2] > cell.location[dir2]\n                    and i000.neighbors[2 * dir2 + 1] != NULL\n                    and i001.neighbors[2 * dir2 + 1] != NULL\n                    and i010.neighbors[2 * dir2 + 1] != NULL\n                    and i011.neighbors[2 * dir2 + 1] != NULL\n                    and i000.neighbors[2 * dir2 + 1].is_leaf()\n                    and i001.neighbors[2 * dir2 + 1].is_leaf()\n                    and i010.neighbors[2 * dir2 + 1].is_leaf()\n                    and i011.neighbors[2 * dir2 + 1].is_leaf()\n                    and i000.neighbors[2 * dir2 + 1].level == i000.level\n                    and i001.neighbors[2 * dir2 + 1].level == i001.level\n                    and i010.neighbors[2 * dir2 + 1].level == i010.level\n                    and i011.neighbors[2 * dir2 + 1].level == i011.level\n                ):\n                    i100 = i000.neighbors[2 * dir2 + 1]\n                    i101 = i001.neighbors[2 * dir2 + 1]\n                    i110 = i010.neighbors[2 * dir2 + 1]\n                    i111 = i011.neighbors[2 * dir2 + 1]\n                else:\n                    i100 = i000\n                    i101 = i001\n                    i110 = i010\n                    i111 = i011\n\n                if i001.index != i000.index:\n                    w1 = ((i001.location[dir0] - locations[i, dir0])/\n                          (i001.location[dir0] - i000.location[dir0]))\n                else:\n                   w1 = 1.0\n\n                if i010.index != i000.index:\n                    w2 = ((i010.location[dir1] - locations[i, dir1])/\n                          (i010.location[dir1] - i000.location[dir1]))\n                else:\n                    w2 = 1.0\n\n                if dim == 3 and i100.index != i000.index:\n                    w3 = ((i100.location[dir2] - locations[i, dir2])/\n                          (i100.location[dir2] - i000.location[dir2]))\n                else:\n                    w3 = 1.0\n\n\n                w1 = _clip01(w1)\n                w2 = _clip01(w2)\n                w3 = _clip01(w3)\n                row_data[0] = w1 * w2 * w3\n                row_data[1] = (1 - w1) * w2 * w3\n                row_data[2] = w1 * (1 - w2) * w3\n                row_data[3] = (1 - w1) * (1 - w2) * w3\n                row_inds[0] = i000.index\n                row_inds[1] = i001.index\n                row_inds[2] = i010.index\n                row_inds[3] = i011.index\n                if dim==3:\n                    row_data[4] = w1 * w2 * (1 - w3)\n                    row_data[5] = (1 - w1) * w2 * (1 - w3)\n                    row_data[6] = w1 * (1 - w2) * (1 - w3)\n                    row_data[7] = (1 - w1) * (1 - w2) * (1 - w3)\n                    row_inds[4] = i100.index\n                    row_inds[5] = i101.index\n                    row_inds[6] = i110.index\n                    row_inds[7] = i111.index\n        return sp.csr_matrix((data, indices, indptr), shape=(locs.shape[0],self.n_cells))\n\n    @property\n    def cell_nodes(self):\n        \"\"\"The index of all nodes for each cell.\n\n        These indices point to non-hanging and hanging nodes.\n\n        Returns\n        -------\n        numpy.ndarray of int\n            Index array of shape (n_cells, 4) if 2D, or (n_cells, 8) if 3D\n\n        See also\n        --------\n        TreeMesh.total_nodes\n        \"\"\"\n        cdef int_t npc = 4 if self.dim == 2 else 8\n        inds = np.empty((self.n_cells, npc), dtype=np.int64)\n        cdef np.int64_t[:, :] node_index = inds\n        cdef int_t i\n\n        for cell in self.tree.cells:\n            for i in range(npc):\n                node_index[cell.index, i] = cell.points[i].index\n\n        return inds\n\n    @property\n    def edge_nodes(self):\n        \"\"\"The index of nodes for every edge.\n\n        The index of the nodes at each end of every (including hanging) edge.\n\n        Returns\n        -------\n        (dim) tuple of numpy.ndarray of int\n            One numpy array for each edge type (x, y, (z)) for this mesh.\n\n        Notes\n        -----\n        These arrays will also index into the hanging nodes.\n        \"\"\"\n        inds_x = np.empty((self.n_total_edges_x, 2), dtype=np.int64)\n        inds_y = np.empty((self.n_total_edges_y, 2), dtype=np.int64)\n        cdef np.int64_t[:, :] edge_inds\n\n        edge_inds = inds_x\n        for it in self.tree.edges_x:\n            edge = it.second\n            edge_inds[edge.index, 0] = edge.points[0].index\n            edge_inds[edge.index, 1] = edge.points[1].index\n\n        edge_inds = inds_y\n        for it in self.tree.edges_y:\n            edge = it.second\n            edge_inds[edge.index, 0] = edge.points[0].index\n            edge_inds[edge.index, 1] = edge.points[1].index\n\n        if self.dim == 2:\n            return inds_x, inds_y\n\n        inds_z = np.empty((self.n_total_edges_z, 2), dtype=np.int64)\n        edge_inds = inds_z\n        for it in self.tree.edges_z:\n            edge = it.second\n            edge_inds[edge.index, 0] = edge.points[0].index\n            edge_inds[edge.index, 1] = edge.points[1].index\n\n        return inds_x, inds_y, inds_z\n\n    def __getstate__(self):\n        \"\"\"Get the current state of the TreeMesh.\"\"\"\n        cdef int id, dim = self._dim\n        indArr = np.empty((self.n_cells, dim), dtype=np.int64)\n        levels = np.empty((self.n_cells), dtype=np.int32)\n        cdef np.int64_t[:, :] _indArr = indArr\n        cdef np.int32_t[:] _levels = levels\n        for cell in self.tree.cells:\n            for id in range(dim):\n                _indArr[cell.index, id] = cell.location_ind[id]\n            _levels[cell.index] = cell.level\n        return indArr, levels\n\n    def __setstate__(self, state):\n        \"\"\"Set the current state of the TreeMesh.\"\"\"\n        indArr, levels = state\n        indArr = np.asarray(indArr)\n        levels = np.asarray(levels)\n        xs = np.array(self._xs)\n        ys = np.array(self._ys)\n        if self._dim == 3:\n            zs = np.array(self._zs)\n            points = np.column_stack((xs[indArr[:, 0]],\n                                      ys[indArr[:, 1]],\n                                      zs[indArr[:, 2]]))\n        else:\n            points = np.column_stack((xs[indArr[:, 0]], ys[indArr[:, 1]]))\n        # Set diagonal balance as false. If the state itself came from a diagonally\n        # balanced tree, those cells will naturally be included in the state information\n        # itself (no need to re-enforce that balancing). This then also allows\n        # us to support reading in older TreeMesh that are not diagonally balanced when\n        # we switch the default to be a diagonally balanced tree.\n        self.insert_cells(points, levels, diagonal_balance=False)\n\n    def __getitem__(self, key):\n        \"\"\"Get a TreeCell or cells.\n\n        Each item of the TreeMesh is a TreeCell that contains information\n        about the\n\n        Returns\n        -------\n        discretize.tree_mesh.TreeCell\n        \"\"\"\n        if isinstance(key, slice):\n            # Get the start, stop, and step from the slice\n            return [self[ii] for ii in range(*key.indices(len(self)))]\n        elif isinstance(key, (int, np.integer)):\n            if key < 0:  # Handle negative indices\n                key += len(self)\n            if key >= len(self):\n                raise IndexError(\n                    \"The index ({0:d}) is out of range.\".format(key)\n                )\n            pycell = TreeCell()\n            pycell._set(self.tree.cells[key])\n            return pycell\n        else:\n            raise TypeError(\"Invalid argument type.\")\n\n    @property\n    def _ubc_indArr(self):\n        if self.__ubc_indArr is not None:\n            return self.__ubc_indArr\n        indArr, levels = self.__getstate__()\n\n        max_level = self.tree.max_level\n\n        levels = 1<<(max_level - levels)\n\n        if self.dim == 2:\n            indArr[:, -1] = (self._ys.shape[0]-1) - indArr[:, -1]\n        else:\n            indArr[:, -1] = (self._zs.shape[0]-1) - indArr[:, -1]\n\n        indArr = (indArr - levels[:, None])//2\n        indArr += 1\n\n        self.__ubc_indArr = (indArr, levels)\n        return self.__ubc_indArr\n\n    @property\n    def _ubc_order(self):\n        if self.__ubc_order is not None:\n            return self.__ubc_order\n        indArr, _ = self._ubc_indArr\n        if self.dim == 2:\n            self.__ubc_order = np.lexsort((indArr[:, 0], indArr[:, 1]))\n        else:\n            self.__ubc_order = np.lexsort((indArr[:, 0], indArr[:, 1], indArr[:, 2]))\n        return self.__ubc_order\n\n    def __dealloc__(self):\n        del self.tree\n        del self.wrapper\n\n    @cython.boundscheck(False)\n    @cython.cdivision(True)\n    def _vol_avg_from_tree(self, _TreeMesh meshin, values=None, output=None):\n        # first check if they have the same tensor base, as it makes it a lot easier...\n        cdef int_t same_base\n        try:\n            same_base = (\n                np.allclose(self.nodes_x, meshin.nodes_x)\n                and np.allclose(self.nodes_y, meshin.nodes_y)\n                and (self.dim == 2 or np.allclose(self.nodes_z, meshin.nodes_z))\n            )\n        except ValueError:\n            same_base = False\n        cdef c_Cell * out_cell\n        cdef c_Cell * in_cell\n\n        cdef np.float64_t[:] vals\n        cdef np.float64_t[:] outs\n        cdef int_t build_mat = 1\n\n        if values is not None:\n            vals = values\n            if output is None:\n                output = np.empty(self.n_cells, dtype=np.float64)\n            else:\n                output = np.require(output, dtype=np.float64, requirements=['A', 'W'])\n            output[:] = 0\n            outs = output\n\n            build_mat = 0\n\n        cdef vector[int_t] row_inds, col_inds\n        cdef vector[int_t] indptr\n        cdef vector[double] all_weights\n\n        cdef vector[int_t] *overlapping_cells\n        cdef double *weights\n        cdef double over_lap_vol\n        cdef double x1m[3]\n        cdef double x1p[3]\n        cdef double x2m[3]\n        cdef double x2p[33]\n        cdef double[:] origin = meshin._origin\n        cdef double[:] xF\n        if self.dim == 2:\n            xF = np.array([meshin._xs[-1], meshin._ys[-1]])\n        else:\n            xF = np.array([meshin._xs[-1], meshin._ys[-1], meshin._zs[-1]])\n\n        cdef int_t nnz_counter = 0\n        cdef int_t nnz_row = 0\n        if build_mat:\n            indptr.push_back(0)\n        cdef int_t i, in_cell_ind\n        cdef int_t n_overlap\n        cdef double weight_sum\n        cdef double weight\n        cdef vector[int_t] out_visited\n        cdef int_t n_unvisited\n\n        # easier path if they share the same base:\n        if same_base:\n            if build_mat:\n                all_weights.resize(meshin.n_cells, 0.0)\n                row_inds.resize(meshin.n_cells, 0)\n            out_visited.resize(self.n_cells, 0)\n            for in_cell in meshin.tree.cells:\n                # for each input cell find containing output cell\n                out_cell = self.tree.containing_cell(\n                    in_cell.location[0],\n                    in_cell.location[1],\n                    in_cell.location[2]\n                )\n                # if containing output cell is lower level (larger) than input cell:\n                # contribution is related to difference of levels (aka ratio of volumes)\n                # else:\n                # contribution is 1.0\n                if out_cell.level < in_cell.level:\n                    out_visited[out_cell.index] = 1\n                    weight = in_cell.volume/out_cell.volume\n                    if not build_mat:\n                        outs[out_cell.index] += weight*vals[in_cell.index]\n                    else:\n                        all_weights[in_cell.index] = weight\n                        row_inds[in_cell.index] = out_cell.index\n\n            if build_mat:\n                P = sp.csr_matrix((all_weights, (row_inds, np.arange(meshin.n_cells))),\n                                  shape=(self.n_cells, meshin.n_cells))\n\n                n_unvisited = self.n_cells - np.sum(out_visited)\n                row_inds.resize(n_unvisited, 0)\n                col_inds.resize(n_unvisited, 0)\n            i = 0\n            # assign weights of 1 to unvisited output cells and find their containing cell\n            for out_cell in self.tree.cells:\n                if not out_visited[out_cell.index]:\n                    in_cell = meshin.tree.containing_cell(\n                        out_cell.location[0],\n                        out_cell.location[1],\n                        out_cell.location[2]\n                    )\n\n                    if not build_mat:\n                        outs[out_cell.index] = vals[in_cell.index]\n                    else:\n                        row_inds[i] = out_cell.index\n                        col_inds[i] = in_cell.index\n                        i += 1\n            if build_mat and n_unvisited > 0:\n                P += sp.csr_matrix(\n                    (np.ones(n_unvisited), (row_inds, col_inds)),\n                    shape=(self.n_cells, meshin.n_cells)\n                )\n            if not build_mat:\n                return output\n            return P\n\n\n        cdef int_t last_point_ind = 7 if self._dim==3 else 3\n        for cell in self.tree.cells:\n            for i_d in range(self._dim):\n                x1m[i_d] = min(cell.min_node().location[i_d], xF[i_d])\n                x1p[i_d] = max(cell.max_node().location[i_d], origin[i_d])\n\n            box = geom.Box(self._dim, x1m, x1p)\n            overlapping_cell_inds = meshin.tree.find_cells_geom(box)\n            n_overlap = overlapping_cell_inds.size()\n            weights = <double *> malloc(n_overlap*sizeof(double))\n            i = 0\n            weight_sum = 0.0\n            nnz_row = 0\n            for in_cell_ind in overlapping_cell_inds:\n                in_cell = meshin.tree.cells[in_cell_ind]\n                x2m = in_cell.min_node().location\n                x2p = in_cell.max_node().location\n\n                over_lap_vol = 1.0\n                for i_d in range(self._dim):\n                    if x1m[i_d]< xF[i_d] and x1p[i_d] > origin[i_d]:\n                        over_lap_vol *= min(x1p[i_d], x2p[i_d]) - max(x1m[i_d], x2m[i_d])\n\n                weights[i] = over_lap_vol\n                if build_mat and weights[i] != 0.0:\n                    nnz_row += 1\n                    row_inds.push_back(in_cell_ind)\n\n                weight_sum += weights[i]\n                i += 1\n            if weight_sum > 0:\n                for i in range(n_overlap):\n                    weights[i] /= weight_sum\n                    if build_mat and weights[i] != 0.0:\n                        all_weights.push_back(weights[i])\n\n            if not build_mat:\n                for i in range(n_overlap):\n                    outs[cell.index] += vals[overlapping_cell_inds[i]]*weights[i]\n            else:\n                nnz_counter += nnz_row\n                indptr.push_back(nnz_counter)\n\n            free(weights)\n            overlapping_cell_inds.clear()\n\n        if not build_mat:\n            return output\n        return sp.csr_matrix((all_weights, row_inds, indptr), shape=(self.n_cells, meshin.n_cells))\n\n    @cython.boundscheck(False)\n    @cython.cdivision(True)\n    def _vol_avg_to_tens(self, out_tens_mesh, values=None, output=None):\n        cdef vector[int_t] *overlapping_cells\n        cdef double *weights\n        cdef double over_lap_vol\n        cdef double x1m[3]\n        cdef double x1p[3]\n        cdef double x2m[3]\n        cdef double x2p[3]\n        cdef double[:] origin\n        cdef double[:] xF\n\n        # first check if they have the same tensor base, as it makes it a lot easier...\n        cdef int_t same_base\n        try:\n            same_base = (\n                np.allclose(self.nodes_x, out_tens_mesh.nodes_x)\n                and np.allclose(self.nodes_y, out_tens_mesh.nodes_y)\n                and (self.dim == 2 or np.allclose(self.nodes_z, out_tens_mesh.nodes_z))\n            )\n        except ValueError:\n            same_base = False\n\n        if same_base:\n            in_cell_inds = self.get_containing_cells(out_tens_mesh.cell_centers)\n            # Every cell input cell is gauranteed to be a lower level than the output tenser mesh\n            # therefore all weights a 1.0\n            if values is not None:\n                if output is None:\n                    output = np.empty(out_tens_mesh.n_cells)\n                output[:] = values[in_cell_inds]\n                return output\n            return sp.csr_matrix(\n                (np.ones(out_tens_mesh.n_cells), (np.arange(out_tens_mesh.n_cells), in_cell_inds)),\n                shape=(out_tens_mesh.n_cells, self.n_cells)\n            )\n\n        if self.dim == 2:\n            origin = np.r_[self.origin, 0.0]\n            xF = np.array([self._xs[-1], self._ys[-1], 0.0])\n        else:\n            origin = self._origin\n            xF = np.array([self._xs[-1], self._ys[-1], self._zs[-1]])\n        cdef c_Cell * in_cell\n\n        cdef np.float64_t[:] vals = np.array([])\n        cdef np.float64_t[::1, :, :] outs = np.array([[[]]])\n\n        cdef vector[int_t] row_inds\n        cdef vector[int_t] indptr\n        cdef vector[double] all_weights\n        cdef int_t nnz_row = 0\n        cdef int_t nnz_counter = 0\n\n        cdef double[:] nodes_x = out_tens_mesh.nodes_x\n        cdef double[:] nodes_y = out_tens_mesh.nodes_y\n        cdef double[:] nodes_z = np.array([0.0, 0.0])\n        if self._dim==3:\n            nodes_z = out_tens_mesh.nodes_z\n\n        cdef int_t nx = len(nodes_x)-1\n        cdef int_t ny = len(nodes_y)-1\n        cdef int_t nz = len(nodes_z)-1\n\n        cdef int_t build_mat = 1\n        if values is not None:\n            vals = values\n            if output is None:\n                output = np.empty(out_tens_mesh.n_cells, dtype=np.float64)\n            else:\n                output = np.require(output, dtype=np.float64, requirements=['A', 'W'])\n            output[:] = 0\n            outs = output.reshape((nx, ny, nz), order='F')\n\n            build_mat = 0\n        if build_mat:\n            indptr.push_back(0)\n\n        cdef int_t ix, iy, iz, in_cell_ind, i, i_dim\n        cdef int_t n_overlap\n        cdef double weight_sum\n\n        #for cell in self.tree.cells:\n        for iz in range(nz):\n            x1m[2] = min(nodes_z[iz], xF[2])\n            x1p[2] = max(nodes_z[iz+1], origin[2])\n            for iy in range(ny):\n                x1m[1] = min(nodes_y[iy], xF[1])\n                x1p[1] = max(nodes_y[iy+1], origin[1])\n                for ix in range(nx):\n                    x1m[0] = min(nodes_x[ix], xF[0])\n                    x1p[0] = max(nodes_x[ix+1], origin[0])\n\n                    box = geom.Box(self._dim, x1m, x1p)\n                    overlapping_cell_inds = self.tree.find_cells_geom(box)\n\n                    n_overlap = overlapping_cell_inds.size()\n                    weights = <double *> malloc(n_overlap*sizeof(double))\n                    i = 0\n                    weight_sum = 0.0\n                    nnz_row = 0\n                    for in_cell_ind in overlapping_cell_inds:\n                        in_cell = self.tree.cells[in_cell_ind]\n                        x2m = in_cell.min_node().location\n                        x2p = in_cell.max_node().location\n\n                        over_lap_vol = 1.0\n                        for i_d in range(self._dim):\n                            if x1m[i_d]< xF[i_d] and x1p[i_d] > origin[i_d]:\n                                over_lap_vol *= min(x1p[i_d], x2p[i_d]) - max(x1m[i_d], x2m[i_d])\n\n                        weights[i] = over_lap_vol\n                        if build_mat and weights[i] != 0.0:\n                            nnz_row += 1\n                            row_inds.push_back(in_cell_ind)\n                        weight_sum += weights[i]\n                        i += 1\n\n                    if weight_sum > 0:\n                        for i in range(n_overlap):\n                            weights[i] /= weight_sum\n                            if build_mat and weights[i] != 0.0:\n                                all_weights.push_back(weights[i])\n\n                    if not build_mat:\n                        for i in range(n_overlap):\n                            outs[ix, iy, iz] += vals[overlapping_cell_inds[i]]*weights[i]\n                    else:\n                        nnz_counter += nnz_row\n                        indptr.push_back(nnz_counter)\n\n                    free(weights)\n                    overlapping_cell_inds.clear()\n\n        if not build_mat:\n            return output\n        return sp.csr_matrix((all_weights, row_inds, indptr), shape=(out_tens_mesh.n_cells, self.n_cells))\n\n    @cython.boundscheck(False)\n    @cython.cdivision(True)\n    def _vol_avg_from_tens(self, in_tens_mesh, values=None, output=None):\n        cdef double *weights\n        cdef double over_lap_vol\n        cdef double x1m, x1p, y1m, y1p, z1m, z1p\n        cdef double x2m, x2p, y2m, y2p, z2m, z2p\n        cdef int_t ix, ix1, ix2, iy, iy1, iy2, iz, iz1, iz2\n        cdef double[:] origin = in_tens_mesh.origin\n        cdef double[:] xF\n\n        # first check if they have the same tensor base, as it makes it a lot easier...\n        cdef int_t same_base\n        try:\n            same_base = (\n                np.allclose(self.nodes_x, in_tens_mesh.nodes_x)\n                and np.allclose(self.nodes_y, in_tens_mesh.nodes_y)\n                and (self.dim == 2 or np.allclose(self.nodes_z, in_tens_mesh.nodes_z))\n            )\n        except ValueError:\n            same_base = False\n\n\n        if same_base:\n            out_cell_inds = self.get_containing_cells(in_tens_mesh.cell_centers)\n            ws = in_tens_mesh.cell_volumes/self.cell_volumes[out_cell_inds]\n            if values is not None:\n                if output is None:\n                    output = np.empty(self.n_cells)\n                output[:] = np.bincount(out_cell_inds, ws*values)\n                return output\n            return sp.csr_matrix(\n                (ws, (out_cell_inds, np.arange(in_tens_mesh.n_cells))),\n                shape=(self.n_cells, in_tens_mesh.n_cells)\n            )\n\n\n        cdef np.float64_t[:] nodes_x = in_tens_mesh.nodes_x\n        cdef np.float64_t[:] nodes_y = in_tens_mesh.nodes_y\n        cdef np.float64_t[:] nodes_z = np.array([0.0, 0.0])\n        if self._dim == 3:\n            nodes_z = in_tens_mesh.nodes_z\n        cdef int_t nx = len(nodes_x)-1\n        cdef int_t ny = len(nodes_y)-1\n        cdef int_t nz = len(nodes_z)-1\n\n        cdef double * dx\n        cdef double * dy\n        cdef double * dz\n\n        if self.dim == 2:\n            xF = np.array([nodes_x[-1], nodes_y[-1]])\n        else:\n            xF = np.array([nodes_x[-1], nodes_y[-1], nodes_z[-1]])\n\n        cdef np.float64_t[::1, :, :] vals\n        cdef np.float64_t[:] outs\n\n        cdef int_t build_mat = 1\n        if values is not None:\n            vals = values.reshape((nx, ny, nz), order='F')\n            if output is None:\n                output = np.empty(self.n_cells, dtype=np.float64)\n            else:\n                output = np.require(output, dtype=np.float64, requirements=['A', 'W'])\n            output[:] = 0\n            outs = output\n\n            build_mat = 0\n\n        cdef vector[int_t] row_inds\n        cdef vector[double] all_weights\n        cdef np.int64_t[:] indptr = np.zeros(self.n_cells+1, dtype=np.int64)\n        cdef int_t nnz_counter = 0\n        cdef int_t nnz_row = 0\n\n        cdef int_t nx_overlap, ny_overlap, nz_overlap, n_overlap\n        cdef int_t i\n        cdef double weight_sum\n\n        for cell in self.tree.cells:\n            x1m = min(cell.points[0].location[0], xF[0])\n            y1m = min(cell.points[0].location[1], xF[1])\n\n            x1p = max(cell.points[3].location[0], origin[0])\n            y1p = max(cell.points[3].location[1], origin[1])\n            if self._dim==3:\n                z1m = min(cell.points[0].location[2], xF[2])\n                z1p = max(cell.points[7].location[2], origin[2])\n            # then need to find overlapping cells of TensorMesh...\n            ix1 = max(_bisect_left(nodes_x, x1m) - 1, 0)\n            ix2 = min(_bisect_right(nodes_x, x1p), nx)\n            iy1 = max(_bisect_left(nodes_y, y1m) - 1, 0)\n            iy2 = min(_bisect_right(nodes_y, y1p), ny)\n            if self._dim==3:\n                iz1 = max(_bisect_left(nodes_z, z1m) - 1, 0)\n                iz2 = min(_bisect_right(nodes_z, z1p), nz)\n            else:\n                iz1 = 0\n                iz2 = 1\n            nx_overlap = ix2-ix1\n            ny_overlap = iy2-iy1\n            nz_overlap = iz2-iz1\n            n_overlap = nx_overlap*ny_overlap*nz_overlap\n            weights = <double *> malloc(n_overlap*sizeof(double))\n\n            dx = <double *> malloc(nx_overlap*sizeof(double))\n            for ix in range(ix1, ix2):\n                x2m = nodes_x[ix]\n                x2p = nodes_x[ix+1]\n                if x1m == xF[0] or x1p == origin[0]:\n                    dx[ix-ix1] = 1.0\n                else:\n                    dx[ix-ix1] = min(x1p, x2p) - max(x1m, x2m)\n\n            dy = <double *> malloc(ny_overlap*sizeof(double))\n            for iy in range(iy1, iy2):\n                y2m = nodes_y[iy]\n                y2p = nodes_y[iy+1]\n                if y1m == xF[1] or y1p == origin[1]:\n                    dy[iy-iy1] = 1.0\n                else:\n                    dy[iy-iy1] = min(y1p, y2p) - max(y1m, y2m)\n\n            dz = <double *> malloc(nz_overlap*sizeof(double))\n            for iz in range(iz1, iz2):\n                z2m = nodes_z[iz]\n                z2p = nodes_z[iz+1]\n                if self._dim==3:\n                    if z1m == xF[2] or z1p == origin[2]:\n                        dz[iz-iz1] = 1.0\n                    else:\n                        dz[iz-iz1] = min(z1p, z2p) - max(z1m, z2m)\n                else:\n                    dz[iz-iz1] = 1.0\n\n            i = 0\n            weight_sum = 0.0\n            nnz_row = 0\n            for iz in range(iz1, iz2):\n                for iy in range(iy1, iy2):\n                    for ix in range(ix1, ix2):\n                        in_cell_ind = ix + (iy + iz*ny)*nx\n                        weights[i] = dx[ix-ix1]*dy[iy-iy1]*dz[iz-iz1]\n                        if build_mat and weights[i] != 0.0:\n                            nnz_row += 1\n                            row_inds.push_back(in_cell_ind)\n\n                        weight_sum += weights[i]\n                        i += 1\n\n            for i in range(n_overlap):\n                weights[i] /= weight_sum\n                if build_mat and weights[i] != 0.0:\n                    all_weights.push_back(weights[i])\n\n            if not build_mat:\n                i = 0\n                for iz in range(iz1, iz2):\n                    for iy in range(iy1, iy2):\n                        for ix in range(ix1, ix2):\n                            outs[cell.index] += vals[ix, iy, iz]*weights[i]\n                            i += 1\n            else:\n                nnz_counter += nnz_row\n                indptr[cell.index+1] = nnz_counter\n\n            free(weights)\n            free(dx)\n            free(dy)\n            free(dz)\n\n        if not build_mat:\n            return output\n        return sp.csr_matrix((all_weights, row_inds, indptr), shape=(self.n_cells, in_tens_mesh.n_cells))\n\n    def get_overlapping_cells(self, rectangle):\n        \"\"\"Find the indicis of cells that overlap the given rectangle\n\n        Parameters\n        ----------\n        rectangle: (2 * dim) array_like\n            array ordered ``[x_min, x_max, y_min, y_max, (z_min, z_max)]`` describing\n            the axis aligned rectangle of interest.\n\n        Returns\n        -------\n        list of int\n            The indices of cells which overlap the axis aligned rectangle.\n        \"\"\"\n        return self.get_cells_in_aabb(*rectangle.reshape(self.dim, 2).T)\n\n    def _error_if_not_finalized(self, method: str):\n        \"\"\"\n        Raise error if mesh is not finalized.\n        \"\"\"\n        if not self.finalized:\n            msg = (\n                f\"`{type(self).__name__}.{method}` requires a finalized mesh. \"\n                \"Use the `finalize()` method to finalize it.\"\n            )\n            raise TreeMeshNotFinalizedError(msg)\n\n    def _require_ndarray_with_dim(self, name, arr, ndim=1, dtype=None, requirements=None):\n        \"\"\"Returns an ndarray that has dim along it's last dimension, with ndim dims,\n\n        Parameters\n        ----------\n        name : str\n            name of the parameter for raised error\n        arr : array_like\n        ndim : {1, 2, 3}\n        dtype, optional\n            dtype input to np.requires\n        requirements, optional\n            requirements input to np.requires, defaults to 'C'.\n\n        Returns\n        -------\n        numpy.ndarray\n            validated array\n        \"\"\"\n        if requirements is None:\n            requirements = 'C'\n        if ndim == 1:\n            arr = np.atleast_1d(arr)\n        elif ndim > 1:\n            arr = np.atleast_2d(arr)\n            if ndim == 3 and arr.ndim != 3:\n                arr = arr[None, ...]\n        else:\n            arr = np.asarray(arr)\n        if arr.ndim != ndim:\n            raise ValueError(f\"{name} must have at most {ndim} dimensions.\")\n        if arr.shape[-1] != self.dim:\n            raise ValueError(\n                f\"Expected the last dimension of {name}.shape={arr.shape} to be {self.dim}.\"\n            )\n        return np.require(arr, dtype=dtype, requirements=requirements)\n\ndef _check_first_dim_broadcast(**kwargs):\n    \"\"\"Perform a check to make sure that the first dimensions of the inputs will broadcast.\"\"\"\n    n_items = 1\n    err = False\n    for key, arr in kwargs.items():\n        test_len = arr.shape[0]\n        if test_len != 1:\n            if n_items == 1:\n                n_items = test_len\n            elif test_len != n_items:\n                err = True\n                break\n    if err:\n        message = \"First dimensions of\"\n        for key, arr in kwargs.items():\n            message += f\" {key}: {arr.shape},\"\n        message = message[:-1]\n        message += \" do not broadcast.\"\n        raise ValueError(message)\n    return n_items\n\n\ncdef inline double _clip01(double x) nogil:\n    return min(1, max(x, 0))\n\ncdef inline int _wrap_levels(int l, int max_level):\n    if l < 0:\n        l = (max_level + 1) - (abs(l) % (max_level + 1))\n    return l\n"
  },
  {
    "path": "discretize/_extensions/triplet.h",
    "content": "#ifndef __TRIPLET_H\n#define __TRIPLET_H\n\n#include <cstddef>\n#include <utility>\n#include <functional>\n\ntemplate<class T, class U, class V>\nstruct triplet{\n    T v1{};\n    U v2{};\n    V v3{};\n\n    triplet(){}\n\n    triplet(T first, U second, V third){\n        v1 = first;\n        v2 = second;\n        v3 = third;\n    }\n    bool operator==(const triplet<T, U, V> &other) const{\n        return (v1 == other.v1\n            && v2 == other.v2\n            && v3 == other.v3);\n    }\n    bool operator<(const triplet<T, U, V> &other) const{\n        return ! (\n            (v1 > other.v1)\n            || (v2 > other.v2)\n            || (v3 > other.v3)\n        );\n    }\n};\n\nnamespace std {\n\n  template<class T, class U, class V>\n  struct hash<triplet<T, U, V> >\n  {\n    std::size_t operator()(const triplet<T, U, V>& k) const\n    {\n      using std::hash;\n      // Compute individual hash values for first,\n      // second and third and combine them using XOR\n      // and bit shifting:\n\n      return ((hash<T>()(k.v1)\n               ^ (hash<U>()(k.v2) << 1)) >> 1)\n               ^ (hash<V>()(k.v3) << 1);\n    }\n  };\n\n  template<class T, class U>\n  struct hash<pair<T, U> >\n  {\n    size_t operator()(const pair<T, U>& k) const\n    {\n      using std::hash;\n\n      return ((hash<T>()(k.first)\n               ^ (hash<U>()(k.second) << 1)) >> 1);\n    }\n  };\n}\n#endif\n"
  },
  {
    "path": "discretize/base/__init__.py",
    "content": "\"\"\"\n==================================\nBase Mesh (:mod:`discretize.base`)\n==================================\n.. currentmodule:: discretize.base\n\nThe ``base`` sub-package houses the fundamental classes for all meshes in ``discretize``.\n\nBase Mesh Class\n---------------\n.. autosummary::\n  :toctree: generated/\n\n  BaseMesh\n  BaseRegularMesh\n  BaseRectangularMesh\n  BaseTensorMesh\n\"\"\"\n\nfrom discretize.base.base_mesh import BaseMesh\nfrom discretize.base.base_regular_mesh import BaseRegularMesh, BaseRectangularMesh\nfrom discretize.base.base_tensor_mesh import BaseTensorMesh\n"
  },
  {
    "path": "discretize/base/base_mesh.py",
    "content": "\"\"\"Module for the base ``discretize`` mesh.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as sp\nimport os\nimport json\nfrom scipy.spatial import KDTree\nfrom discretize.utils import is_scalar, mkvc, sdiag, sdinv\nfrom discretize.utils.code_utils import (\n    deprecate_property,\n    deprecate_method,\n    as_array_n_by_dim,\n)\n\n\nclass BaseMesh:\n    \"\"\"\n    Base mesh class for the ``discretize`` package.\n\n    This class contains the basic structure of properties and methods\n    that should be supported on all discretize meshes.\n    \"\"\"\n\n    _aliases = {\n        \"nC\": \"n_cells\",\n        \"nN\": \"n_nodes\",\n        \"nE\": \"n_edges\",\n        \"nF\": \"n_faces\",\n        \"serialize\": \"to_dict\",\n        \"gridCC\": \"cell_centers\",\n        \"gridN\": \"nodes\",\n        \"aveF2CC\": \"average_face_to_cell\",\n        \"aveF2CCV\": \"average_face_to_cell_vector\",\n        \"aveCC2F\": \"average_cell_to_face\",\n        \"aveCCV2F\": \"average_cell_vector_to_face\",\n        \"aveE2CC\": \"average_edge_to_cell\",\n        \"aveE2CCV\": \"average_edge_to_cell_vector\",\n        \"aveN2CC\": \"average_node_to_cell\",\n        \"aveN2E\": \"average_node_to_edge\",\n        \"aveN2F\": \"average_node_to_face\",\n    }\n\n    def __getattr__(self, name):\n        \"\"\"Reimplement get attribute to allow for aliases.\"\"\"\n        if name == \"_aliases\":\n            raise AttributeError\n        name = self._aliases.get(name, name)\n        return super().__getattribute__(name)\n\n    def to_dict(self):\n        \"\"\"Represent the mesh's attributes as a dictionary.\n\n        The dictionary representation of the mesh class necessary to reconstruct the\n        object. This is useful for serialization. All of the attributes returned in this\n        dictionary will be JSON serializable.\n\n        The mesh class is also stored in the dictionary as strings under the\n        `__module__` and `__class__` keys.\n\n        Returns\n        -------\n        dict\n            Dictionary of {attribute: value} for the attributes of this mesh.\n        \"\"\"\n        cls = type(self)\n        out = {\n            \"__module__\": cls.__module__,\n            \"__class__\": cls.__name__,\n        }\n        for item in self._items:\n            attr = getattr(self, item, None)\n            if attr is not None:\n                if isinstance(attr, np.ndarray):\n                    attr = attr.tolist()\n                elif isinstance(attr, tuple):\n                    # change to a list and make sure inner items are not numpy arrays\n                    attr = list(attr)\n                    for i, thing in enumerate(attr):\n                        if isinstance(thing, np.ndarray):\n                            attr[i] = thing.tolist()\n                out[item] = attr\n        return out\n\n    def equals(self, other_mesh):\n        \"\"\"Compare the current mesh with another mesh to determine if they are identical.\n\n        This method compares all the properties of the current mesh to *other_mesh*\n        and determines if both meshes are identical. If so, this function returns\n        a boolean value of *True* . Otherwise, the function returns *False* .\n\n        Parameters\n        ----------\n        other_mesh : discretize.base.BaseMesh\n            An instance of any discretize mesh class.\n\n        Returns\n        -------\n        bool\n            *True* if meshes are identical and *False* otherwise.\n        \"\"\"\n        if type(self) is not type(other_mesh):\n            return False\n        for item in self._items:\n            my_attr = getattr(self, item, None)\n            other_mesh_attr = getattr(other_mesh, item, None)\n            if isinstance(my_attr, np.ndarray):\n                is_equal = np.allclose(my_attr, other_mesh_attr, rtol=0, atol=0)\n            elif isinstance(my_attr, tuple):\n                is_equal = len(my_attr) == len(other_mesh_attr)\n                if is_equal:\n                    for thing1, thing2 in zip(my_attr, other_mesh_attr):\n                        if isinstance(thing1, np.ndarray):\n                            is_equal = np.allclose(thing1, thing2, rtol=0, atol=0)\n                        else:\n                            try:\n                                is_equal = thing1 == thing2\n                            except Exception:\n                                is_equal = False\n                    if not is_equal:\n                        return is_equal\n            else:\n                try:\n                    is_equal = my_attr == other_mesh_attr\n                except Exception:\n                    is_equal = False\n            if not is_equal:\n                return is_equal\n        return is_equal\n\n    def serialize(self):\n        \"\"\"Represent the mesh's attributes as a dictionary.\n\n        An alias for :py:meth:`~.BaseMesh.to_dict`\n\n        See Also\n        --------\n        to_dict\n        \"\"\"\n        return self.to_dict()\n\n    @classmethod\n    def deserialize(cls, items, **kwargs):\n        \"\"\"Create this mesh from a dictionary of attributes.\n\n        Parameters\n        ----------\n        items : dict\n            dictionary of {attribute : value} pairs that will be passed to this class's\n            initialization method as keyword arguments.\n        **kwargs\n            This is used to catch (and ignore) keyword arguments that used to be used.\n        \"\"\"\n        items.pop(\"__module__\", None)\n        items.pop(\"__class__\", None)\n        return cls(**items)\n\n    def save(self, file_name=\"mesh.json\", verbose=False, **kwargs):\n        \"\"\"Save the mesh to json.\n\n        This method is used to save a mesh by writing\n        its properties to a .json file. To load a mesh you have\n        previously saved, see :py:func:`~discretize.utils.load_mesh`.\n\n        Parameters\n        ----------\n        file_name : str, optional\n            File name for saving the mesh properties\n        verbose : bool, optional\n            If *True*, the path of the json file is printed\n        \"\"\"\n        if \"filename\" in kwargs:\n            raise TypeError(\n                \"The filename keyword argument has been removed, please use file_name. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n        f = os.path.abspath(file_name)  # make sure we are working with abs path\n        with open(f, \"w\") as outfile:\n            json.dump(self.to_dict(), outfile)\n\n        if verbose:\n            print(\"Saved {}\".format(f))\n\n        return f\n\n    def copy(self):\n        \"\"\"Make a copy of the current mesh.\n\n        Returns\n        -------\n        type(mesh)\n            A copy of this mesh.\n        \"\"\"\n        cls = type(self)\n        items = self.to_dict()\n        items.pop(\"__module__\", None)\n        items.pop(\"__class__\", None)\n        return cls(**items)\n\n    def validate(self):\n        \"\"\"Return the validation state of the mesh.\n\n        This mesh is valid immediately upon initialization\n\n        Returns\n        -------\n        bool : True\n        \"\"\"\n        return True\n\n    # Counting dim, n_cells, n_nodes, n_edges, n_faces\n    @property\n    def dim(self):\n        \"\"\"The dimension of the mesh (1, 2, or 3).\n\n        The dimension is an integer denoting whether the mesh\n        is 1D, 2D or 3D.\n\n        Returns\n        -------\n        int\n            Dimension of the mesh; i.e. 1, 2 or 3\n        \"\"\"\n        raise NotImplementedError(f\"dim not implemented for {type(self)}\")\n\n    @property\n    def n_cells(self):\n        \"\"\"Total number of cells in the mesh.\n\n        Returns\n        -------\n        int\n            Number of cells in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nC**\n        \"\"\"\n        raise NotImplementedError(f\"n_cells not implemented for {type(self)}\")\n\n    def __len__(self):\n        \"\"\"Total number of cells in the mesh.\n\n        Essentially this is an alias for :py:attr:`~.BaseMesh.n_cells`.\n\n        Returns\n        -------\n        int\n            Number of cells in the mesh\n        \"\"\"\n        return self.n_cells\n\n    @property\n    def n_nodes(self):\n        \"\"\"Total number of nodes in the mesh.\n\n        Returns\n        -------\n        int\n            Number of nodes in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nN**\n        \"\"\"\n        raise NotImplementedError(f\"n_nodes not implemented for {type(self)}\")\n\n    @property\n    def n_edges(self):\n        \"\"\"Total number of edges in the mesh.\n\n        Returns\n        -------\n        int\n            Total number of edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nE**\n        \"\"\"\n        raise NotImplementedError(f\"n_edges not implemented for {type(self)}\")\n\n    @property\n    def n_faces(self):\n        \"\"\"Total number of faces in the mesh.\n\n        Returns\n        -------\n        int\n            Total number of faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nF**\n        \"\"\"\n        raise NotImplementedError(f\"n_faces not implemented for {type(self)}\")\n\n    # grid locations\n    @property\n    def cell_centers(self):\n        \"\"\"Return gridded cell center locations.\n\n        This property returns a numpy array of shape (n_cells, dim)\n        containing gridded cell center locations for all cells in the\n        mesh. The cells are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_cells, dim) numpy.ndarray of float\n            Gridded cell center locations\n\n        Examples\n        --------\n        The following is a 1D example.\n\n        >>> from discretize import TensorMesh\n        >>> hx = np.ones(5)\n        >>> mesh_1D = TensorMesh([hx], '0')\n        >>> mesh_1D.cell_centers\n        array([0.5, 1.5, 2.5, 3.5, 4.5])\n\n        The following is a 3D example.\n\n        >>> hx, hy, hz = np.ones(2), 2*np.ones(2), 3*np.ones(2)\n        >>> mesh_3D = TensorMesh([hx, hy, hz], '000')\n        >>> mesh_3D.cell_centers\n        array([[0.5, 1. , 1.5],\n               [1.5, 1. , 1.5],\n               [0.5, 3. , 1.5],\n               [1.5, 3. , 1.5],\n               [0.5, 1. , 4.5],\n               [1.5, 1. , 4.5],\n               [0.5, 3. , 4.5],\n               [1.5, 3. , 4.5]])\n\n        \"\"\"\n        raise NotImplementedError(f\"cell_centers not implemented for {type(self)}\")\n\n    @property\n    def nodes(self):\n        \"\"\"Return gridded node locations.\n\n        This property returns a numpy array of shape (n_nodes, dim)\n        containing gridded node locations for all nodes in the\n        mesh. The nodes are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_nodes, dim) numpy.ndarray of float\n            Gridded node locations\n\n        Examples\n        --------\n        The following is a 1D example.\n\n        >>> from discretize import TensorMesh\n        >>> hx = np.ones(5)\n        >>> mesh_1D = TensorMesh([hx], '0')\n        >>> mesh_1D.nodes\n        array([0., 1., 2., 3., 4., 5.])\n\n        The following is a 3D example.\n\n        >>> hx, hy, hz = np.ones(2), 2*np.ones(2), 3*np.ones(2)\n        >>> mesh_3D = TensorMesh([hx, hy, hz], '000')\n        >>> mesh_3D.nodes\n        array([[0., 0., 0.],\n               [1., 0., 0.],\n               [2., 0., 0.],\n               [0., 2., 0.],\n               [1., 2., 0.],\n               [2., 2., 0.],\n               [0., 4., 0.],\n               [1., 4., 0.],\n               [2., 4., 0.],\n               [0., 0., 3.],\n               [1., 0., 3.],\n               [2., 0., 3.],\n               [0., 2., 3.],\n               [1., 2., 3.],\n               [2., 2., 3.],\n               [0., 4., 3.],\n               [1., 4., 3.],\n               [2., 4., 3.],\n               [0., 0., 6.],\n               [1., 0., 6.],\n               [2., 0., 6.],\n               [0., 2., 6.],\n               [1., 2., 6.],\n               [2., 2., 6.],\n               [0., 4., 6.],\n               [1., 4., 6.],\n               [2., 4., 6.]])\n\n        \"\"\"\n        raise NotImplementedError(f\"nodes not implemented for {type(self)}\")\n\n    @property\n    def boundary_nodes(self):\n        \"\"\"Boundary node locations.\n\n        This property returns the locations of the nodes on\n        the boundary of the mesh as a numpy array. The shape\n        of the numpy array is the number of boundary nodes by\n        the dimension of the mesh.\n\n        Returns\n        -------\n        (n_boundary_nodes, dim) numpy.ndarray of float\n            Boundary node locations\n        \"\"\"\n        raise NotImplementedError(f\"boundary_nodes not implemented for {type(self)}\")\n\n    @property\n    def faces(self):\n        \"\"\"Gridded face locations.\n\n        This property returns a numpy array of shape (n_faces, dim)\n        containing gridded locations for all faces in the mesh.\n\n        For structued meshes, the first row corresponds to the bottom-front-leftmost x-face.\n        The output array returns the x-faces, then the y-faces, then\n        the z-faces; i.e. *mesh.faces* is equivalent to *np.r_[mesh.faces_x, mesh.faces_y, mesh.face_z]* .\n        For each face type, the locations are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_faces, dim) numpy.ndarray of float\n            Gridded face locations\n\n        Examples\n        --------\n        Here, we provide an example of a minimally staggered curvilinear mesh.\n        In this case, the x and y-faces have normal vectors that are\n        primarily along the x and y-directions, respectively.\n\n        >>> from discretize import CurvilinearMesh\n        >>> from discretize.utils import example_curvilinear_grid, mkvc\n        >>> from matplotlib import pyplot as plt\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"rotate\")\n        >>> mesh1 = CurvilinearMesh([x, y])\n        >>> faces = mesh1.faces\n        >>> x_faces = faces[:mesh1.n_faces_x]\n        >>> y_faces = faces[mesh1.n_faces_x:]\n\n        >>> fig1 = plt.figure(figsize=(5, 5))\n        >>> ax1 = fig1.add_subplot(111)\n        >>> mesh1.plot_grid(ax=ax1)\n        >>> ax1.scatter(x_faces[:, 0], x_faces[:, 1], 30, 'r')\n        >>> ax1.scatter(y_faces[:, 0], y_faces[:, 1], 30, 'g')\n        >>> ax1.legend(['Mesh', 'X-faces', 'Y-faces'], fontsize=16)\n        >>> plt.show()\n\n        Here, we provide an example of a highly irregular curvilinear mesh.\n        In this case, the y-faces are not defined by normal vectors along\n        a particular direction.\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"sphere\")\n        >>> mesh2 = CurvilinearMesh([x, y])\n        >>> faces = mesh2.faces\n        >>> x_faces = faces[:mesh2.n_faces_x]\n        >>> y_faces = faces[mesh2.n_faces_x:]\n\n        >>> fig2 = plt.figure(figsize=(5, 5))\n        >>> ax2 = fig2.add_subplot(111)\n        >>> mesh2.plot_grid(ax=ax2)\n        >>> ax2.scatter(x_faces[:, 0], x_faces[:, 1], 30, 'r')\n        >>> ax2.scatter(y_faces[:, 0], y_faces[:, 1], 30, 'g')\n        >>> ax2.legend(['Mesh', 'X-faces', 'Y-faces'], fontsize=16)\n        >>> plt.show()\n\n        \"\"\"\n        raise NotImplementedError(f\"faces not implemented for {type(self)}\")\n\n    @property\n    def boundary_faces(self):\n        \"\"\"Boundary face locations.\n\n        This property returns the locations of the faces on\n        the boundary of the mesh as a numpy array. The shape\n        of the numpy array is the number of boundary faces by\n        the dimension of the mesh.\n\n        Returns\n        -------\n        (n_boundary_faces, dim) numpy.ndarray of float\n            Boundary faces locations\n        \"\"\"\n        raise NotImplementedError(f\"boundary_faces not implemented for {type(self)}\")\n\n    @property\n    def edges(self):\n        \"\"\"Gridded edge locations.\n\n        This property returns a numpy array of shape (n_edges, dim)\n        containing gridded locations for all edges in the mesh.\n\n        For structured meshes, the first row corresponds to the bottom-front-leftmost x-edge.\n        The output array returns the x-edges, then the y-edges, then\n        the z-edges; i.e. *mesh.edges* is equivalent to *np.r_[mesh.edges_x, mesh.edges_y, mesh.edges_z]* .\n        For each edge type, the locations are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_edges, dim) numpy.ndarray of float\n            Gridded edge locations\n\n        Examples\n        --------\n        Here, we provide an example of a minimally staggered curvilinear mesh.\n        In this case, the x and y-edges have normal vectors that are\n        primarily along the x and y-directions, respectively.\n\n        >>> from discretize import CurvilinearMesh\n        >>> from discretize.utils import example_curvilinear_grid, mkvc\n        >>> from matplotlib import pyplot as plt\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"rotate\")\n        >>> mesh1 = CurvilinearMesh([x, y])\n        >>> edges = mesh1.edges\n        >>> x_edges = edges[:mesh1.n_edges_x]\n        >>> y_edges = edges[mesh1.n_edges_x:]\n\n        >>> fig1 = plt.figure(figsize=(5, 5))\n        >>> ax1 = fig1.add_subplot(111)\n        >>> mesh1.plot_grid(ax=ax1)\n        >>> ax1.scatter(x_edges[:, 0], x_edges[:, 1], 30, 'r')\n        >>> ax1.scatter(y_edges[:, 0], y_edges[:, 1], 30, 'g')\n        >>> ax1.legend(['Mesh', 'X-edges', 'Y-edges'], fontsize=16)\n        >>> plt.show()\n\n        Here, we provide an example of a highly irregular curvilinear mesh.\n        In this case, the y-edges are not defined by normal vectors along\n        a particular direction.\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"sphere\")\n        >>> mesh2 = CurvilinearMesh([x, y])\n        >>> edges = mesh2.edges\n        >>> x_edges = edges[:mesh2.n_edges_x]\n        >>> y_edges = edges[mesh2.n_edges_x:]\n\n        >>> fig2 = plt.figure(figsize=(5, 5))\n        >>> ax2 = fig2.add_subplot(111)\n        >>> mesh2.plot_grid(ax=ax2)\n        >>> ax2.scatter(x_edges[:, 0], x_edges[:, 1], 30, 'r')\n        >>> ax2.scatter(y_edges[:, 0], y_edges[:, 1], 30, 'g')\n        >>> ax2.legend(['Mesh', 'X-edges', 'Y-edges'], fontsize=16)\n        >>> plt.show()\n\n        \"\"\"\n        raise NotImplementedError(f\"edges not implemented for {type(self)}\")\n\n    @property\n    def boundary_edges(self):\n        \"\"\"Boundary edge locations.\n\n        This property returns the locations of the edges on\n        the boundary of the mesh as a numpy array. The shape\n        of the numpy array is the number of boundary edges by\n        the dimension of the mesh.\n\n        Returns\n        -------\n        (n_boundary_edges, dim) numpy.ndarray of float\n            Boundary edge locations\n        \"\"\"\n        raise NotImplementedError(f\"boundary_edges not implemented for {type(self)}\")\n\n    # unit directions\n\n    @property\n    def face_normals(self):\n        \"\"\"Unit normal vectors for all mesh faces.\n\n        The unit normal vector defines the direction that\n        is perpendicular to a surface. Calling\n        *face_normals* returns a numpy.ndarray containing\n        the unit normal vectors for all faces in the mesh.\n        For a 3D mesh, the array would have shape (n_faces, dim).\n        The rows of the output are organized by x-faces,\n        then y-faces, then z-faces vectors.\n\n        Returns\n        -------\n        (n_faces, dim) numpy.ndarray of float\n            Unit normal vectors for all mesh faces\n        \"\"\"\n        raise NotImplementedError(f\"face_normals not implemented for {type(self)}\")\n\n    @property\n    def edge_tangents(self):\n        \"\"\"Unit tangent vectors for all mesh edges.\n\n        For a given edge, the unit tangent vector defines the\n        path direction one would take if traveling along that edge.\n        Calling *edge_tangents* returns a numpy.ndarray containing\n        the unit tangent vectors for all edges in the mesh.\n        For a 3D mesh, the array would have shape (n_edges, dim).\n        The rows of the output are organized by x-edges,\n        then y-edges, then z-edges vectors.\n\n        Returns\n        -------\n        (n_edges, dim) numpy.ndarray of float\n            Unit tangent vectors for all mesh edges\n        \"\"\"\n        raise NotImplementedError(f\"edge_tangents not implemented for {type(self)}\")\n\n    @property\n    def boundary_face_outward_normals(self):\n        \"\"\"Outward normal vectors of boundary faces.\n\n        This property returns the outward normal vectors of faces\n        the boundary of the mesh as a numpy array. The shape\n        of the numpy array is the number of boundary faces by\n        the dimension of the mesh.\n\n        Returns\n        -------\n        (n_boundary_faces, dim) numpy.ndarray of float\n            Outward normal vectors of boundary faces\n        \"\"\"\n        raise NotImplementedError(\n            f\"boundary_face_outward_normals not implemented for {type(self)}\"\n        )\n\n    def project_face_vector(self, face_vectors):\n        \"\"\"Project vectors onto the faces of the mesh.\n\n        Consider a numpy array *face_vectors* whose rows provide\n        a vector for each face in the mesh. For each face,\n        *project_face_vector* computes the dot product between\n        a vector and the corresponding face's unit normal vector.\n        That is, *project_face_vector* projects the vectors\n        in *face_vectors* to the faces of the mesh.\n\n        Parameters\n        ----------\n        face_vectors : (n_faces, dim) numpy.ndarray\n            Numpy array containing the vectors that will be projected to the mesh faces\n\n        Returns\n        -------\n        (n_faces) numpy.ndarray of float\n            Dot product between each vector and the unit normal vector of the corresponding face\n        \"\"\"\n        if not isinstance(face_vectors, np.ndarray):\n            raise Exception(\"face_vectors must be an ndarray\")\n        if not (\n            len(face_vectors.shape) == 2\n            and face_vectors.shape[0] == self.n_faces\n            and face_vectors.shape[1] == self.dim\n        ):\n            raise Exception(\"face_vectors must be an ndarray of shape (n_faces, dim)\")\n        return np.sum(face_vectors * self.face_normals, 1)\n\n    def project_edge_vector(self, edge_vectors):\n        \"\"\"Project vectors to the edges of the mesh.\n\n        Consider a numpy array *edge_vectors* whose rows provide\n        a vector for each edge in the mesh. For each edge,\n        *project_edge_vector* computes the dot product between\n        a vector and the corresponding edge's unit tangent vector.\n        That is, *project_edge_vector* projects the vectors\n        in *edge_vectors* to the edges of the mesh.\n\n        Parameters\n        ----------\n        edge_vectors : (n_edges, dim) numpy.ndarray\n            Numpy array containing the vectors that will be projected to the mesh edges\n\n        Returns\n        -------\n        (n_edges) numpy.ndarray of float\n            Dot product between each vector and the unit tangent vector of the corresponding edge\n        \"\"\"\n        if not isinstance(edge_vectors, np.ndarray):\n            raise Exception(\"edge_vectors must be an ndarray\")\n        if not (\n            len(edge_vectors.shape) == 2\n            and edge_vectors.shape[0] == self.n_edges\n            and edge_vectors.shape[1] == self.dim\n        ):\n            raise Exception(\"edge_vectors must be an ndarray of shape (nE, dim)\")\n        return np.sum(edge_vectors * self.edge_tangents, 1)\n\n    # Mesh properties\n    @property\n    def cell_volumes(self):\n        \"\"\"Return cell volumes.\n\n        Calling this property will compute and return a 1D array\n        containing the volumes of mesh cells.\n\n        Returns\n        -------\n        (n_cells) numpy.ndarray\n            The quantity returned depends on the dimensions of the mesh:\n\n                - *1D:* Returns the cell widths\n                - *2D:* Returns the cell areas\n                - *3D:* Returns the cell volumes\n        \"\"\"\n        raise NotImplementedError(f\"cell_volumes not implemented for {type(self)}\")\n\n    @property\n    def face_areas(self):\n        \"\"\"Return areas of all faces in the mesh.\n\n        Calling this property will compute and return the areas of all\n        faces as a 1D numpy array. For structured meshes, the returned quantity is\n        ordered x-face areas, then y-face areas, then z-face areas.\n\n        Returns\n        -------\n        (n_faces) numpy.ndarray\n            The length of the quantity returned depends on the dimensions of the mesh:\n\n            - *1D:* returns the x-face areas\n            - *2D:* returns the x-face and y-face areas in order; i.e. y-edge\n              and x-edge lengths, respectively\n            - *3D:* returns the x, y and z-face areas in order\n        \"\"\"\n        raise NotImplementedError(f\"face_areas not implemented for {type(self)}\")\n\n    @property\n    def edge_lengths(self):\n        \"\"\"Return lengths of all edges in the mesh.\n\n        Calling this property will compute and return the lengths of all\n        edges in the mesh. For structured meshes, the returned quantity is ordered\n        x-edge lengths, then y-edge lengths, then z-edge lengths.\n\n        Returns\n        -------\n        (n_edges) numpy.ndarray\n            The length of the quantity returned depends on the dimensions of the mesh:\n\n            - *1D:* returns the x-edge lengths\n            - *2D:* returns the x-edge and y-edge lengths in order\n            - *3D:* returns the x, y and z-edge lengths in order\n        \"\"\"\n        raise NotImplementedError(f\"edge_lengths not implemented for {type(self)}\")\n\n    # Differential Operators\n    @property\n    def face_divergence(self):\n        r\"\"\"Face divergence operator (faces to cell-centres).\n\n        This property constructs the 2nd order numerical divergence operator\n        that maps from faces to cell centers. The operator is a sparse matrix\n        :math:`\\mathbf{D_f}` that can be applied as a matrix-vector product to\n        a discrete vector :math:`\\mathbf{u}` that lives on mesh faces; i.e.::\n\n            div_u = Df @ u\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n        *See notes for additional details.*\n\n        Returns\n        -------\n        (n_cells, n_faces) scipy.sparse.csr_matrix\n            The numerical divergence operator from faces to cell centers\n\n        Notes\n        -----\n        In continuous space, the divergence operator is defined as:\n\n        .. math::\n            \\phi = \\nabla \\cdot \\vec{u} = \\frac{\\partial u_x}{\\partial x}\n            + \\frac{\\partial u_y}{\\partial y} + \\frac{\\partial u_z}{\\partial z}\n\n        Where :math:`\\mathbf{u}` is the discrete representation of the continuous variable\n        :math:`\\vec{u}` on cell faces and :math:`\\boldsymbol{\\phi}` is the discrete\n        representation of :math:`\\phi` at cell centers, **face_divergence** constructs a\n        discrete linear operator :math:`\\mathbf{D_f}` such that:\n\n        .. math::\n            \\boldsymbol{\\phi} = \\mathbf{D_f \\, u}\n\n        For each cell, the computation of the face divergence can be expressed\n        according to the integral form below. For cell :math:`i` whose corresponding\n        faces are indexed as a subset :math:`K` from the set of all mesh faces:\n\n        .. math::\n            \\phi_i = \\frac{1}{V_i} \\sum_{k \\in K} A_k \\, \\vec{u}_k \\cdot \\hat{n}_k\n\n        where :math:`V_i` is the volume of cell :math:`i`, :math:`A_k` is\n        the surface area of face *k*, :math:`\\vec{u}_k` is the value of\n        :math:`\\vec{u}` on face *k*, and :math:`\\hat{n}_k`\n        represents the outward normal vector of face *k* for cell *i*.\n\n\n        Examples\n        --------\n        Below, we demonstrate 1) how to apply the face divergence operator to\n        a discrete vector and 2) the mapping of the face divergence operator and\n        its sparsity. Our example is carried out on a 2D mesh but it can\n        be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        Define a 2D mesh\n\n        >>> h = np.ones(20)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete vector on mesh faces\n\n        >>> faces_x = mesh.faces_x\n        >>> faces_y = mesh.faces_y\n        >>> ux = (faces_x[:, 0] / np.sqrt(np.sum(faces_x ** 2, axis=1))) * np.exp(\n        ...     -(faces_x[:, 0] ** 2 + faces_x[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> uy = (faces_y[:, 1] / np.sqrt(np.sum(faces_y ** 2, axis=1))) * np.exp(\n        ...     -(faces_y[:, 0] ** 2 + faces_y[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> u = np.r_[ux, uy]\n\n        Construct the divergence operator and apply to face-vector\n\n        >>> Df = mesh.face_divergence\n        >>> div_u = Df @ u\n\n        Plot the original face-vector and its divergence\n\n        >>> fig = plt.figure(figsize=(13, 6))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(\n        ...     u, ax=ax1, v_type=\"F\", view=\"vec\", stream_opts={\"color\": \"w\", \"density\": 1.0}\n        ... )\n        >>> ax1.set_title(\"Vector at cell faces\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(div_u, ax=ax2)\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Divergence at cell centers\", fontsize=14)\n        >>> plt.show()\n\n        The discrete divergence operator is a sparse matrix that maps\n        from faces to cell centers. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements in\n        the original discrete quantity :math:`\\mathbf{u}` and its\n        discrete divergence :math:`\\boldsymbol{\\phi}` as well as a\n        spy plot.\n\n        >>> mesh = TensorMesh([[(1, 6)], [(1, 3)]])\n        >>> fig = plt.figure(figsize=(10, 10))\n        >>> ax1 = fig.add_subplot(211)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.plot(\n        ...     mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8\n        ... )\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0]+0.05, loc[1]+0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.plot(\n        ...     mesh.faces_x[:, 0], mesh.faces_x[:, 1], \"g>\", markersize=8\n        ... )\n        >>> for ii, loc in zip(range(mesh.nFx), mesh.faces_x):\n        ...     ax1.text(loc[0]+0.05, loc[1]+0.02, \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.plot(\n        ...     mesh.faces_y[:, 0], mesh.faces_y[:, 1], \"g^\", markersize=8\n        ... )\n        >>> for ii, loc in zip(range(mesh.nFy), mesh.faces_y):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.1, \"{0:d}\".format((ii + mesh.nFx)), color=\"g\")\n\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.set_title(\"Mapping of Face Divergence\", fontsize=14, pad=15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (centers)', r'$\\mathbf{u}$ (faces)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(212)\n        >>> ax2.spy(mesh.face_divergence)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(f\"face_divergence not implemented for {type(self)}\")\n\n    @property\n    def nodal_gradient(self):\n        r\"\"\"Nodal gradient operator (nodes to edges).\n\n        This property constructs the 2nd order numerical gradient operator\n        that maps from nodes to edges. The operator is a sparse matrix\n        :math:`\\mathbf{G_n}` that can be applied as a matrix-vector product\n        to a discrete scalar quantity :math:`\\boldsymbol{\\phi}` that\n        lives on the nodes, i.e.::\n\n            grad_phi = Gn @ phi\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n\n        Returns\n        -------\n        (n_edges, n_nodes) scipy.sparse.csr_matrix\n            The numerical gradient operator from nodes to edges\n\n        Notes\n        -----\n        In continuous space, the gradient operator is defined as:\n\n        .. math::\n            \\vec{u} = \\nabla \\phi = \\frac{\\partial \\phi}{\\partial x}\\hat{x}\n            + \\frac{\\partial \\phi}{\\partial y}\\hat{y}\n            + \\frac{\\partial \\phi}{\\partial z}\\hat{z}\n\n        Where :math:`\\boldsymbol{\\phi}` is the discrete representation of the continuous variable\n        :math:`\\phi` on the nodes and :math:`\\mathbf{u}` is the discrete\n        representation of :math:`\\vec{u}` on the edges, **nodal_gradient** constructs a\n        discrete linear operator :math:`\\mathbf{G_n}` such that:\n\n        .. math::\n            \\mathbf{u} = \\mathbf{G_n} \\, \\boldsymbol{\\phi}\n\n        The Cartesian components of :math:`\\vec{u}` are defined on their corresponding\n        edges (x, y or z) as follows; e.g. the x-component of the gradient is defined\n        on x-edges. For edge :math:`i` which defines a straight path\n        of length :math:`h_i` between adjacent nodes :math:`n_1` and :math:`n_2`:\n\n        .. math::\n            u_i = \\frac{\\phi_{n_2} - \\phi_{n_1}}{h_i}\n\n        Note that :math:`u_i \\in \\mathbf{u}` may correspond to a value on an\n        x, y or z edge. See the example below.\n\n        Examples\n        --------\n        Below, we demonstrate 1) how to apply the nodal gradient operator to\n        a discrete scalar quantity, and 2) the mapping of the nodal gradient\n        operator and its sparsity. Our example is carried out on a 2D mesh\n        but it can be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        For a discrete scalar quantity defined on the nodes, we take the\n        gradient by constructing the gradient operator and multiplying\n        as a matrix-vector product.\n\n        Create a uniform grid\n\n        >>> h = np.ones(20)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete scalar on nodes\n\n        >>> nodes = mesh.nodes\n        >>> phi = np.exp(-(nodes[:, 0] ** 2 + nodes[:, 1] ** 2) / 4 ** 2)\n\n        Construct the gradient operator and apply to vector\n\n        >>> Gn = mesh.nodal_gradient\n        >>> grad_phi = Gn @ phi\n\n        Plot the original function and the gradient\n\n        >>> fig = plt.figure(figsize=(13, 6))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi, v_type=\"N\", ax=ax1)\n        >>> ax1.set_title(\"Scalar at nodes\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(\n        ...     grad_phi, ax=ax2, v_type=\"E\", view=\"vec\",\n        ...     stream_opts={\"color\": \"w\", \"density\": 1.0}\n        ... )\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Gradient at edges\", fontsize=14)\n        >>> plt.show()\n\n        The nodal gradient operator is a sparse matrix that maps\n        from nodes to edges. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements in\n        the original discrete quantity :math:`\\boldsymbol{\\phi}` and its\n        discrete gradient as well as a spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 6)]])\n        >>> fig = plt.figure(figsize=(12, 10))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.set_title(\"Mapping of Gradient Operator\", fontsize=14, pad=15)\n        >>> ax1.plot(mesh.nodes[:, 0], mesh.nodes[:, 1], \"ro\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nN), mesh.nodes):\n        >>>     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n\n        >>> ax1.plot(mesh.edges_x[:, 0], mesh.edges_x[:, 1], \"g>\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nEx), mesh.edges_x):\n        >>>     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"g\")\n\n        >>> ax1.plot(mesh.edges_y[:, 0], mesh.edges_y[:, 1], \"g^\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nEy), mesh.edges_y):\n        >>>     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format((ii + mesh.nEx)), color=\"g\")\n\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.legend(\n        >>>     ['Mesh', r'$\\mathbf{\\phi}$ (nodes)', r'$\\mathbf{u}$ (edges)'],\n        >>>     loc='upper right', fontsize=14\n        >>> )\n        >>> ax2 = fig.add_subplot(122)\n        >>> ax2.spy(mesh.nodal_gradient)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Edge Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Node Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(f\"nodal_gradient not implemented for {type(self)}\")\n\n    @property\n    def edge_curl(self):\n        r\"\"\"Edge curl operator (edges to faces).\n\n        This property constructs the 2nd order numerical curl operator\n        that maps from edges to faces. The operator is a sparse matrix\n        :math:`\\mathbf{C_e}` that can be applied as a matrix-vector product\n        to a discrete vector quantity **u** that lives\n        on the edges; i.e.::\n\n            curl_u = Ce @ u\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n\n        Returns\n        -------\n        (n_faces, n_edges) scipy.sparse.csr_matrix\n            The numerical curl operator from edges to faces\n\n        Notes\n        -----\n        In continuous space, the curl operator is defined as:\n\n        .. math::\n            \\vec{w} = \\nabla \\times \\vec{u} =\n            \\begin{vmatrix}\n            \\hat{x} & \\hat{y} & \\hat{z} \\\\\n            \\partial_x & \\partial_y & \\partial_z \\\\\n            u_x & u_y & u_z\n            \\end{vmatrix}\n\n        Where :math:`\\mathbf{u}` is the discrete representation of the continuous variable\n        :math:`\\vec{u}` on cell edges and :math:`\\mathbf{w}` is the discrete\n        representation of the curl on the faces, **edge_curl** constructs a\n        discrete linear operator :math:`\\mathbf{C_e}` such that:\n\n        .. math::\n            \\mathbf{w} = \\mathbf{C_e \\, u}\n\n        The computation of the curl on mesh faces can be expressed\n        according to the integral form below. For face :math:`i` bordered by\n        a set of edges indexed by subset :math:`K`:\n\n        .. math::\n            w_i = \\frac{1}{A_i} \\sum_{k \\in K} \\vec{u}_k \\cdot \\vec{\\ell}_k\n\n        where :math:`A_i` is the surface area of face *i*,\n        :math:`u_k` is the value of :math:`\\vec{u}` on face *k*,\n        and \\vec{\\ell}_k is the path along edge *k*.\n\n        Examples\n        --------\n        Below, we demonstrate the mapping and sparsity of the edge curl\n        for a 3D tensor mesh. We choose a the index for a single face,\n        and illustrate which edges are used to compute the curl on that\n        face.\n\n        >>> from discretize import TensorMesh\n        >>> from discretize.utils import mkvc\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> import matplotlib as mpl\n        >>> import mpl_toolkits.mplot3d as mp3d\n        >>> mpl.rcParams.update({'font.size': 14})\n\n        Create a simple tensor mesh, and grab the **edge_curl** operator:\n\n        >>> mesh = TensorMesh([[(1, 2)], [(1, 2)], [(1, 2)]])\n        >>> Ce = mesh.edge_curl\n\n        Then we choose a *face* for illustration purposes:\n\n        >>> face_ind = 2  # Index of a face in the mesh (could be x, y or z)\n        >>> edge_ind = np.where(\n        ...     np.sum((mesh.edges-mesh.faces[face_ind, :])**2, axis=1) <= 0.5 + 1e-6\n        ... )[0]\n\n        >>> face = mesh.faces[face_ind, :]\n        >>> face_norm = mesh.face_normals[face_ind, :]\n        >>> edges = mesh.edges[edge_ind, :]\n        >>> edge_tan = mesh.edge_tangents[edge_ind, :]\n        >>> node = np.min(edges, axis=0)\n\n        >>> min_edges = np.min(edges, axis=0)\n        >>> max_edges = np.max(edges, axis=0)\n        >>> if face_norm[0] == 1:\n        ...     k = (edges[:, 1] == min_edges[1]) | (edges[:, 2] == max_edges[2])\n        ...     poly = node + np.c_[np.r_[0, 0, 0, 0], np.r_[0, 1, 1, 0], np.r_[0, 0, 1, 1]]\n        ...     ds = [0.07, -0.07, -0.07]\n        ... elif face_norm[1] == 1:\n        ...     k = (edges[:, 0] == max_edges[0]) | (edges[:, 2] == min_edges[2])\n        ...     poly = node + np.c_[np.r_[0, 1, 1, 0], np.r_[0, 0, 0, 0], np.r_[0, 0, 1, 1]]\n        ...     ds = [0.07, -0.09, -0.07]\n        ... elif face_norm[2] == 1:\n        ...     k = (edges[:, 0] == min_edges[0]) | (edges[:, 1] == max_edges[1])\n        ...     poly = node + np.c_[np.r_[0, 1, 1, 0], np.r_[0, 0, 1, 1], np.r_[0, 0, 0, 0]]\n        ...     ds = [0.07, -0.09, -0.07]\n        >>> edge_tan[k, :] *= -1\n\n        Plot the curve and its mapping for a single face.\n\n        >>> fig = plt.figure(figsize=(10, 15))\n        >>> ax1 = fig.add_axes([0, 0.35, 1, 0.6], projection='3d', elev=25, azim=-60)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.plot(\n        ...     mesh.edges[edge_ind, 0], mesh.edges[edge_ind, 1], mesh.edges[edge_ind, 2],\n        ...     \"go\", markersize=10\n        ... )\n        >>> ax1.plot(\n        ...     mesh.faces[face_ind, 0], mesh.faces[face_ind, 1], mesh.faces[face_ind, 2],\n        ...     \"rs\", markersize=10\n        ... )\n        >>> poly = mp3d.art3d.Poly3DCollection(\n        ...     [poly], alpha=0.1, facecolor='r', linewidth=None\n        ... )\n        >>> ax1.add_collection(poly)\n        >>> ax1.quiver(\n        ...     edges[:, 0], edges[:, 1], edges[:, 2],\n        ...     0.5*edge_tan[:, 0], 0.5*edge_tan[:, 1], 0.5*edge_tan[:, 2],\n        ...     edgecolor='g', pivot='middle', linewidth=4, arrow_length_ratio=0.25\n        ... )\n        >>> ax1.text(face[0]+ds[0], face[1]+ds[1], face[2]+ds[2], \"{0:d}\".format(face_ind), color=\"r\")\n        >>> for ii, loc in zip(range(len(edge_ind)), edges):\n        ...     ax1.text(loc[0]+ds[0], loc[1]+ds[1], loc[2]+ds[2], \"{0:d}\".format(edge_ind[ii]), color=\"g\")\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{u}$ (edges)', r'$\\mathbf{w}$ (face)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n\n        Manually make axis properties invisible\n\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.set_zticks([])\n        >>> ax1.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n        >>> ax1.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n        >>> ax1.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n        >>> ax1.xaxis.line.set_color((1.0, 1.0, 1.0, 0.0))\n        >>> ax1.yaxis.line.set_color((1.0, 1.0, 1.0, 0.0))\n        >>> ax1.zaxis.line.set_color((1.0, 1.0, 1.0, 0.0))\n        >>> ax1.set_xlabel('X', labelpad=-15, fontsize=16)\n        >>> ax1.set_ylabel('Y', labelpad=-20, fontsize=16)\n        >>> ax1.set_zlabel('Z', labelpad=-20, fontsize=16)\n        >>> ax1.set_title(\"Mapping for a Single Face\", fontsize=16, pad=-15)\n\n        >>> ax2 = fig.add_axes([0.05, 0.05, 0.9, 0.3])\n        >>> ax2.spy(Ce)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=16, pad=5)\n        >>> ax2.set_ylabel(\"Face Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Edge Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(f\"edge_curl not implemented for {type(self)}\")\n\n    @property\n    def boundary_face_scalar_integral(self):\n        r\"\"\"Represent the operation of integrating a scalar function on the boundary.\n\n        This matrix represents the boundary surface integral of a scalar function\n        multiplied with a finite volume test function on the mesh.\n\n        Returns\n        -------\n        (n_faces, n_boundary_faces) scipy.sparse.csr_matrix\n\n        Notes\n        -----\n        The integral we are representing on the boundary of the mesh is\n\n        .. math:: \\int_{\\Omega} u\\vec{w} \\cdot \\hat{n} \\partial \\Omega\n\n        In discrete form this is:\n\n        .. math:: w^T * P @ u_b\n\n        where `w` is defined on all faces, and `u_b` is defined on boundary faces.\n        \"\"\"\n        raise NotImplementedError(\n            f\"boundary_face_scalar_integral not implemented for {type(self)}\"\n        )\n\n    @property\n    def boundary_edge_vector_integral(self):\n        r\"\"\"Represent the operation of integrating a vector function on the boundary.\n\n        This matrix represents the boundary surface integral of a vector function\n        multiplied with a finite volume test function on the mesh.\n\n        In 1D and 2D, the operation assumes that the right array contains only a single\n        component of the vector ``u``. In 3D, however, we must assume that ``u`` will\n        contain each of the three vector components, and it must be ordered as,\n        ``[edges_1_x, ... ,edge_N_x, edge_1_y, ..., edge_N_y, edge_1_z, ..., edge_N_z]``\n        , where ``N`` is the number of boundary edges.\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            Sparse matrix of shape (n_edges, n_boundary_edges) for 1D or 2D mesh,\n            (n_edges, 3*n_boundary_edges) for a 3D mesh.\n\n        Notes\n        -----\n        The integral we are representing on the boundary of the mesh is\n\n        .. math:: \\int_{\\Omega} \\vec{w} \\cdot (\\vec{u} \\times \\hat{n}) \\partial \\Omega\n\n        In discrete form this is:\n\n        .. math:: w^T * P @ u_b\n\n        where `w` is defined on all edges, and `u_b` is all three components defined on\n        boundary edges.\n        \"\"\"\n        raise NotImplementedError(\n            f\"boundary_edge_vector_integral not implemented for {type(self)}\"\n        )\n\n    @property\n    def boundary_node_vector_integral(self):\n        r\"\"\"Represent the operation of integrating a vector function dotted with the boundary normal.\n\n        This matrix represents the boundary surface integral of a vector function\n        dotted with the boundary normal and multiplied with a scalar finite volume\n        test function on the mesh.\n\n        Returns\n        -------\n        (n_nodes, dim * n_boundary_nodes) scipy.sparse.csr_matrix\n            Sparse matrix of shape.\n\n        Notes\n        -----\n        The integral we are representing on the boundary of the mesh is\n\n        .. math:: \\int_{\\Omega} (w \\vec{u}) \\cdot \\hat{n} \\partial \\Omega\n\n        In discrete form this is:\n\n        .. math:: w^T * P @ u_b\n\n        where `w` is defined on all nodes, and `u_b` is all three components defined on\n        boundary nodes.\n        \"\"\"\n        raise NotImplementedError(\n            f\"boundary_node_vector_integral not implemented for {type(self)}\"\n        )\n\n    @property\n    def nodal_laplacian(self):\n        r\"\"\"Nodal scalar Laplacian operator (nodes to nodes).\n\n        This property constructs the 2nd order scalar Laplacian operator\n        that maps from nodes to nodes. The operator is a sparse matrix\n        :math:`\\mathbf{L_n}` that can be applied as a matrix-vector product to a\n        discrete scalar quantity :math:`\\boldsymbol{\\phi}` that lives on the\n        nodes, i.e.::\n\n            laplace_phi = Ln @ phi\n\n        The operator ``*`` assumes a zero Neumann boundary condition for the discrete\n        scalar quantity. Once constructed, the operator is stored permanently as\n        a property of the mesh.\n\n        Returns\n        -------\n        (n_nodes, n_nodes) scipy.sparse.csr_matrix\n            The numerical Laplacian operator from nodes to nodes\n\n        Notes\n        -----\n        In continuous space, the scalar Laplacian operator is defined as:\n\n        .. math::\n            \\psi = \\nabla^2 \\phi = \\frac{\\partial^2 \\phi}{\\partial x^2}\n            + \\frac{\\partial^2 \\phi}{\\partial y^2}\n            + \\frac{\\partial^2 \\phi}{\\partial z^2}\n\n        Where :math:`\\boldsymbol{\\phi}` is the discrete representation of the continuous variable\n        :math:`\\phi` on the nodes, and :math:`\\boldsymbol{\\psi}` is the discrete representation\n        of its scalar Laplacian on the nodes, **nodal_laplacian** constructs a\n        discrete linear operator :math:`\\mathbf{L_n}` such that:\n\n        .. math::\n            \\boldsymbol{\\psi} = \\mathbf{L_n} \\, \\boldsymbol{\\phi}\n        \"\"\"\n        # EXAMPLE INACTIVE BECAUSE OPERATOR IS BROKEN\n        #\n        # Examples\n        # --------\n\n        # Below, we demonstrate how to apply the nodal Laplacian operator to\n        # a discrete scalar quantity, the mapping of the nodal Laplacian operator and\n        # its sparsity. Our example is carried out on a 2D mesh but it can\n        # be done equivalently for a 3D mesh.\n\n        # We start by importing the necessary packages and modules.\n\n        # >>> from discretize import TensorMesh\n        # >>> import numpy as np\n        # >>> import matplotlib.pyplot as plt\n        # >>> import matplotlib as mpl\n\n        # For a discrete scalar quantity defined on the nodes, we take the\n        # Laplacian by constructing the operator and multiplying\n        # as a matrix-vector product.\n\n        # >>> # Create a uniform grid\n        # >>> h = np.ones(20)\n        # >>> mesh = TensorMesh([h, h], \"CC\")\n        # >>>\n        # >>> # Create a discrete scalar on nodes. The scalar MUST\n        # >>> # respect the zero Neumann boundary condition.\n        # >>> nodes = mesh.nodes\n        # >>> phi = np.exp(-(nodes[:, 0] ** 2 + nodes[:, 1] ** 2) / 4 ** 2)\n        # >>>\n        # >>> # Construct the Laplacian operator and apply to vector\n        # >>> Ln = mesh.nodal_laplacian\n        # >>> laplacian_phi = Ln @ phi\n        # >>>\n        # >>> # Plot\n        # >>> fig = plt.figure(figsize=(13, 6))\n        # >>> ax1 = fig.add_subplot(121)\n        # >>> mesh.plot_image(phi, ax=ax1)\n        # >>> ax1.set_title(\"Scalar at nodes\", fontsize=14)\n        # >>> ax2 = fig.add_subplot(122)\n        # >>> mesh.plot_image(laplacian_phi, ax=ax1)\n        # >>> ax2.set_yticks([])\n        # >>> ax2.set_ylabel(\"\")\n        # >>> ax2.set_title(\"Laplacian at nodes\", fontsize=14)\n        # >>> plt.show()\n\n        # The nodal Laplacian operator is a sparse matrix that maps\n        # from nodes to nodes. To demonstrate this, we construct\n        # a small 2D mesh. We then show the ordering of the nodes\n        # and a spy plot illustrating the sparsity of the operator.\n\n        # >>> mesh = TensorMesh([[(1, 4)], [(1, 4)]])\n        # >>> fig = plt.figure(figsize=(12, 6))\n        # >>> ax1 = fig.add_subplot(211)\n        # >>> mesh.plot_grid(ax=ax1)\n        # >>> ax1.set_title(\"Ordering of the Nodes\", fontsize=14, pad=15)\n        # >>> ax1.plot(mesh.nodes[:, 0], mesh.nodes[:, 1], \"ro\", markersize=8)\n        # >>> for ii, loc in zip(range(mesh.nN), mesh.nodes):\n        # ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n        # >>> ax1.set_xticks([])\n        # >>> ax1.set_yticks([])\n        # >>> ax1.spines['bottom'].set_color('white')\n        # >>> ax1.spines['top'].set_color('white')\n        # >>> ax1.spines['left'].set_color('white')\n        # >>> ax1.spines['right'].set_color('white')\n        # >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        # >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        # >>> ax1.legend(\n        # ...     ['Mesh', r'$\\mathbf{\\phi}$ (nodes)'],\n        # ...     loc='upper right', fontsize=14\n        # ... )\n        # >>> ax2 = fig.add_subplot(212)\n        # >>> ax2.spy(mesh.nodal_laplacian)\n        # >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        # >>> ax2.set_ylabel(\"Node Index\", fontsize=12)\n        # >>> ax2.set_xlabel(\"Node Index\", fontsize=12)\n        raise NotImplementedError(f\"nodal_laplacian not implemented for {type(self)}\")\n\n    @property\n    def stencil_cell_gradient(self):\n        r\"\"\"Stencil for cell gradient operator (cell centers to faces).\n\n        This property constructs a differencing operator that acts on\n        cell centered quantities. The operator takes the difference between\n        the values at adjacent cell centers along each axis direction,\n        and places the result on the shared face; e.g. differences\n        along the x-axis are mapped to x-faces. The operator is a sparse\n        matrix :math:`\\mathbf{G}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            diff_phi = G @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **stencil_cell_gradient** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **stencil_cell_gradient** is called, the boundary conditions are\n        enforced for the differencing operator. Once constructed,\n        the operator is stored as a property of the mesh.\n\n        Returns\n        -------\n        (n_faces, n_cells) scipy.sparse.csr_matrix\n            The stencil for the cell gradient\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the cell gradient\n        stencil, construct the cell gradient stencil and apply it to a discrete\n        scalar quantity. The mapping of the cell gradient operator and\n        its sparsity is also illustrated. Our example is carried out on a 2D\n        mesh but it can be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers. In this case, the scalar represents some block within\n        a homogeneous medium.\n\n        Create a uniform grid\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete scalar at cell centers\n\n        >>> centers = mesh.cell_centers\n        >>> phi = np.zeros(mesh.nC)\n        >>> k = (np.abs(mesh.cell_centers[:, 0]) < 10.) & (np.abs(mesh.cell_centers[:, 1]) < 10.)\n        >>> phi[k] = 1.\n\n        Before constructing the operator, we must define\n        the boundary conditions; zero Neumann for our example. Once the\n        operator is created, it is applied as a matrix-vector product.\n\n        >>> mesh.set_cell_gradient_BC(['neumann', 'neumann'])\n        >>> G = mesh.stencil_cell_gradient\n        >>> diff_phi = G @ phi\n\n        Now we plot the original scalar, and the differencing taken along the\n        x and y axes.\n\n        >>> fig = plt.figure(figsize=(15, 4.5))\n        >>> ax1 = fig.add_subplot(131)\n        >>> mesh.plot_image(phi, ax=ax1)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n\n        >>> ax2 = fig.add_subplot(132)\n        >>> mesh.plot_image(diff_phi, ax=ax2, v_type=\"Fx\")\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Difference (x-axis)\", fontsize=14)\n\n        >>> ax3 = fig.add_subplot(133)\n        >>> mesh.plot_image(diff_phi, ax=ax3, v_type=\"Fy\")\n        >>> ax3.set_yticks([])\n        >>> ax3.set_ylabel(\"\")\n        >>> ax3.set_title(\"Difference (y-axis)\", fontsize=14)\n        >>> plt.show()\n\n        The cell gradient stencil is a sparse differencing matrix that maps\n        from cell centers to faces. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements\n        and a spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 6)]])\n        >>> mesh.set_cell_gradient_BC('neumann')\n\n        >>> fig = plt.figure(figsize=(12, 10))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.set_title(\"Mapping of Stencil\", fontsize=14, pad=15)\n        >>> ax1.plot(mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.plot(mesh.faces_x[:, 0], mesh.faces_x[:, 1], \"g>\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFx), mesh.faces_x):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.plot(mesh.faces_y[:, 0], mesh.faces_y[:, 1], \"g^\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFy), mesh.faces_y):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format((ii + mesh.nFx)), color=\"g\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (centers)', r'$\\mathbf{G^\\ast \\phi}$ (faces)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n\n        >>> ax2 = fig.add_subplot(122)\n        >>> ax2.spy(mesh.stencil_cell_gradient)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Face Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"stencil_cell_gradient not implemented for {type(self)}\"\n        )\n\n    # Inner Products\n    def get_face_inner_product(\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        do_fast=True,\n        **kwargs,\n    ):\n        r\"\"\"Generate the face inner product matrix or its inverse.\n\n        This method generates the inner product matrix (or its inverse)\n        when discrete variables are defined on mesh faces. It is also capable of\n        constructing the inner product matrix when physical properties\n        are defined in the form of constitutive relations. For a comprehensive\n        description of the inner product matrices that can be constructed\n        with **get_face_inner_product**, see *Notes*.\n\n        Parameters\n        ----------\n        model : None or numpy.ndarray, optional\n            Parameters defining the material properties for every cell in the mesh.\n            Inner product matrices can be constructed for the following cases:\n\n            - *None* : returns the basic inner product matrix\n            - *(n_cells)* :class:`numpy.ndarray` : returns inner product matrix for an\n              isotropic model. The array contains a scalar physical property value for\n              each cell.\n            - *(n_cells, dim)* :class:`numpy.ndarray` : returns inner product matrix for\n              diagonal anisotropic case. Columns are ordered ``np.c_[σ_xx, σ_yy, σ_zz]``.\n              This can also a be a 1D array with the same number of total elements in\n              column major order.\n            - *(n_cells, 3)* :class:`numpy.ndarray` (``dim`` is 2) or\n              *(n_cells, 6)* :class:`numpy.ndarray` (``dim`` is 3) :\n              returns inner product matrix for full tensor properties case. Columns are\n              ordered ``np.c_[σ_xx, σ_yy, σ_zz, σ_xy, σ_xz, σ_yz]`` This can also be a\n              1D array with the same number of total elements in column major order.\n\n        invert_model : bool, optional\n            The inverse of *model* is used as the physical property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product matrix.\n            The inverse not implemented for full tensor properties.\n        do_fast : bool, optional\n            Do a faster implementation (if available).\n\n        Returns\n        -------\n        (n_faces, n_faces) scipy.sparse.csr_matrix\n            inner product matrix\n\n        Notes\n        -----\n        For continuous vector quantities :math:`\\vec{u}` and :math:`\\vec{w}`\n        whose discrete representations :math:`\\mathbf{u}` and :math:`\\mathbf{w}`\n        live on the faces, **get_face_inner_product** constructs the inner product matrix\n        :math:`\\mathbf{M_\\ast}` (or its inverse :math:`\\mathbf{M_\\ast^{-1}}`) for the\n        following cases:\n\n        **Basic Inner Product:** the inner product between :math:`\\vec{u}` and :math:`\\vec{w}`\n\n        .. math::\n            \\langle \\vec{u}, \\vec{w} \\rangle = \\mathbf{u^T \\, M \\, w}\n\n        **Isotropic Case:** the inner product between :math:`\\vec{u}` and :math:`\\sigma \\vec{w}`\n        where :math:`\\sigma` is a scalar function.\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle = \\mathbf{u^T \\, M_\\sigma \\, w}\n\n        **Tensor Case:** the inner product between :math:`\\vec{u}` and :math:`\\Sigma \\vec{w}`\n        where :math:`\\Sigma` is tensor function; :math:`\\sigma_{xy} = \\sigma_{xz} = \\sigma_{yz} = 0`\n        for diagonal anisotropy.\n\n        .. math::\n            \\langle \\vec{u}, \\Sigma \\vec{w} \\rangle = \\mathbf{u^T \\, M_\\Sigma \\, w}\n            \\;\\;\\; \\textrm{where} \\;\\;\\;\n            \\Sigma = \\begin{bmatrix}\n            \\sigma_{xx} & \\sigma_{xy} & \\sigma_{xz} \\\\\n            \\sigma_{xy} & \\sigma_{yy} & \\sigma_{yz} \\\\\n            \\sigma_{xz} & \\sigma_{yz} & \\sigma_{zz}\n            \\end{bmatrix}\n\n        Examples\n        --------\n        Here we provide some examples of face inner product matrices.\n        For simplicity, we will work on a 2 x 2 x 2 tensor mesh.\n        As seen below, we begin by constructing and imaging the basic\n        face inner product matrix.\n\n        >>> from discretize import TensorMesh\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> import matplotlib as mpl\n\n        >>> h = np.ones(2)\n        >>> mesh = TensorMesh([h, h, h])\n        >>> Mf = mesh.get_face_inner_product()\n\n        >>> fig = plt.figure(figsize=(6, 6))\n        >>> ax = fig.add_subplot(111)\n        >>> ax.imshow(Mf.todense())\n        >>> ax.set_title('Basic Face Inner Product Matrix', fontsize=18)\n        >>> plt.show()\n\n        Next, we consider the case where the physical properties\n        of the cells are defined by consistutive relations. For\n        the isotropic, diagonal anisotropic and full tensor cases,\n        we show the physical property tensor for a single cell.\n\n        Define 4 constitutive parameters and define the tensor\n        for each cell for isotropic, diagonal and tensor cases.\n\n        >>> sig1, sig2, sig3, sig4, sig5, sig6 = 6, 5, 4, 3, 2, 1\n        >>> sig_iso_tensor = sig1 * np.eye(3)\n        >>> sig_diag_tensor = np.diag(np.array([sig1, sig2, sig3]))\n        >>> sig_full_tensor = np.array([\n        ...     [sig1, sig4, sig5],\n        ...     [sig4, sig2, sig6],\n        ...     [sig5, sig6, sig3]\n        ... ])\n\n        Then plot matrix entries,\n\n        >>> fig = plt.figure(figsize=(15, 5))\n        >>> ax1 = fig.add_subplot(131)\n        >>> ax1.imshow(sig_iso_tensor)\n        >>> ax1.axis('off')\n        >>> ax1.set_title(\"Tensor (isotropic)\", fontsize=16)\n        >>> ax2 = fig.add_subplot(132)\n        >>> ax2.imshow(sig_diag_tensor)\n        >>> ax2.axis('off')\n        >>> ax2.set_title(\"Tensor (diagonal anisotropic)\", fontsize=16)\n        >>> ax3 = fig.add_subplot(133)\n        >>> ax3.imshow(sig_full_tensor)\n        >>> ax3.axis('off')\n        >>> ax3.set_title(\"Tensor (full anisotropic)\", fontsize=16)\n        >>> plt.show()\n\n        Here, construct and image the face inner product matrices for\n        the isotropic, diagonal anisotropic and full tensor cases.\n        Spy plots are used to demonstrate the sparsity of the inner\n        product matrices.\n\n        Isotropic case:\n\n        >>> v = np.ones(mesh.nC)\n        >>> sig = sig1 * v\n        >>> M1 = mesh.get_face_inner_product(sig)\n\n        Diagonal anisotropic case:\n\n        >>> sig = np.c_[sig1*v, sig2*v, sig3*v]\n        >>> M2 = mesh.get_face_inner_product(sig)\n\n        Full anisotropic case:\n\n        >>> sig = np.tile(np.c_[sig1, sig2, sig3, sig4, sig5, sig6], (mesh.nC, 1))\n        >>> M3 = mesh.get_face_inner_product(sig)\n\n        And then we can plot the sparse representation,\n\n        >>> fig = plt.figure(figsize=(12, 4))\n        >>> ax1 = fig.add_subplot(131)\n        >>> ax1.spy(M1, ms=5)\n        >>> ax1.set_title(\"M (isotropic)\", fontsize=16)\n        >>> ax2 = fig.add_subplot(132)\n        >>> ax2.spy(M2, ms=5)\n        >>> ax2.set_title(\"M (diagonal anisotropic)\", fontsize=16)\n        >>> ax3 = fig.add_subplot(133)\n        >>> ax3.spy(M3, ms=5)\n        >>> ax3.set_title(\"M (full anisotropic)\", fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"get_face_inner_product not implemented for {type(self)}\"\n        )\n\n    def get_edge_inner_product(\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        do_fast=True,\n        **kwargs,\n    ):\n        r\"\"\"Generate the edge inner product matrix or its inverse.\n\n        This method generates the inner product matrix (or its inverse)\n        when discrete variables are defined on mesh edges. It is also capable of\n        constructing the inner product matrix when physical properties\n        are defined in the form of constitutive relations. For a comprehensive\n        description of the inner product matrices that can be constructed\n        with **get_edge_inner_product**, see *Notes*.\n\n        Parameters\n        ----------\n        model : None or numpy.ndarray\n            Parameters defining the material properties for every cell in the mesh.\n            Inner product matrices can be constructed for the following cases:\n\n            - *None* : returns the basic inner product matrix\n            - *(n_cells)* :class:`numpy.ndarray` : returns inner product matrix for an\n              isotropic model. The array contains a scalar physical property value for\n              each cell.\n            - *(n_cells, dim)* :class:`numpy.ndarray` : returns inner product matrix for\n              diagonal anisotropic case. Columns are ordered ``np.c_[σ_xx, σ_yy, σ_zz]``.\n              This can also a be a 1D array with the same number of total elements in\n              column major order.\n            - *(n_cells, 3)* :class:`numpy.ndarray` (``dim`` is 2) or\n              *(n_cells, 6)* :class:`numpy.ndarray` (``dim`` is 3) :\n              returns inner product matrix for full tensor properties case. Columns are\n              ordered ``np.c_[σ_xx, σ_yy, σ_zz, σ_xy, σ_xz, σ_yz]`` This can also be a\n              1D array with the same number of total elements in column major order.\n\n        invert_model : bool, optional\n            The inverse of *model* is used as the physical property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product matrix.\n            The inverse not implemented for full tensor properties.\n        do_fast : bool, optional\n            Do a faster implementation (if available).\n\n        Returns\n        -------\n        (n_edges, n_edges) scipy.sparse.csr_matrix\n            inner product matrix\n\n        Notes\n        -----\n        For continuous vector quantities :math:`\\vec{u}` and :math:`\\vec{w}`\n        whose discrete representations :math:`\\mathbf{u}` and :math:`\\mathbf{w}`\n        live on the edges, **get_edge_inner_product** constructs the inner product\n        matrix :math:`\\mathbf{M_\\ast}` (or its inverse :math:`\\mathbf{M_\\ast^{-1}}`) for\n        the following cases:\n\n        **Basic Inner Product:** the inner product between :math:`\\vec{u}` and\n        :math:`\\vec{w}`.\n\n        .. math::\n            \\langle \\vec{u}, \\vec{w} \\rangle = \\mathbf{u^T \\, M \\, w}\n\n        **Isotropic Case:** the inner product between :math:`\\vec{u}` and\n        :math:`\\sigma \\vec{w}` where :math:`\\sigma` is a scalar function.\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle = \\mathbf{u^T \\, M_\\sigma \\, w}\n\n        **Tensor Case:** the inner product between :math:`\\vec{u}` and\n        :math:`\\Sigma \\vec{w}` where :math:`\\Sigma` is tensor function;\n        :math:`\\sigma_{xy} = \\sigma_{xz} = \\sigma_{yz} = 0` for diagonal anisotropy.\n\n        .. math::\n            \\langle \\vec{u}, \\Sigma \\vec{w} \\rangle =\n            \\mathbf{u^T \\, M_\\Sigma \\, w} \\;\\;\\; \\textrm{where} \\;\\;\\;\n            \\Sigma = \\begin{bmatrix}\n            \\sigma_{xx} & \\sigma_{xy} & \\sigma_{xz} \\\\\n            \\sigma_{xy} & \\sigma_{yy} & \\sigma_{yz} \\\\\n            \\sigma_{xz} & \\sigma_{yz} & \\sigma_{zz}\n            \\end{bmatrix}\n\n        Examples\n        --------\n        Here we provide some examples of edge inner product matrices.\n        For simplicity, we will work on a 2 x 2 x 2 tensor mesh.\n        As seen below, we begin by constructing and imaging the basic\n        edge inner product matrix.\n\n        >>> from discretize import TensorMesh\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> import matplotlib as mpl\n\n        >>> h = np.ones(2)\n        >>> mesh = TensorMesh([h, h, h])\n        >>> Me = mesh.get_edge_inner_product()\n\n        >>> fig = plt.figure(figsize=(6, 6))\n        >>> ax = fig.add_subplot(111)\n        >>> ax.imshow(Me.todense())\n        >>> ax.set_title('Basic Edge Inner Product Matrix', fontsize=18)\n        >>> plt.show()\n\n        Next, we consider the case where the physical properties\n        of the cells are defined by consistutive relations. For\n        the isotropic, diagonal anisotropic and full tensor cases,\n        we show the physical property tensor for a single cell.\n\n        Define 4 constitutive parameters and define the tensor\n        for each cell for isotropic, diagonal and tensor cases.\n\n        >>> sig1, sig2, sig3, sig4, sig5, sig6 = 6, 5, 4, 3, 2, 1\n        >>> sig_iso_tensor = sig1 * np.eye(3)\n        >>> sig_diag_tensor = np.diag(np.array([sig1, sig2, sig3]))\n        >>> sig_full_tensor = np.array([\n        ...     [sig1, sig4, sig5],\n        ...     [sig4, sig2, sig6],\n        ...     [sig5, sig6, sig3]\n        ... ])\n\n        Then plot the matrix entries,\n\n        >>> fig = plt.figure(figsize=(15, 5))\n        >>> ax1 = fig.add_subplot(131)\n        >>> ax1.imshow(sig_iso_tensor)\n        >>> ax1.axis('off')\n        >>> ax1.set_title(\"Tensor (isotropic)\", fontsize=16)\n        >>> ax2 = fig.add_subplot(132)\n        >>> ax2.imshow(sig_diag_tensor)\n        >>> ax2.axis('off')\n        >>> ax2.set_title(\"Tensor (diagonal anisotropic)\", fontsize=16)\n        >>> ax3 = fig.add_subplot(133)\n        >>> ax3.imshow(sig_full_tensor)\n        >>> ax3.axis('off')\n        >>> ax3.set_title(\"Tensor (full anisotropic)\", fontsize=16)\n        >>> plt.show()\n\n        Here construct and image the edge inner product matrices for\n        the isotropic, diagonal anisotropic and full tensor cases.\n        Spy plots are used to demonstrate the sparsity of the inner\n        product matrices.\n\n        Isotropic case:\n\n        >>> v = np.ones(mesh.nC)\n        >>> sig = sig1 * v\n        >>> M1 = mesh.get_edge_inner_product(sig)\n\n        Diagonal anisotropic case:\n\n        >>> sig = np.c_[sig1*v, sig2*v, sig3*v]\n        >>> M2 = mesh.get_edge_inner_product(sig)\n\n        Full anisotropic\n\n        >>> sig = np.tile(np.c_[sig1, sig2, sig3, sig4, sig5, sig6], (mesh.nC, 1))\n        >>> M3 = mesh.get_edge_inner_product(sig)\n\n        Then plot the sparse representation,\n\n        >>> fig = plt.figure(figsize=(12, 4))\n        >>> ax1 = fig.add_subplot(131)\n        >>> ax1.spy(M1, ms=5)\n        >>> ax1.set_title(\"M (isotropic)\", fontsize=16)\n        >>> ax2 = fig.add_subplot(132)\n        >>> ax2.spy(M2, ms=5)\n        >>> ax2.set_title(\"M (diagonal anisotropic)\", fontsize=16)\n        >>> ax3 = fig.add_subplot(133)\n        >>> ax3.spy(M3, ms=5)\n        >>> ax3.set_title(\"M (full anisotropic)\", fontsize=16)\n        >>> plt.show()\n\n        \"\"\"\n        raise NotImplementedError(\n            f\"get_edge_inner_product not implemented for {type(self)}\"\n        )\n\n    def get_edge_inner_product_surface(\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        **kwargs,\n    ):\n        r\"\"\"Generate the edge inner product surface matrix or its inverse.\n\n        This method generates the inner product surface matrix (or its inverse)\n        when discrete variables are defined on mesh edges. It is also capable of\n        constructing the inner product surface matrix when diagnostic\n        properties (e.g. conductance) are defined on mesh faces. For a comprehensive\n        description of the inner product surface matrices that can be constructed\n        with **get_edge_inner_product_surface**, see *Notes*.\n\n        Parameters\n        ----------\n        model : None or numpy.ndarray\n            Parameters defining the diagnostic properties for every face in the mesh.\n            Inner product surface matrices can be constructed for the following cases:\n\n            - *None* : returns the basic inner product surface matrix\n            - *(n_faces)* :class:`numpy.ndarray` : returns inner product surface matrix\n              for an isotropic model. The array contains a scalar diagnostic property value\n              for each face.\n\n        invert_model : bool, optional\n            The inverse of *model* is used as the diagnostic property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product surface matrix.\n            The inverse not implemented for full tensor properties.\n\n        Returns\n        -------\n        (n_edges, n_edges) scipy.sparse.csr_matrix\n            inner product surface matrix\n\n        Notes\n        -----\n        For continuous vector quantities :math:`\\vec{u}` and :math:`\\vec{w}`, and\n        scalar physical property distribution :math:`\\sigma`, we define the following inner product:\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle = \\int_\\Omega \\, \\vec{u} \\cdot \\sigma \\vec{v} \\, dv\n\n        If the material property is distributed over a set of surfaces :math:`S_i` with thickness\n        :math:`h`, we can define a diagnostic property value :math:`\\tau = \\sigma h`.\n        And the inner-product can be approximated by a set of surface integrals as follows:\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle =\n            \\sum_i \\int_{S_i} \\, \\vec{u} \\cdot \\tau \\vec{v} \\, da\n\n        Let :math:`\\vec{u}` and :math:`\\vec{w}` have discrete representations :math:`\\mathbf{u}`\n        and :math:`\\mathbf{w}` that live on the edges. Assuming the contribution of vector components\n        normal to the surface are negligible compared to tangential components,\n        **get_edge_inner_product_suface** constructs the inner product matrix :math:`\\mathbf{M_\\tau}`\n        (or its inverse :math:`\\mathbf{M_\\tau^{-1}}`) such that:\n\n        .. math::\n            \\sum_i \\int_{S_i} \\, \\vec{u} \\cdot \\tau \\vec{v} \\, da\n            \\approx \\sum_i \\int_{S_i} \\, \\vec{u}_\\parallel \\cdot \\tau \\vec{v}_\\parallel \\, da\n            = \\mathbf{u^T \\, M_\\tau \\, w}\n\n        where the diagnostic properties on mesh faces (i.e. the model) are stored within an array of the form:\n\n        .. math::\n            \\boldsymbol{\\tau} = \\begin{bmatrix} \\boldsymbol{\\tau_x} \\\\\n            \\boldsymbol{\\tau_y} \\\\ \\boldsymbol{\\tau_z} \\end{bmatrix}\n\n        Examples\n        --------\n        Here we provide an example of edge inner product surface matrix.\n        For simplicity, we will work on a 2 x 2 x 2 tensor mesh.\n        As seen below, we begin by constructing and imaging the basic\n        edge inner product surface matrix.\n\n        >>> from discretize import TensorMesh\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> import matplotlib as mpl\n\n        >>> h = np.ones(2)\n        >>> mesh = TensorMesh([h, h, h])\n        >>> Me = mesh.get_edge_inner_product_surface()\n\n        >>> fig = plt.figure(figsize=(6, 6))\n        >>> ax = fig.add_subplot(111)\n        >>> ax.imshow(Me.todense())\n        >>> ax.set_title('Basic Edge Inner Product Surface Matrix', fontsize=18)\n        >>> plt.show()\n\n        Next, we consider the case where the physical properties\n        are defined by diagnostic properties on mesh faces. For the isotropic case,\n        we show the physical property tensor for a single cell.\n\n        Define the diagnostic property values for x, y and z faces.\n\n        >>> tau_x, tau_y, tau_z = 3, 2, 1\n\n        Here construct and image the edge inner product surface matrix for the isotropic case.\n        Spy plots are used to demonstrate the sparsity of the inner product surface matrices.\n\n        >>> tau = np.r_[\n        >>>     tau_x * np.ones(mesh.n_faces_x),\n        >>>     tau_y * np.ones(mesh.n_faces_y),\n        >>>     tau_z * np.ones(mesh.n_faces_z)\n        >>> ]\n        >>> M = mesh.get_edge_inner_product_surface(tau)\n\n        Then plot the sparse representation,\n\n        >>> fig = plt.figure(figsize=(4, 4))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.imshow(M.todense())\n        >>> ax1.set_title(\"M (isotropic)\", fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"get_edge_inner_product_surface not implemented for {type(self)}\"\n        )\n\n    def get_face_inner_product_surface(\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        **kwargs,\n    ):\n        r\"\"\"Generate the face inner product matrix or its inverse.\n\n        This method generates the inner product surface matrix (or its inverse)\n        when discrete variables are defined on mesh faces. It is also capable of\n        constructing the inner product surface matrix when diagnostic quantitative\n        properties (e.g. conductance) are defined on mesh faces. For a comprehensive\n        description of the inner product surface matrices that can be constructed\n        with **get_face_inner_product_surface**, see *Notes*.\n\n        Parameters\n        ----------\n        model : None or numpy.ndarray\n            Parameters defining the diagnostic properties for every face in the mesh.\n            Inner product surface matrices can be constructed for the following cases:\n\n            - *None* : returns the basic inner product surface matrix\n            - *(n_faces)* :class:`numpy.ndarray` : returns inner product surface matrix\n              for an isotropic model. The array contains a scalar diagnostic property value\n              for each face.\n\n        invert_model : bool, optional\n            The inverse of *model* is used as the diagnostic property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product surface matrix.\n            The inverse not implemented for full tensor properties.\n\n        Returns\n        -------\n        (n_faces, n_faces) scipy.sparse.csr_matrix\n            inner product matrix\n\n        Notes\n        -----\n        For continuous vector quantities :math:`\\vec{u}` and :math:`\\vec{w}`, and\n        scalar physical property distribution :math:`\\sigma`, we define the following inner product:\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle = \\int_\\Omega \\, \\vec{u} \\cdot \\sigma \\vec{v} \\, dv\n\n        If the material property is distributed over a set of surfaces :math:`S_i` with thickness\n        :math:`h`, we can define a diagnostic property value :math:`\\tau = \\sigma h`.\n        And the inner-product can be approximated by a set of surface integrals as follows:\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle =\n            \\sum_i \\int_{S_i} \\, \\vec{u} \\cdot \\tau \\vec{v} \\, da\n\n        Let :math:`\\vec{u}` and :math:`\\vec{w}` have discrete representations :math:`\\mathbf{u}`\n        and :math:`\\mathbf{w}` that live on the edges. Assuming the contribution of vector components\n        tangential to the surface are negligible compared to normal components,\n        **get_face_inner_product_suface** constructs the inner product matrix :math:`\\mathbf{M_\\tau}`\n        (or its inverse :math:`\\mathbf{M_\\tau^{-1}}`) such that:\n\n        .. math::\n            \\sum_i \\int_{S_i} \\, \\vec{u} \\cdot \\tau \\vec{v} \\, da\n            \\approx \\sum_i \\int_{S_i} \\, \\vec{u}_\\perp \\cdot \\tau \\vec{v}_\\perp \\, da\n            = \\mathbf{u^T \\, M_\\tau \\, w}\n\n        where the diagnostic properties on mesh faces (i.e. the model) are stored within an array of the form:\n\n        .. math::\n            \\boldsymbol{\\tau} = \\begin{bmatrix} \\boldsymbol{\\tau_x} \\\\\n            \\boldsymbol{\\tau_y} \\\\ \\boldsymbol{\\tau_z} \\end{bmatrix}\n\n        Examples\n        --------\n        Here we provide an example of face inner product surface matrix.\n        For simplicity, we will work on a 2 x 2 x 2 tensor mesh.\n        As seen below, we begin by constructing and imaging the basic\n        face inner product surface matrix.\n\n        >>> from discretize import TensorMesh\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> import matplotlib as mpl\n\n        >>> h = np.ones(2)\n        >>> mesh = TensorMesh([h, h, h])\n        >>> Mf = mesh.get_face_inner_product_surface()\n\n        >>> fig = plt.figure(figsize=(6, 6))\n        >>> ax = fig.add_subplot(111)\n        >>> ax.imshow(Mf.todense())\n        >>> ax.set_title('Basic Face Inner Product Surface Matrix', fontsize=18)\n        >>> plt.show()\n\n        Next, we consider the case where the physical properties\n        are defined by diagnostic properties on mesh faces. For the isotropic case,\n        we show the physical property tensor for a single cell.\n\n        Define the diagnostic property values for x, y and z faces.\n\n        >>> tau_x, tau_y, tau_z = 3, 2, 1\n\n        Here construct and image the face inner product surface matrix for the isotropic case.\n        Spy plots are used to demonstrate the sparsity of the inner product surface matrices.\n\n        >>> tau = np.r_[\n        >>>     tau_x * np.ones(mesh.n_faces_x),\n        >>>     tau_y * np.ones(mesh.n_faces_y),\n        >>>     tau_z * np.ones(mesh.n_faces_z)\n        >>> ]\n        >>> M = mesh.get_face_inner_product_surface(tau)\n\n        Then plot the sparse representation,\n\n        >>> fig = plt.figure(figsize=(4, 4))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.imshow(M.todense())\n        >>> ax1.set_title(\"M (isotropic)\", fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        try:\n            face_areas = self.face_areas\n        except NotImplementedError:\n            raise NotImplementedError(\n                f\"get_face_inner_product_surface not implemented for {type(self)}\"\n            )\n\n        if model is None:\n            model = np.ones(self.nF)\n\n        if invert_model:\n            model = 1.0 / model\n\n        if is_scalar(model):\n            model = model * np.ones(self.nF)\n\n        # Isotropic case only\n        if model.size != self.nF:\n            raise ValueError(\n                \"Unexpected shape of tensor: {}\".format(model.shape),\n                \"Must be scalar or have length equal to total number of faces.\",\n            )\n\n        M = sdiag(face_areas * mkvc(model))\n\n        if invert_matrix:\n            return sdinv(M)\n        else:\n            return M\n\n    def get_edge_inner_product_line(\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        **kwargs,\n    ):\n        r\"\"\"Generate the edge inner product line matrix or its inverse.\n\n        This method generates the inner product line matrix (or its inverse)\n        when discrete variables are defined on mesh edges. It is also capable of\n        constructing the inner product line matrix when diagnostic\n        properties (e.g. integrated conductance) are defined on mesh edges.\n        For a comprehensive description of the inner product line matrices that\n        can be constructed with **get_edge_inner_product_line**, see *Notes*.\n\n        Parameters\n        ----------\n        model : None or numpy.ndarray\n            Parameters defining the diagnostic property for every edge in the mesh.\n            Inner product line matrices can be constructed for the following cases:\n\n            - *None* : returns the basic inner product line matrix\n            - *(n_edges)* :class:`numpy.ndarray` : returns inner product line matrix\n              for an isotropic model. The array contains a scalar diagnostic property value\n              for each edge.\n\n        invert_model : bool, optional\n            The inverse of *model* is used as the diagnostic property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product line matrix.\n            The inverse not implemented for full tensor properties.\n\n        Returns\n        -------\n        (n_edges, n_edges) scipy.sparse.csr_matrix\n            inner product line matriz\n\n        Notes\n        -----\n        For continuous vector quantities :math:`\\vec{u}` and :math:`\\vec{w}`, and\n        scalar physical property distribution :math:`\\sigma`, we define the following inner product:\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle = \\int_\\Omega \\, \\vec{u} \\cdot \\sigma \\vec{v} \\, dv\n\n        If the material property is distributed over a set of lines :math:`\\ell_i` with cross-sectional\n        area :math:`a`, we can define a diagnostic property value :math:`\\lambda = \\sigma a`.\n        And the inner-product can be approximated by a set of line integrals as follows:\n\n        .. math::\n            \\langle \\vec{u}, \\sigma \\vec{w} \\rangle =\n            \\sum_i \\int_{\\ell_i} \\, \\vec{u} \\cdot \\lambda \\vec{v} \\, ds\n\n        Let :math:`\\vec{u}` and :math:`\\vec{w}` have discrete representations :math:`\\mathbf{u}`\n        and :math:`\\mathbf{w}` that live on the edges. Assuming the contribution of vector components\n        perpendicular to the lines are negligible compared to parallel components,\n        **get_edge_inner_product_line** constructs the inner product matrix :math:`\\mathbf{M_\\lambda}`\n        (or its inverse :math:`\\mathbf{M_\\lambda^{-1}}`) such that:\n\n        .. math::\n            \\sum_i \\int_{\\ell_i} \\, \\vec{u} \\cdot \\lambda \\vec{v} \\, ds\n            \\approx \\sum_i \\int_{\\ell_i} \\, \\vec{u}_\\parallel \\cdot \\lambda \\vec{v}_\\parallel \\, ds\n            = \\mathbf{u^T \\, M_\\lambda \\, w}\n\n        where the diagnostic properties on mesh edges (i.e. the model) are stored within an array of the form:\n\n        .. math::\n            \\boldsymbol{\\lambda} = \\begin{bmatrix} \\boldsymbol{\\lambda_x} \\\\\n            \\boldsymbol{\\lambda_y} \\\\ \\boldsymbol{\\lambda_z} \\end{bmatrix}\n\n        Examples\n        --------\n        Here we provide an example of edge inner product line matrix.\n        For simplicity, we will work on a 2 x 2 x 2 tensor mesh.\n        As seen below, we begin by constructing and imaging the basic\n        edge inner product line matrix.\n\n        >>> from discretize import TensorMesh\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n\n        >>> h = np.ones(2)\n        >>> mesh = TensorMesh([h, h, h])\n        >>> Me = mesh.get_edge_inner_product_line()\n\n        >>> fig = plt.figure(figsize=(6, 6))\n        >>> ax = fig.add_subplot(111)\n        >>> ax.imshow(Me.todense())\n        >>> ax.set_title('Basic Edge Inner Product Line Matrix', fontsize=18)\n        >>> plt.show()\n\n        Next, we consider the case where the physical properties\n        are defined by diagnostic properties on mesh edges. For the isotropic case,\n        we show the physical property tensor for a single cell.\n\n        Define the diagnostic property values for x, y and z faces.\n\n        >>> tau_x, tau_y, tau_z = 3, 2, 1\n\n        Here construct and image the edge inner product line matrix for the isotropic case.\n        Spy plots are used to demonstrate the sparsity of the matrix.\n\n        >>> tau = np.r_[\n        >>>     tau_x * np.ones(mesh.n_edges_x),\n        >>>     tau_y * np.ones(mesh.n_edges_y),\n        >>>     tau_z * np.ones(mesh.n_edges_z)\n        >>> ]\n        >>> M = mesh.get_edge_inner_product_line(tau)\n\n        Then plot the sparse representation,\n\n        >>> fig = plt.figure(figsize=(4, 4))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.imshow(M.todense())\n        >>> ax1.set_title(\"M (isotropic)\", fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        try:\n            edge_lengths = self.edge_lengths\n        except NotImplementedError:\n            raise NotImplementedError(\n                f\"get_edge_inner_product_line not implemented for {type(self)}\"\n            )\n\n        if model is None:\n            model = np.ones(self.nE)\n\n        if invert_model:\n            model = 1.0 / model\n\n        if is_scalar(model):\n            model = model * np.ones(self.nE)\n\n        # Isotropic case only\n        if model.size != self.nE:\n            raise ValueError(\n                \"Unexpected shape of tensor: {}\".format(model.shape),\n                \"Must be scalar or have length equal to total number of edges.\",\n            )\n\n        M = sdiag(edge_lengths * mkvc(model))\n\n        if invert_matrix:\n            return sdinv(M)\n        else:\n            return M\n\n    def get_face_inner_product_deriv(\n        self, model, do_fast=True, invert_model=False, invert_matrix=False, **kwargs\n    ):\n        r\"\"\"Get a function handle to multiply a vector with derivative of face inner product matrix (or its inverse).\n\n        Let :math:`\\mathbf{M}(\\mathbf{m})` be the face inner product matrix\n        constructed with a set of physical property parameters :math:`\\mathbf{m}`\n        (or its inverse). **get_face_inner_product_deriv** constructs a function handle\n\n        .. math::\n            \\mathbf{F}(\\mathbf{u}) = \\mathbf{u}^T \\, \\frac{\\partial \\mathbf{M}(\\mathbf{m})}{\\partial \\mathbf{m}}\n\n        which accepts any numpy.array :math:`\\mathbf{u}` of shape (n_faces,). That is,\n        **get_face_inner_product_deriv** constructs a function handle for computing\n        the dot product between a vector :math:`\\mathbf{u}` and the derivative of the\n        face inner product matrix (or its inverse) with respect to the property parameters.\n        When computed, :math:`\\mathbf{F}(\\mathbf{u})` returns a ``scipy.sparse.csr_matrix``\n        of shape (n_faces, n_param).\n\n        The function handle can be created for isotropic, diagonal\n        isotropic and full tensor physical properties; see notes.\n\n        Parameters\n        ----------\n        model : numpy.ndarray\n            Parameters defining the material properties for every cell in the mesh.\n            Inner product matrices can be constructed for the following cases:\n\n            - *(n_cells)* :class:`numpy.ndarray` : Isotropic case. *model* contains a\n              scalar physical property value for each cell.\n            - *(n_cells, dim)* :class:`numpy.ndarray` : Diagonal anisotropic case.\n              Columns are ordered ``np.c_[σ_xx, σ_yy, σ_zz]``. This can also a be a 1D\n              array with the same number of total elements in column major order.\n            - *(n_cells, 3)* :class:`numpy.ndarray` (``dim`` is 2) or\n              *(n_cells, 6)* :class:`numpy.ndarray` (``dim`` is 3) : Full tensor properties case. Columns\n              are ordered ``np.c_[σ_xx, σ_yy, σ_zz, σ_xy, σ_xz, σ_yz]`` This can also be\n              a 1D array with the same number of total elements in column major order.\n\n        do_fast : bool, optional\n            Do a faster implementation (if available).\n        invert_model : bool, optional\n            The inverse of *model* is used as the physical property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product matrix.\n            The inverse not implemented for full tensor properties.\n\n        Returns\n        -------\n        function\n            The function handle :math:`\\mathbf{F}(\\mathbf{u})` which accepts a\n            (``n_faces``) :class:`numpy.ndarray` :math:`\\mathbf{u}`. The function\n            returns a (``n_faces``, ``n_params``) :class:`scipy.sparse.csr_matrix`.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{M}(\\mathbf{m})` be the face inner product matrix (or its inverse)\n        for the set of physical property parameters :math:`\\mathbf{m}`. And let :math:`\\mathbf{u}`\n        be a discrete quantity that lives on the faces. **get_face_inner_product_deriv**\n        creates a function handle for computing the following:\n\n        .. math::\n            \\mathbf{F}(\\mathbf{u}) = \\mathbf{u}^T \\, \\frac{\\partial \\mathbf{M}(\\mathbf{m})}{\\partial \\mathbf{m}}\n\n        The dimensions of the sparse matrix constructed by computing :math:`\\mathbf{F}(\\mathbf{u})`\n        for some :math:`\\mathbf{u}` depends on the constitutive relation defined for each cell.\n        These cases are summarized below.\n\n        **Isotropic Case:** The physical property for each cell is defined by a scalar value.\n        Therefore :math:`\\mathbf{m}` is a (``n_cells``) :class:`numpy.ndarray`. The sparse matrix\n        output by computing :math:`\\mathbf{F}(\\mathbf{u})` has shape (``n_faces``, ``n_cells``).\n\n        **Diagonal Anisotropic Case:** In this case, the physical properties for each cell are\n        defined by a diagonal tensor\n\n        .. math::\n            \\Sigma = \\begin{bmatrix}\n            \\sigma_{xx} & 0 & 0 \\\\\n            0 & \\sigma_{yy} & 0 \\\\\n            0 & 0 & \\sigma_{zz}\n            \\end{bmatrix}\n\n        Thus there are ``dim * n_cells`` physical property parameters and :math:`\\mathbf{m}` is\n        a (``dim * n_cells``) :class:`numpy.ndarray`.  The sparse matrix\n        output by computing :math:`\\mathbf{F}(\\mathbf{u})` has shape (``n_faces``, ``dim * n_cells``).\n\n        **Full Tensor Case:** In this case, the physical properties for each cell are\n        defined by a full tensor\n\n        .. math::\n            \\Sigma = \\begin{bmatrix}\n            \\sigma_{xx} & \\sigma_{xy} & \\sigma_{xz} \\\\\n            \\sigma_{xy} & \\sigma_{yy} & \\sigma_{yz} \\\\\n            \\sigma_{xz} & \\sigma_{yz} & \\sigma_{zz}\n            \\end{bmatrix}\n\n        Thus there are ``6 * n_cells`` physical property parameters in 3 dimensions, or\n        ``3 * n_cells`` physical property parameters in 2 dimensions, and\n        :math:`\\mathbf{m}` is a (``n_params``) :class:`numpy.ndarray`.\n        The sparse matrix output by computing :math:`\\mathbf{F}(\\mathbf{u})`\n        has shape (``n_faces``, ``n_params``).\n\n        Examples\n        --------\n        Here, we construct a 4 cell by 4 cell tensor mesh. For our first example we\n        consider isotropic physical properties; that is, the physical properties\n        of each cell are defined a scalar value. We construct the face inner product\n        matrix and visualize it with a spy plot. We then use\n        **get_face_inner_product_deriv** to construct the function handle\n        :math:`\\mathbf{F}(\\mathbf{u})` and plot the evaluation\n        of this function on a spy plot.\n\n        >>> from discretize import TensorMesh\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> import matplotlib as mpl\n        >>> mpl.rcParams.update({'font.size': 14})\n        >>> rng = np.random.default_rng(45)\n        >>> mesh = TensorMesh([[(1, 4)], [(1, 4)]])\n\n        Define a model, and a random vector to multiply the derivative with,\n        then we grab the respective derivative function and calculate the\n        sparse matrix,\n\n        >>> m = rng.random(mesh.nC)  # physical property parameters\n        >>> u = rng.random(mesh.nF)  # vector of shape (n_faces)\n        >>> Mf = mesh.get_face_inner_product(m)\n        >>> F = mesh.get_face_inner_product_deriv(m)  # Function handle\n        >>> dFdm_u = F(u)\n\n        Spy plot for the inner product matrix and its derivative\n\n        >>> fig = plt.figure(figsize=(15, 5))\n        >>> ax1 = fig.add_axes([0.05, 0.05, 0.3, 0.85])\n        >>> ax1.spy(Mf, ms=6)\n        >>> ax1.set_title(\"Face Inner Product Matrix (Isotropic)\", fontsize=14, pad=5)\n        >>> ax1.set_xlabel(\"Face Index\", fontsize=12)\n        >>> ax1.set_ylabel(\"Face Index\", fontsize=12)\n        >>> ax2 = fig.add_axes([0.43, 0.05, 0.17, 0.8])\n        >>> ax2.spy(dFdm_u, ms=6)\n        >>> ax2.set_title(\n        ...     r\"$u^T \\, \\dfrac{\\partial M(m)}{\\partial m}$ (Isotropic)\",\n        ...     fontsize=14, pad=5\n        ... )\n        >>> ax2.set_xlabel(\"Parameter Index\", fontsize=12)\n        >>> ax2.set_ylabel(\"Face Index\", fontsize=12)\n        >>> plt.show()\n\n        For our second example, the physical properties on the mesh are fully\n        anisotropic; that is, the physical properties of each cell are defined\n        by a tensor with parameters :math:`\\sigma_1`, :math:`\\sigma_2` and :math:`\\sigma_3`.\n        Once again we construct the face inner product matrix and visualize it with a\n        spy plot. We then use **get_face_inner_product_deriv** to construct the\n        function handle :math:`\\mathbf{F}(\\mathbf{u})` and plot the evaluation\n        of this function on a spy plot.\n\n        >>> m = rng.random((mesh.nC, 3))  # anisotropic physical property parameters\n        >>> u = rng.random(mesh.nF)     # vector of shape (n_faces)\n        >>> Mf = mesh.get_face_inner_product(m)\n        >>> F = mesh.get_face_inner_product_deriv(m)  # Function handle\n        >>> dFdm_u = F(u)\n\n        Plot the anisotropic inner product matrix and its derivative matrix,\n\n        >>> fig = plt.figure(figsize=(15, 5))\n        >>> ax1 = fig.add_axes([0.05, 0.05, 0.3, 0.8])\n        >>> ax1.spy(Mf, ms=6)\n        >>> ax1.set_title(\"Face Inner Product (Full Tensor)\", fontsize=14, pad=5)\n        >>> ax1.set_xlabel(\"Face Index\", fontsize=12)\n        >>> ax1.set_ylabel(\"Face Index\", fontsize=12)\n        >>> ax2 = fig.add_axes([0.4, 0.05, 0.45, 0.85])\n        >>> ax2.spy(dFdm_u, ms=6)\n        >>> ax2.set_title(\n        ...     r\"$u^T \\, \\dfrac{\\partial M(m)}{\\partial m} \\;$ (Full Tensor)\",\n        ...     fontsize=14, pad=5\n        ... )\n        >>> ax2.set_xlabel(\"Parameter Index\", fontsize=12)\n        >>> ax2.set_ylabel(\"Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"get_face_inner_product_deriv not implemented for {type(self)}\"\n        )\n\n    def get_edge_inner_product_deriv(\n        self, model, do_fast=True, invert_model=False, invert_matrix=False, **kwargs\n    ):\n        r\"\"\"Get a function handle to multiply vector with derivative of edge inner product matrix (or its inverse).\n\n        Let :math:`\\mathbf{M}(\\mathbf{m})` be the edge inner product matrix\n        constructed with a set of physical property parameters :math:`\\mathbf{m}`\n        (or its inverse). **get_edge_inner_product_deriv** constructs a function handle\n\n        .. math::\n            \\mathbf{F}(\\mathbf{u}) = \\mathbf{u}^T \\, \\frac{\\partial \\mathbf{M}(\\mathbf{m})}{\\partial \\mathbf{m}}\n\n        which accepts any numpy.array :math:`\\mathbf{u}` of shape (n_edges,). That is,\n        **get_edge_inner_product_deriv** constructs a function handle for computing\n        the dot product between a vector :math:`\\mathbf{u}` and the derivative of the\n        edge inner product matrix (or its inverse) with respect to the property parameters.\n        When computed, :math:`\\mathbf{F}(\\mathbf{u})` returns a ``scipy.sparse.csr_matrix``\n        of shape (n_edges, n_param).\n\n        The function handle can be created for isotropic, diagonal\n        isotropic and full tensor physical properties; see notes.\n\n        Parameters\n        ----------\n        model : numpy.ndarray\n            Parameters defining the material properties for every cell in the mesh.\n\n            Allows for the following cases:\n\n            - *(n_cells)* :class:`numpy.ndarray` : Isotropic case. *model* contains a\n              scalar physical property value for each cell.\n            - *(n_cells, dim)* :class:`numpy.ndarray` : Diagonal anisotropic case.\n              Columns are ordered ``np.c_[σ_xx, σ_yy, σ_zz]``. This can also a be a 1D\n              array with the same number of total elements in column major order.\n            - *(n_cells, 3)* :class:`numpy.ndarray` (``dim`` is 2) or\n              *(n_cells, 6)* :class:`numpy.ndarray` (``dim`` is 3) : Full tensor properties case. Columns\n              are ordered ``np.c_[σ_xx, σ_yy, σ_zz, σ_xy, σ_xz, σ_yz]`` This can also be\n              a 1D array with the same number of total elements in column major order.\n\n        invert_model : bool, optional\n            The inverse of *model* is used as the physical property.\n        invert_matrix : bool, optional\n            Returns the function handle for the inverse of the inner product matrix\n            The inverse not implemented for full tensor properties.\n        do_fast : bool, optional\n            Do a faster implementation (if available).\n\n        Returns\n        -------\n        function\n            The function handle :math:`\\mathbf{F}(\\mathbf{u})` which accepts a\n            (``n_edges``) :class:`numpy.ndarray` :math:`\\mathbf{u}`. The function\n            returns a (``n_edges``, ``n_params``) :class:`scipy.sparse.csr_matrix`.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{M}(\\mathbf{m})` be the edge inner product matrix (or its inverse)\n        for the set of physical property parameters :math:`\\mathbf{m}`. And let :math:`\\mathbf{u}`\n        be a discrete quantity that lives on the edges. **get_edge_inner_product_deriv**\n        creates a function handle for computing the following:\n\n        .. math::\n            \\mathbf{F}(\\mathbf{u}) = \\mathbf{u}^T \\, \\frac{\\partial \\mathbf{M}(\\mathbf{m})}{\\partial \\mathbf{m}}\n\n        The dimensions of the sparse matrix constructed by computing :math:`\\mathbf{F}(\\mathbf{u})`\n        for some :math:`\\mathbf{u}` depends on the constitutive relation defined for each cell.\n        These cases are summarized below.\n\n        **Isotropic Case:** The physical property for each cell is defined by a scalar value.\n        Therefore :math:`\\mathbf{m}` is a (``n_cells``) :class:`numpy.ndarray`. The sparse matrix\n        output by computing :math:`\\mathbf{F}(\\mathbf{u})` has shape (``n_edges``, ``n_cells``).\n\n        **Diagonal Anisotropic Case:** In this case, the physical properties for each cell are\n        defined by a diagonal tensor\n\n        .. math::\n            \\Sigma = \\begin{bmatrix}\n            \\sigma_{xx} & 0 & 0 \\\\\n            0 & \\sigma_{yy} & 0 \\\\\n            0 & 0 & \\sigma_{zz}\n            \\end{bmatrix}\n\n        Thus there are ``dim * n_cells`` physical property parameters and :math:`\\mathbf{m}` is\n        a (``dim * n_cells``) :class:`numpy.ndarray`.  The sparse matrix\n        output by computing :math:`\\mathbf{F}(\\mathbf{u})` has shape (``n_edges``, ``dim * n_cells``).\n\n        **Full Tensor Case:** In this case, the physical properties for each cell are\n        defined by a full tensor\n\n        .. math::\n            \\Sigma = \\begin{bmatrix}\n            \\sigma_{xx} & \\sigma_{xy} & \\sigma_{xz} \\\\\n            \\sigma_{xy} & \\sigma_{yy} & \\sigma_{yz} \\\\\n            \\sigma_{xz} & \\sigma_{yz} & \\sigma_{zz}\n            \\end{bmatrix}\n\n        Thus there are ``6 * n_cells`` physical property parameters in 3 dimensions, or\n        ``3 * n_cells`` physical property parameters in 2 dimensions, and :math:`\\mathbf{m}` is\n        a (``n_params``) :class:`numpy.ndarray`. The sparse matrix\n        output by computing :math:`\\mathbf{F}(\\mathbf{u})` has shape (``n_edges``, ``n_params``).\n\n        Examples\n        --------\n        Here, we construct a 4 cell by 4 cell tensor mesh. For our first example we\n        consider isotropic physical properties; that is, the physical properties\n        of each cell are defined a scalar value. We construct the edge inner product\n        matrix and visualize it with a spy plot. We then use\n        **get_edge_inner_product_deriv** to construct the function handle\n        :math:`\\mathbf{F}(\\mathbf{u})` and plot the evaluation of this function on a spy\n        plot.\n\n        >>> from discretize import TensorMesh\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> import matplotlib as mpl\n        >>> mpl.rcParams.update({'font.size': 14})\n        >>> rng = np.random.default_rng(45)\n        >>> mesh = TensorMesh([[(1, 4)], [(1, 4)]])\n\n        Next we create a random isotropic model vector, and a random vector to multiply\n        the derivative with (for illustration purposes).\n\n        >>> m = rng.random(mesh.nC)  # physical property parameters\n        >>> u = rng.random(mesh.nF)  # vector of shape (n_edges)\n        >>> Me = mesh.get_edge_inner_product(m)\n        >>> F = mesh.get_edge_inner_product_deriv(m)  # Function handle\n        >>> dFdm_u = F(u)\n\n        Plot inner product matrix and its derivative matrix\n\n        >>> fig = plt.figure(figsize=(15, 5))\n        >>> ax1 = fig.add_axes([0.05, 0.05, 0.3, 0.8])\n        >>> ax1.spy(Me, ms=6)\n        >>> ax1.set_title(\"Edge Inner Product Matrix (Isotropic)\", fontsize=14, pad=5)\n        >>> ax1.set_xlabel(\"Edge Index\", fontsize=12)\n        >>> ax1.set_ylabel(\"Edge Index\", fontsize=12)\n        >>> ax2 = fig.add_axes([0.43, 0.05, 0.17, 0.8])\n        >>> ax2.spy(dFdm_u, ms=6)\n        >>> ax2.set_title(\n        ...     r\"$u^T \\, \\dfrac{\\partial M(m)}{\\partial m}$ (Isotropic)\",\n        ...     fontsize=14, pad=5\n        ... )\n        >>> ax2.set_xlabel(\"Parameter Index\", fontsize=12)\n        >>> ax2.set_ylabel(\"Edge Index\", fontsize=12)\n        >>> plt.show()\n\n        For our second example, the physical properties on the mesh are fully\n        anisotropic; that is, the physical properties of each cell are defined\n        by a tensor with parameters :math:`\\sigma_1`, :math:`\\sigma_2` and :math:`\\sigma_3`.\n        Once again we construct the edge inner product matrix and visualize it with a\n        spy plot. We then use **get_edge_inner_product_deriv** to construct the\n        function handle :math:`\\mathbf{F}(\\mathbf{u})` and plot the evaluation\n        of this function on a spy plot.\n\n        >>> m = rng.random((mesh.nC, 3))  # physical property parameters\n        >>> u = rng.random(mesh.nF)     # vector of shape (n_edges)\n        >>> Me = mesh.get_edge_inner_product(m)\n        >>> F = mesh.get_edge_inner_product_deriv(m)  # Function handle\n        >>> dFdm_u = F(u)\n\n        Plot the anisotropic inner product matrix and its derivative matrix\n\n        >>> fig = plt.figure(figsize=(15, 5))\n        >>> ax1 = fig.add_axes([0.05, 0.05, 0.3, 0.8])\n        >>> ax1.spy(Me, ms=6)\n        >>> ax1.set_title(\"Edge Inner Product (Full Tensor)\", fontsize=14, pad=5)\n        >>> ax1.set_xlabel(\"Edge Index\", fontsize=12)\n        >>> ax1.set_ylabel(\"Edge Index\", fontsize=12)\n        >>> ax2 = fig.add_axes([0.4, 0.05, 0.45, 0.8])\n        >>> ax2.spy(dFdm_u, ms=6)\n        >>> ax2.set_title(\n        ...     r\"$u^T \\, \\dfrac{\\partial M(m)}{\\partial m} \\;$ (Full Tensor)\",\n        ...     fontsize=14, pad=5\n        ... )\n        >>> ax2.set_xlabel(\"Parameter Index\", fontsize=12)\n        >>> ax2.set_ylabel(\"Edge Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"get_edge_inner_product_deriv not implemented for {type(self)}\"\n        )\n\n    def get_edge_inner_product_surface_deriv(\n        self,\n        model,\n        invert_model=False,\n        invert_matrix=False,\n        **kwargs,\n    ):\n        r\"\"\"Get a function handle to multiply a vector with derivative of edge inner product surface matrix (or its inverse).\n\n        Let :math:`\\mathbf{M}(\\mathbf{m})` be the edge inner product surface matrix\n        constructed with a set of diagnostic property parameters :math:`\\mathbf{m}`\n        (or its inverse) defined on mesh faces. **get_edge_inner_product_surface_deriv**\n        constructs a function handle\n\n        .. math::\n            \\mathbf{F}(\\mathbf{u}) = \\mathbf{u}^T \\, \\frac{\\partial \\mathbf{M}(\\mathbf{m})}{\\partial \\mathbf{m}}\n\n        which accepts any numpy.array :math:`\\mathbf{u}` of shape (n_edges,). That is,\n        **get_edge_inner_product_surface_deriv** constructs a function handle for computing\n        the dot product between a vector :math:`\\mathbf{u}` and the derivative of the\n        edge inner product surface matrix (or its inverse) with respect to the property parameters.\n        When computed, :math:`\\mathbf{F}(\\mathbf{u})` returns a ``scipy.sparse.csr_matrix``\n        of shape (n_edges, n_param).\n\n        The function handle can only be created for isotropic diagnostic properties.\n\n        Parameters\n        ----------\n        model : (n_faces, ) numpy.ndarray\n            Parameters defining the diagnostic property values for every face in the mesh.\n        invert_model : bool, optional\n            The inverse of *model* is used as the diagnostic property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product surface matrix.\n\n        Returns\n        -------\n        function\n            The function handle :math:`\\mathbf{F}(\\mathbf{u})` which accepts a\n            (``n_edges``) :class:`numpy.ndarray` :math:`\\mathbf{u}`. The function\n            returns a (``n_edges``, ``n_params``) :class:`scipy.sparse.csr_matrix`.\n        \"\"\"\n        raise NotImplementedError(\n            f\"get_edge_inner_product_surface_deriv not implemented for {type(self)}\"\n        )\n\n    def get_face_inner_product_surface_deriv(\n        self,\n        model,\n        invert_model=False,\n        invert_matrix=False,\n        **kwargs,\n    ):\n        r\"\"\"Get a function handle to multiply a vector with derivative of face inner product surface matrix (or its inverse).\n\n        Let :math:`\\mathbf{M}(\\mathbf{m})` be the face inner product surface matrix\n        constructed with a set of diagnostic property parameters :math:`\\mathbf{m}`\n        (or its inverse) defined on mesh faces. **get_face_inner_product_surface_deriv**\n        constructs a function handle\n\n        .. math::\n            \\mathbf{F}(\\mathbf{u}) = \\mathbf{u}^T \\, \\frac{\\partial \\mathbf{M}(\\mathbf{m})}{\\partial \\mathbf{m}}\n\n        which accepts any numpy.array :math:`\\mathbf{u}` of shape (n_faces,). That is,\n        **get_face_inner_product_surface_deriv** constructs a function handle for computing\n        the dot product between a vector :math:`\\mathbf{u}` and the derivative of the\n        face inner product surface matrix (or its inverse) with respect to the property parameters.\n        When computed, :math:`\\mathbf{F}(\\mathbf{u})` returns a ``scipy.sparse.csr_matrix``\n        of shape (n_faces, n_param).\n\n        The function handle can only be created for isotropic diagnostic properties.\n\n        Parameters\n        ----------\n        model : (n_faces, ) numpy.ndarray\n            Parameters defining the diagnostic property values for every face in the mesh.\n        invert_model : bool, optional\n            The inverse of *model* is used as the diagnostic property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product surface matrix.\n\n        Returns\n        -------\n        function\n            The function handle :math:`\\mathbf{F}(\\mathbf{u})` which accepts a\n            (``n_faces``) :class:`numpy.ndarray` :math:`\\mathbf{u}`. The function\n            returns a (``n_faces``, ``n_params``) :class:`scipy.sparse.csr_matrix`.\n        \"\"\"\n        try:\n            A = sdiag(self.face_areas)\n        except NotImplementedError:\n            raise NotImplementedError(\n                f\"get_face_inner_product_surface_deriv not implemented for {type(self)}\"\n            )\n\n        if model is None:\n            tensorType = -1\n        elif is_scalar(model):\n            tensorType = 0\n        elif model.size == self.nF:\n            tensorType = 1\n        else:\n            raise ValueError(\n                \"Unexpected shape of tensor: {}\".format(model.shape),\n                \"Must be scalar or have length equal to total number of faces.\",\n            )\n\n        dMdprop = None\n\n        if invert_matrix or invert_model:\n            MI = self.get_face_inner_product_surface(\n                model,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n\n        if tensorType == 0:  # isotropic, constant\n            ones = sp.csr_matrix(\n                (np.ones(self.nF), (range(self.nF), np.zeros(self.nF))),\n                shape=(self.nF, 1),\n            )\n            if not invert_matrix and not invert_model:\n                dMdprop = A * ones\n            elif invert_matrix and invert_model:\n                dMdprop = sdiag(MI.diagonal() ** 2) * A * ones * sdiag(1.0 / model**2)\n            elif invert_model:\n                dMdprop = A * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = sdiag(-MI.diagonal() ** 2) * A\n\n        elif tensorType == 1:  # isotropic, variable in space\n            if not invert_matrix and not invert_model:\n                dMdprop = A\n            elif invert_matrix and invert_model:\n                dMdprop = sdiag(MI.diagonal() ** 2) * A * sdiag(1.0 / model**2)\n            elif invert_model:\n                dMdprop = A * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = sdiag(-MI.diagonal() ** 2) * A\n\n        if dMdprop is not None:\n\n            def innerProductDeriv(v):\n                return sdiag(v) * dMdprop\n\n            return innerProductDeriv\n        else:\n            return None\n\n    def get_edge_inner_product_line_deriv(\n        self,\n        model,\n        invert_model=False,\n        invert_matrix=False,\n        **kwargs,\n    ):\n        r\"\"\"Get a function handle to multiply a vector with derivative of edge inner product line matrix (or its inverse).\n\n        Let :math:`\\mathbf{M}(\\mathbf{m})` be the edge inner product line matrix\n        constructed with a set of diagnostic property parameters :math:`\\mathbf{m}`\n        (or its inverse) defined on mesh edges. **get_edge_inner_product_line_deriv**\n        constructs a function handle\n\n        .. math::\n            \\mathbf{F}(\\mathbf{u}) = \\mathbf{u}^T \\, \\frac{\\partial \\mathbf{M}(\\mathbf{m})}{\\partial \\mathbf{m}}\n\n        which accepts any numpy.array :math:`\\mathbf{u}` of shape (n_edges,). That is,\n        **get_edge_inner_product_line_deriv** constructs a function handle for computing\n        the dot product between a vector :math:`\\mathbf{u}` and the derivative of the\n        edge inner product line matrix (or its inverse) with respect to the diagnostic parameters.\n        When computed, :math:`\\mathbf{F}(\\mathbf{u})` returns a ``scipy.sparse.csr_matrix``\n        of shape (n_edges, n_param).\n\n        The function handle can only be created for isotropic diagnostic properties.\n\n        Parameters\n        ----------\n        model : (n_edges, ) numpy.ndarray\n            Parameters defining the diagnostic property values for every edge in the mesh.\n        invert_model : bool, optional\n            The inverse of *model* is used as the diagnostic property.\n        invert_matrix : bool, optional\n            Returns the inverse of the inner product line matrix.\n\n        Returns\n        -------\n        function\n            The function handle :math:`\\mathbf{F}(\\mathbf{u})` which accepts a\n            (``n_edges``) :class:`numpy.ndarray` :math:`\\mathbf{u}`. The function\n            returns a (``n_edges``, ``n_params``) :class:`scipy.sparse.csr_matrix`.\n        \"\"\"\n        try:\n            L = sdiag(self.edge_lengths)\n        except NotImplementedError:\n            raise NotImplementedError(\n                f\"get_edge_inner_product_line_deriv not implemented for {type(self)}\"\n            )\n\n        if model is None:\n            tensorType = -1\n        elif is_scalar(model):\n            tensorType = 0\n        elif model.size == self.nE:\n            tensorType = 1\n        else:\n            raise ValueError(\n                \"Unexpected shape of tensor: {}.\".format(model.shape),\n                \"Must be scalar or have length equal to total number of edges: {}.\".format(\n                    self.nE\n                ),\n            )\n\n        dMdprop = None\n\n        if invert_matrix or invert_model:\n            MI = self.get_edge_inner_product_line(\n                model,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n\n        if tensorType == 0:  # isotropic, constant\n            ones = sp.csr_matrix(\n                (np.ones(self.nE), (range(self.nE), np.zeros(self.nE))),\n                shape=(self.nE, 1),\n            )\n            if not invert_matrix and not invert_model:\n                dMdprop = L * ones\n            elif invert_matrix and invert_model:\n                dMdprop = sdiag(MI.diagonal() ** 2) * L * ones * sdiag(1.0 / model**2)\n            elif invert_model:\n                dMdprop = L * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = sdiag(-MI.diagonal() ** 2) * L\n\n        elif tensorType == 1:  # isotropic, variable in space\n            if not invert_matrix and not invert_model:\n                dMdprop = L\n            elif invert_matrix and invert_model:\n                dMdprop = sdiag(MI.diagonal() ** 2) * L * sdiag(1.0 / model**2)\n            elif invert_model:\n                dMdprop = L * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = sdiag(-MI.diagonal() ** 2) * L\n\n        if dMdprop is not None:\n\n            def innerProductDeriv(v):\n                return sdiag(v) * dMdprop\n\n            return innerProductDeriv\n        else:\n            return None\n\n    # Averaging\n    @property\n    def average_face_to_cell(self):\n        r\"\"\"Averaging operator from faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on mesh faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_faces) scipy.sparse.csr_matrix\n            The scalar averaging operator from faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_f}` be a discrete scalar quantity that\n        lives on mesh faces. **average_face_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{fc}}` that projects\n        :math:`\\boldsymbol{\\phi_f}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{fc}} \\, \\boldsymbol{\\phi_f}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Afc @ phi_f\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the faces. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a scalar variable on faces\n\n        >>> phi_f = np.zeros(mesh.nF)\n        >>> xy = mesh.faces\n        >>> phi_f[(xy[:, 1] > 0)] = 25.0\n        >>> phi_f[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n\n        >>> Afc = mesh.average_face_to_cell\n        >>> phi_c = Afc @ phi_f\n\n        And finally plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi_f, ax=ax1, v_type=\"F\")\n        >>> ax1.set_title(\"Variable at faces\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Afc, ms=1)\n        >>> ax1.set_title(\"Face Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_face_to_cell not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_face_to_cell_vector(self):\n        r\"\"\"Averaging operator from faces to cell centers (vector quantities).\n\n        This property constructs the averaging operator that independently maps the\n        Cartesian components of vector quantities from faces to cell centers.\n        This averaging operators is used when a discrete vector quantity defined on mesh faces\n        must be approximated at cell centers. Once constructed, the operator is\n        stored permanently as a property of the mesh.\n\n        Be aware that the Cartesian components of the original vector\n        are defined on their respective faces; e.g. the x-component lives\n        on x-faces. However, the x, y and z components are being averaged\n        separately to cell centers. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_c = Afc @ u_f\n\n        Returns\n        -------\n        (dim * n_cells, n_faces) scipy.sparse.csr_matrix\n            The vector averaging operator from faces to cell centers. Since we\n            are averaging a vector quantity to cell centers, the first dimension\n            of the operator is the mesh dimension times the number of cells.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_f}` be the discrete representation of a vector\n        quantity whose Cartesian components are defined on their respective faces;\n        e.g. :math:`u_x` is defined on x-faces.\n        **average_face_to_cell_vector** constructs a discrete linear operator\n        :math:`\\mathbf{A_{fc}}` that projects each Cartesian component of\n        :math:`\\mathbf{u_f}` independently to cell centers, i.e.:\n\n        .. math::\n            \\mathbf{u_c} = \\mathbf{A_{fc}} \\, \\mathbf{u_f}\n\n        where :math:`\\mathbf{u_c}` is a discrete vector quantity whose Cartesian\n        components defined at the cell centers and organized into a 1D array of\n        the form np.r_[ux, uy, uz]. For each cell, and for each Cartesian component,\n        we are simply taking the average of the values\n        defined on the cell's corresponding faces and placing the result at\n        the cell's center.\n\n        Examples\n        --------\n        Here we compute the values of a vector function discretized to the mesh faces.\n        We then create an averaging operator to approximate the function at cell centers.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> h = 0.5 * np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a discrete vector on mesh faces\n\n        >>> faces_x = mesh.faces_x\n        >>> faces_y = mesh.faces_y\n        >>> u_fx = -(faces_x[:, 1] / np.sqrt(np.sum(faces_x ** 2, axis=1))) * np.exp(\n        >>>     -(faces_x[:, 0] ** 2 + faces_x[:, 1] ** 2) / 6 ** 2\n        >>> )\n        >>> u_fy = (faces_y[:, 0] / np.sqrt(np.sum(faces_y ** 2, axis=1))) * np.exp(\n        >>>     -(faces_y[:, 0] ** 2 + faces_y[:, 1] ** 2) / 6 ** 2\n        >>> )\n        >>> u_f = np.r_[u_fx, u_fy]\n\n        Next, we construct the averaging operator and apply it to\n        the discrete vector quantity to approximate the value at cell centers.\n\n        >>> Afc = mesh.average_face_to_cell_vector\n        >>> u_c = Afc @ u_f\n\n        And finally, plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(u_f, ax=ax1, v_type=\"F\", view='vec')\n        >>> ax1.set_title(\"Variable at faces\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(u_c, ax=ax2, v_type=\"CCv\", view='vec')\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Afc, ms=1)\n        >>> ax1.set_title(\"Face Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Vector Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_face_to_cell_vector not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_cell_to_face(self):\n        r\"\"\"Averaging operator from cell centers to faces (scalar quantities).\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to face. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to faces. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_faces, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from cell centers to faces\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_c}` be a discrete scalar quantity that\n        lives at cell centers. **average_cell_to_face** constructs a discrete\n        linear operator :math:`\\mathbf{A_{cf}}` that projects\n        :math:`\\boldsymbol{\\phi_c}` to faces, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_f} = \\mathbf{A_{cf}} \\, \\boldsymbol{\\phi_c}\n\n        where :math:`\\boldsymbol{\\phi_f}` approximates the value of the scalar\n        quantity at the faces. For each face, we are performing a weighted average\n        between the values at adjacent cell centers. In 1D, where adjacent cells\n        :math:`i` and :math:`i+1` have widths :math:`h_i` and :math:`h_{i+1}`,\n        :math:`\\phi` on face is approximated by:\n\n        .. math::\n            \\phi_{i \\! + \\! 1/2} \\approx \\frac{h_{i+1} \\phi_i + h_i \\phi_{i+1}}{h_i + h_{i+1}}\n\n        On boundary faces, nearest neighbour is used to extrapolate the value\n        from the nearest cell center. Once the operator is construct, the averaging\n        is implemented as a matrix vector product, i.e.::\n\n            phi_f = Acf @ phi_c\n\n        Examples\n        --------\n        Here we compute the values of a scalar function at cell centers. We then create\n        an averaging operator to approximate the function on the faces. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Create a scalar variable at cell centers\n\n        >>> phi_c = np.zeros(mesh.nC)\n        >>> xy = mesh.cell_centers\n        >>> phi_c[(xy[:, 1] > 0)] = 25.0\n        >>> phi_c[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at the faces.\n\n        >>> Acf = mesh.average_cell_to_face\n        >>> phi_f = Acf @ phi_c\n\n        Plot the results\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi_c, ax=ax1, v_type=\"CC\")\n        >>> ax1.set_title(\"Variable at cell centers\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_f, ax=ax2, v_type=\"F\")\n        >>> ax2.set_title(\"Averaged to faces\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator.\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Acf, ms=1)\n        >>> ax1.set_title(\"Cell Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_cell_to_face not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_cell_vector_to_face(self):\n        r\"\"\"Averaging operator from cell centers to faces (vector quantities).\n\n        This property constructs the averaging operator that independently maps the\n        Cartesian components of vector quantities from cell centers to faces.\n        This averaging operators is used when a discrete vector quantity defined at\n        cell centers must be approximated on the faces. Once constructed, the operator is\n        stored permanently as a property of the mesh.\n\n        Be aware that the Cartesian components of the original vector\n        are defined seperately at cell centers in a 1D numpy.array organized [ux, uy, uz].\n        Once projected to faces, the Cartesian components are defined on their respective\n        faces; e.g. the x-component lives on x-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_f = Acf @ u_c\n\n        Returns\n        -------\n        (n_faces, dim * n_cells) scipy.sparse.csr_matrix\n            The vector averaging operator from cell centers to faces. Since we\n            are averaging a vector quantity from cell centers, the second dimension\n            of the operator is the mesh dimension times the number of cells.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_c}` be the discrete representation of a vector\n        quantity whose Cartesian components are defined separately at cell centers.\n        **average_cell_vector_to_face** constructs a discrete linear operator\n        :math:`\\mathbf{A_{cf}}` that projects each Cartesian component of\n        :math:`\\mathbf{u_c}` to the faces, i.e.:\n\n        .. math::\n            \\mathbf{u_f} = \\mathbf{A_{cf}} \\, \\mathbf{u_c}\n\n        where :math:`\\mathbf{u_f}` is the discrete vector quantity whose Cartesian\n        components are approximated on their respective cell faces; e.g. the x-component is\n        approximated on x-faces. For each face (x, y or z), we are simply taking a weighted average\n        between the values of the correct Cartesian component at the corresponding cell centers.\n\n        E.g. for the x-component, which is projected to x-faces, the weighted average on\n        a 2D mesh would be:\n\n        .. math::\n            u_x(i \\! + \\! 1/2, j) = \\frac{h_{i+1} u_x (i,j) + h_i u_x(i \\! + \\! 1,j)}{hx_i + hx_{i+1}}\n\n        where :math:`h_i` and :math:`h_{i+1}` represent the cell respective cell widths\n        in the x-direction. For boundary faces, nearest neighbor is used to extrapolate\n        the values.\n\n        Examples\n        --------\n        Here we compute the values of a vector function discretized to cell centers.\n        We then create an averaging operator to approximate the function on the faces.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = 0.5 * np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a discrete vector at cell centers,\n\n        >>> centers = mesh.cell_centers\n        >>> u_x = -(centers[:, 1] / np.sqrt(np.sum(centers ** 2, axis=1))) * np.exp(\n        ...     -(centers[:, 0] ** 2 + centers[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> u_y = (centers[:, 0] / np.sqrt(np.sum(centers ** 2, axis=1))) * np.exp(\n        ...     -(centers[:, 0] ** 2 + centers[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> u_c = np.r_[u_x, u_y]\n\n        Next, we construct the averaging operator and apply it to\n        the discrete vector quantity to approximate the value on the faces.\n\n        >>> Acf = mesh.average_cell_vector_to_face\n        >>> u_f = Acf @ u_c\n\n        And plot the results\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(u_c, ax=ax1, v_type=\"CCv\", view='vec')\n        >>> ax1.set_title(\"Variable at faces\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(u_f, ax=ax2, v_type=\"F\", view='vec')\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Acf, ms=1)\n        >>> ax1.set_title(\"Cell Vector Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_cell_vector_to_face not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_cell_to_edge(self):\n        r\"\"\"Averaging operator from cell centers to edges (scalar quantities).\n\n        This property constructs an averaging operator that maps scalar\n        quantities from cell centers to edge. This averaging operator is\n        used when a discrete scalar quantity defined cell centers must be\n        projected to edges. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_edges, n_cells) scipy.sparse.csr_matrix\n            The scalar averaging operator from edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_c}` be a discrete scalar quantity that\n        lives at cell centers. **average_cell_to_edge** constructs a discrete\n        linear operator :math:`\\mathbf{A_{ce}}` that projects\n        :math:`\\boldsymbol{\\phi_c}` to edges, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_e} = \\mathbf{A_{ce}} \\, \\boldsymbol{\\phi_c}\n\n        where :math:`\\boldsymbol{\\phi_e}` approximates the value of the scalar\n        quantity at the edges. For each edge, we are performing a weighted average\n        between the values at adjacent cell centers. In 1D, where adjacent cells\n        :math:`i` and :math:`i+1` have widths :math:`h_i` and :math:`h_{i+1}`,\n        :math:`\\phi` on edge (node location in 1D) is approximated by:\n\n        .. math::\n            \\phi_{i \\! + \\! 1/2} \\approx \\frac{h_{i+1} \\phi_i + h_i \\phi_{i+1}}{h_i + h_{i+1}}\n\n        On boundary edges, nearest neighbour is used to extrapolate the value\n        from the nearest cell center. Once the operator is construct, the averaging\n        is implemented as a matrix vector product, i.e.::\n\n            phi_e = Ace @ phi_c\n\n        Examples\n        --------\n        Here we compute the values of a scalar function at cell centers. We then create\n        an averaging operator to approximate the function on the edges. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a scalar variable at cell centers\n\n        >>> phi_c = np.zeros(mesh.nC)\n        >>> xy = mesh.cell_centers\n        >>> phi_c[(xy[:, 1] > 0)] = 25.0\n        >>> phi_c[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at the edges.\n\n        >>> Ace = mesh.average_cell_to_edge\n        >>> phi_e = Ace @ phi_c\n\n        And plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi_c, ax=ax1, v_type=\"CC\")\n        >>> ax1.set_title(\"Variable at cell centers\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_e, ax=ax2, v_type=\"E\")\n        >>> ax2.set_title(\"Averaged to edges\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator.\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Ace, ms=1)\n        >>> ax1.set_title(\"Cell Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Edge Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_cell_to_edge not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_edge_to_cell(self):\n        r\"\"\"Averaging operator from edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on mesh edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges) scipy.sparse.csr_matrix\n            The scalar averaging operator from edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_e}` be a discrete scalar quantity that\n        lives on mesh edges. **average_edge_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{ec}}` that projects\n        :math:`\\boldsymbol{\\phi_e}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{ec}} \\, \\boldsymbol{\\phi_e}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Aec @ phi_e\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the edges. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a scalar variable on edges,\n\n        >>> phi_e = np.zeros(mesh.nE)\n        >>> xy = mesh.edges\n        >>> phi_e[(xy[:, 1] > 0)] = 25.0\n        >>> phi_e[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n\n        >>> Aec = mesh.average_edge_to_cell\n        >>> phi_c = Aec @ phi_e\n\n        And plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi_e, ax=ax1, v_type=\"E\")\n        >>> ax1.set_title(\"Variable at edges\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Aec, ms=1)\n        >>> ax1.set_title(\"Edge Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_edge_to_cell not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_edge_to_cell_vector(self):\n        r\"\"\"Averaging operator from edges to cell centers (vector quantities).\n\n        This property constructs the averaging operator that independently maps the\n        Cartesian components of vector quantities from edges to cell centers.\n        This averaging operators is used when a discrete vector quantity defined on mesh edges\n        must be approximated at cell centers. Once constructed, the operator is\n        stored permanently as a property of the mesh.\n\n        Be aware that the Cartesian components of the original vector\n        are defined on their respective edges; e.g. the x-component lives\n        on x-edges. However, the x, y and z components are being averaged\n        separately to cell centers. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_c = Aec @ u_e\n\n        Returns\n        -------\n        (dim * n_cells, n_edges) scipy.sparse.csr_matrix\n            The vector averaging operator from edges to cell centers. Since we\n            are averaging a vector quantity to cell centers, the first dimension\n            of the operator is the mesh dimension times the number of cells.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_e}` be the discrete representation of a vector\n        quantity whose Cartesian components are defined on their respective edges;\n        e.g. :math:`u_x` is defined on x-edges.\n        **average_edge_to_cell_vector** constructs a discrete linear operator\n        :math:`\\mathbf{A_{ec}}` that projects each Cartesian component of\n        :math:`\\mathbf{u_e}` independently to cell centers, i.e.:\n\n        .. math::\n            \\mathbf{u_c} = \\mathbf{A_{ec}} \\, \\mathbf{u_e}\n\n        where :math:`\\mathbf{u_c}` is a discrete vector quantity whose Cartesian\n        components defined at the cell centers and organized into a 1D array of\n        the form np.r_[ux, uy, uz]. For each cell, and for each Cartesian component,\n        we are simply taking the average of the values\n        defined on the cell's corresponding edges and placing the result at\n        the cell's center.\n\n        Examples\n        --------\n        Here we compute the values of a vector function discretized to the edges.\n        We then create an averaging operator to approximate the function at cell centers.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = 0.5 * np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a discrete vector on mesh edges\n\n        >>> edges_x = mesh.edges_x\n        >>> edges_y = mesh.edges_y\n        >>> u_ex = -(edges_x[:, 1] / np.sqrt(np.sum(edges_x ** 2, axis=1))) * np.exp(\n        ...     -(edges_x[:, 0] ** 2 + edges_x[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> u_ey = (edges_y[:, 0] / np.sqrt(np.sum(edges_y ** 2, axis=1))) * np.exp(\n        ...     -(edges_y[:, 0] ** 2 + edges_y[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> u_e = np.r_[u_ex, u_ey]\n\n        Next, we construct the averaging operator and apply it to\n        the discrete vector quantity to approximate the value at cell centers.\n\n        >>> Aec = mesh.average_edge_to_cell_vector\n        >>> u_c = Aec @ u_e\n\n        And plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(u_e, ax=ax1, v_type=\"E\", view='vec')\n        >>> ax1.set_title(\"Variable at edges\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(u_c, ax=ax2, v_type=\"CCv\", view='vec')\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Aec, ms=1)\n        >>> ax1.set_title(\"Edge Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Vector Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_edge_to_cell_vector not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_edge_to_face(self):\n        r\"\"\"Averaging operator from edges to faces.\n\n        This property constructs the averaging operator that maps uantities from edges to faces.\n        This averaging operators is used when a discrete quantity defined on mesh edges\n        must be approximated at faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            u_f = Aef @ u_e\n\n        Once constructed, the operator is stored permanently as a property of the mesh.\n\n        Returns\n        -------\n        (n_faces, n_edges) scipy.sparse.csr_matrix\n            The averaging operator from edges to faces.\n\n        Notes\n        -----\n        Let :math:`\\mathbf{u_e}` be the discrete representation of aquantity whose\n        that is defined on the edges. **average_edge_to_face**\n        constructs a discrete linear operator :math:`\\mathbf{A_{ef}}` that\n        projects :math:`\\mathbf{u_e}` to its corresponding face, i.e.:\n\n        .. math::\n            \\mathbf{u_f} = \\mathbf{A_{ef}} \\, \\mathbf{u_e}\n\n        where :math:`\\mathbf{u_f}` is a quantity defined on the respective faces.\n\n        Examples\n        --------\n        Here we compute the values of a vector function discretized to the edges.\n        We then create an averaging operator to approximate the function on\n        the faces.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = 0.5 * np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Create a discrete vector on mesh edges\n\n        >>> edges = mesh.edges\n        >>> u_ex = -(edges[:, 1] / np.sqrt(np.sum(edges ** 2, axis=1))) * np.exp(\n        ...     -(edges[:, 0] ** 2 + edges[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> u_ey = (edges[:, 0] / np.sqrt(np.sum(edges ** 2, axis=1))) * np.exp(\n        ...     -(edges[:, 0] ** 2 + edges[:, 1] ** 2) / 6 ** 2\n        ... )\n        >>> u_e = np.c_[u_ex, u_ey]\n\n        Next, we construct the averaging operator and apply it to\n        the discrete vector quantity to approximate the value at the faces.\n\n        >>> Aef = mesh.average_edge_to_face\n        >>> u_f = Aef @ u_e\n\n        Plot the results,\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> proj_ue = mesh.project_edge_vector(u_e)\n        >>> mesh.plot_image(proj_ue, ax=ax1, v_type=\"E\", view='vec')\n        >>> ax1.set_title(\"Variable at edges\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> proj_uf = mesh.project_face_vector(u_f)\n        >>> mesh.plot_image(proj_uf, ax=ax2, v_type=\"F\", view='vec')\n        >>> ax2.set_title(\"Averaged to faces\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Aef, ms=1)\n        >>> ax1.set_title(\"Edge Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_edge_to_face not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_node_to_cell(self):\n        r\"\"\"Averaging operator from nodes to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_n}` be a discrete scalar quantity that\n        lives on mesh nodes. **average_node_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{nc}}` that projects\n        :math:`\\boldsymbol{\\phi_f}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{nc}} \\, \\boldsymbol{\\phi_n}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its nodes. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Anc @ phi_n\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the nodes. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we Create a scalar variable on nodes\n\n        >>> phi_n = np.zeros(mesh.nN)\n        >>> xy = mesh.nodes\n        >>> phi_n[(xy[:, 1] > 0)] = 25.0\n        >>> phi_n[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n\n        >>> Anc = mesh.average_node_to_cell\n        >>> phi_c = Anc @ phi_n\n\n        Plot the results,\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi_n, ax=ax1, v_type=\"N\")\n        >>> ax1.set_title(\"Variable at nodes\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Anc, ms=1)\n        >>> ax1.set_title(\"Node Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_node_to_cell not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_node_to_edge(self):\n        r\"\"\"Averaging operator from nodes to edges (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges; scalar at edges is organized in a 1D numpy.array\n        of the form [x-edges, y-edges, z-edges]. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to edges. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_edges, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to edges\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_n}` be a discrete scalar quantity that\n        lives on mesh nodes. **average_node_to_edge** constructs a discrete\n        linear operator :math:`\\mathbf{A_{ne}}` that projects\n        :math:`\\boldsymbol{\\phi_n}` to edges, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_e} = \\mathbf{A_{ne}} \\, \\boldsymbol{\\phi_n}\n\n        where :math:`\\boldsymbol{\\phi_e}` approximates the value of the scalar\n        quantity at edges. For each edge, we are simply averaging\n        the values defined on the nodes it connects. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_e = Ane @ phi_n\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the nodes. We then create\n        an averaging operator to approximate the function at the edges. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a scalar variable on nodes,\n\n        >>> phi_n = np.zeros(mesh.nN)\n        >>> xy = mesh.nodes\n        >>> phi_n[(xy[:, 1] > 0)] = 25.0\n        >>> phi_n[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value on the edges.\n\n        >>> Ane = mesh.average_node_to_edge\n        >>> phi_e = Ane @ phi_n\n\n        Plot the results,\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi_n, ax=ax1, v_type=\"N\")\n        >>> ax1.set_title(\"Variable at nodes\")\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_e, ax=ax2, v_type=\"E\")\n        >>> ax2.set_title(\"Averaged to edges\")\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Ane, ms=1)\n        >>> ax1.set_title(\"Node Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Edge Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_node_to_edge not implemented for {type(self)}\"\n        )\n\n    @property\n    def average_node_to_face(self):\n        r\"\"\"Averaging operator from nodes to faces (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from nodes to edges; scalar at faces is organized in a 1D numpy.array\n        of the form [x-faces, y-faces, z-faces]. This averaging operator is\n        used when a discrete scalar quantity defined on mesh nodes must be\n        projected to faces. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_faces, n_nodes) scipy.sparse.csr_matrix\n            The scalar averaging operator from nodes to faces\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_n}` be a discrete scalar quantity that\n        lives on mesh nodes. **average_node_to_face** constructs a discrete\n        linear operator :math:`\\mathbf{A_{nf}}` that projects\n        :math:`\\boldsymbol{\\phi_n}` to faces, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_f} = \\mathbf{A_{nf}} \\, \\boldsymbol{\\phi_n}\n\n        where :math:`\\boldsymbol{\\phi_f}` approximates the value of the scalar\n        quantity at faces. For each face, we are simply averaging the values at\n        the nodes which outline the face. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_f = Anf @ phi_n\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the nodes. We then create\n        an averaging operator to approximate the function at the faces. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we, create a scalar variable on nodes\n\n        >>> phi_n = np.zeros(mesh.nN)\n        >>> xy = mesh.nodes\n        >>> phi_n[(xy[:, 1] > 0)] = 25.0\n        >>> phi_n[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value on the faces.\n\n        >>> Anf = mesh.average_node_to_face\n        >>> phi_f = Anf @ phi_n\n\n        Plot the results,\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi_n, ax=ax1, v_type=\"N\")\n        >>> ax1.set_title(\"Variable at nodes\")\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_f, ax=ax2, v_type=\"F\")\n        >>> ax2.set_title(\"Averaged to faces\")\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Anf, ms=1)\n        >>> ax1.set_title(\"Node Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"average_node_to_face not implemented for {type(self)}\"\n        )\n\n    @property\n    def project_face_to_boundary_face(self):\n        r\"\"\"Projection matrix from all faces to boundary faces.\n\n        Constructs and returns a matrix :math:`\\mathbf{P}` that projects from\n        all mesh faces to boundary faces. That is, for a discrete vector\n        :math:`\\mathbf{u}` that lives on the faces, the values on the boundary\n        faces :math:`\\mathbf{u_b}` can be extracted via the following\n        matrix-vector product::\n\n            ub = P @ u\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            (n_boundary_faces, n_faces) Projection matrix with shape\n        \"\"\"\n        raise NotImplementedError(\n            f\"project_face_to_boundary_face not implemented for {type(self)}\"\n        )\n\n    @property\n    def project_edge_to_boundary_edge(self):\n        r\"\"\"Projection matrix from all edges to boundary edges.\n\n        Constructs and returns a matrix :math:`\\mathbf{P}` that projects from\n        all mesh edges to boundary edges. That is, for a discrete vector\n        :math:`\\mathbf{u}` that lives on the edges, the values on the boundary\n        edges :math:`\\mathbf{u_b}` can be extracted via the following\n        matrix-vector product::\n\n            ub = P @ u\n\n        Returns\n        -------\n        (n_boundary_edges, n_edges) scipy.sparse.csr_matrix\n            Projection matrix with shape\n        \"\"\"\n        raise NotImplementedError(\n            f\"project_edge_to_boundary_edge not implemented for {type(self)}\"\n        )\n\n    @property\n    def project_node_to_boundary_node(self):\n        r\"\"\"Projection matrix from all nodes to boundary nodes.\n\n        Constructs and returns a matrix :math:`\\mathbf{P}` that projects from\n        all mesh nodes to boundary nodes. That is, for a discrete scalar\n        :math:`\\mathbf{u}` that lives on the nodes, the values on the boundary\n        nodes :math:`\\mathbf{u_b}` can be extracted via the following\n        matrix-vector product::\n\n            ub = P @ u\n\n        Returns\n        -------\n        (n_boundary_nodes, n_nodes) scipy.sparse.csr_matrix\n            Projection matrix with shape\n        \"\"\"\n        raise NotImplementedError(\n            f\"project_node_to_boundary_node not implemented for {type(self)}\"\n        )\n\n    def closest_points_index(self, locations, grid_loc=\"CC\", discard=False):\n        \"\"\"Find the indicies for the nearest grid location for a set of points.\n\n        Parameters\n        ----------\n        locations : (n, dim) numpy.ndarray\n            Points to query.\n        grid_loc : {'CC', 'N', 'Fx', 'Fy', 'Fz', 'Ex', 'Ex', 'Ey', 'Ez'}\n            Specifies the grid on which points are being moved to.\n        discard : bool, optional\n            Whether to discard the intenally created `scipy.spatial.KDTree`.\n\n        Returns\n        -------\n        (n ) numpy.ndarray of int\n            Vector of length *n* containing the indicies for the closest\n            respective cell center, node, face or edge.\n\n        Examples\n        --------\n        Here we define a set of random (x, y) locations and find the closest\n        cell centers and nodes on a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> from discretize.utils import closest_points_index\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = 2*np.ones(5)\n        >>> mesh = TensorMesh([h, h], x0='00')\n\n        Define some random locations, grid cell centers and grid nodes,\n\n        >>> xy_random = np.random.uniform(0, 10, size=(4,2))\n        >>> xy_centers = mesh.cell_centers\n        >>> xy_nodes = mesh.nodes\n\n        Find indicies of closest cell centers and nodes,\n\n        >>> ind_centers = mesh.closest_points_index(xy_random, 'cell_centers')\n        >>> ind_nodes = mesh.closest_points_index(xy_random, 'nodes')\n\n        Plot closest cell centers and nodes\n\n        >>> fig = plt.figure(figsize=(5, 5))\n        >>> ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])\n        >>> mesh.plot_grid(ax=ax)\n        >>> ax.scatter(xy_random[:, 0], xy_random[:, 1], 50, 'k')\n        >>> ax.scatter(xy_centers[ind_centers, 0], xy_centers[ind_centers, 1], 50, 'r')\n        >>> ax.scatter(xy_nodes[ind_nodes, 0], xy_nodes[ind_nodes, 1], 50, 'b')\n        >>> plt.show()\n        \"\"\"\n        locations = as_array_n_by_dim(locations, self.dim)\n\n        grid_loc = self._parse_location_type(grid_loc)\n        tree_name = f\"_{grid_loc}_tree\"\n\n        tree = getattr(self, tree_name, None)\n        if tree is None:\n            grid = as_array_n_by_dim(getattr(self, grid_loc), self.dim)\n            tree = KDTree(grid)\n        _, ind = tree.query(locations)\n\n        if not discard:\n            setattr(self, tree_name, tree)\n\n        return ind\n\n    def point2index(self, locs):\n        \"\"\"Find cells that contain the given points.\n\n        Returns an array of index values of the cells that contain the given\n        points\n\n        Parameters\n        ----------\n        locs: (N, dim) array_like\n            points to search for the location of\n\n        Returns\n        -------\n        (N) array_like of int\n            Cell indices that contain the points\n        \"\"\"\n        raise NotImplementedError(f\"point2index not implemented for {type(self)}\")\n\n    def get_interpolation_matrix(\n        self, loc, location_type=\"cell_centers\", zeros_outside=False, **kwargs\n    ):\n        \"\"\"Construct a linear interpolation matrix from mesh.\n\n        This method constructs a linear interpolation matrix from tensor locations\n        (nodes, cell-centers, faces, etc...) on the mesh to a set of arbitrary locations.\n\n        Parameters\n        ----------\n        loc : (n_pts, dim) numpy.ndarray\n            Location of points being to interpolate to. Must have same dimensions as the mesh.\n        location_type : str, optional\n            Tensor locations on the mesh being interpolated from. *location_type* must be one of:\n\n            - 'Ex', 'edges_x'           -> x-component of field defined on x edges\n            - 'Ey', 'edges_y'           -> y-component of field defined on y edges\n            - 'Ez', 'edges_z'           -> z-component of field defined on z edges\n            - 'Fx', 'faces_x'           -> x-component of field defined on x faces\n            - 'Fy', 'faces_y'           -> y-component of field defined on y faces\n            - 'Fz', 'faces_z'           -> z-component of field defined on z faces\n            - 'N', 'nodes'              -> scalar field defined on nodes\n            - 'CC', 'cell_centers'      -> scalar field defined on cell centers\n            - 'CCVx', 'cell_centers_x'  -> x-component of vector field defined on cell centers\n            - 'CCVy', 'cell_centers_y'  -> y-component of vector field defined on cell centers\n            - 'CCVz', 'cell_centers_z'  -> z-component of vector field defined on cell centers\n        zeros_outside : bool, optional\n            If *False*, nearest neighbour is used to compute the interpolate value\n            at locations outside the mesh. If *True* , values at locations outside\n            the mesh will be zero.\n\n        Returns\n        -------\n        (n_pts, n_loc_type) scipy.sparse.csr_matrix\n            A sparse matrix which interpolates the specified tensor quantity on mesh to\n            the set of specified locations.\n\n\n        Examples\n        --------\n        Here is a 1D example where a function evaluated on the nodes\n        is interpolated to a set of random locations. To compare the accuracy, the\n        function is evaluated at the set of random locations.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> rng = np.random.default_rng(14)\n\n        >>> locs = rng.random(50)*0.8+0.1\n        >>> dense = np.linspace(0, 1, 200)\n        >>> fun = lambda x: np.cos(2*np.pi*x)\n\n        >>> hx = 0.125 * np.ones(8)\n        >>> mesh1D = TensorMesh([hx])\n        >>> Q = mesh1D.get_interpolation_matrix(locs, 'nodes')\n\n        >>> plt.figure(figsize=(5, 3))\n        >>> plt.plot(dense, fun(dense), ':', c=\"C0\", lw=3, label=\"True Function\")\n        >>> plt.plot(mesh1D.nodes, fun(mesh1D.nodes), 's', c=\"C0\", ms=8, label=\"True sampled\")\n        >>> plt.plot(locs, Q*fun(mesh1D.nodes), 'o', ms=4, label=\"Interpolated\")\n        >>> plt.legend()\n        >>> plt.show()\n\n        Here, demonstrate a similar example on a 2D mesh using a 2D Gaussian distribution.\n        We interpolate the Gaussian from the nodes to cell centers and examine the relative\n        error.\n\n        >>> hx = np.ones(10)\n        >>> hy = np.ones(10)\n        >>> mesh2D = TensorMesh([hx, hy], x0='CC')\n        >>> def fun(x, y):\n        ...     return np.exp(-(x**2 + y**2)/2**2)\n\n        >>> nodes = mesh2D.nodes\n        >>> val_nodes = fun(nodes[:, 0], nodes[:, 1])\n        >>> centers = mesh2D.cell_centers\n        >>> val_centers = fun(centers[:, 0], centers[:, 1])\n        >>> A = mesh2D.get_interpolation_matrix(centers, 'nodes')\n        >>> val_interp = A.dot(val_nodes)\n\n        >>> fig = plt.figure(figsize=(11,3.3))\n        >>> clim = (0., 1.)\n        >>> ax1 = fig.add_subplot(131)\n        >>> ax2 = fig.add_subplot(132)\n        >>> ax3 = fig.add_subplot(133)\n        >>> mesh2D.plot_image(val_centers, ax=ax1, clim=clim)\n        >>> mesh2D.plot_image(val_interp, ax=ax2, clim=clim)\n        >>> mesh2D.plot_image(val_centers-val_interp, ax=ax3, clim=clim)\n        >>> ax1.set_title('Analytic at Centers')\n        >>> ax2.set_title('Interpolated from Nodes')\n        >>> ax3.set_title('Relative Error')\n        >>> plt.show()\n        \"\"\"\n        raise NotImplementedError(\n            f\"get_interpolation_matrix not implemented for {type(self)}\"\n        )\n\n    def _parse_location_type(self, location_type):\n        if len(location_type) == 0:\n            return location_type\n        elif location_type[0] == \"F\":\n            if len(location_type) > 1:\n                return \"faces_\" + location_type[-1]\n            else:\n                return \"faces\"\n        elif location_type[0] == \"E\":\n            if len(location_type) > 1:\n                return \"edges_\" + location_type[-1]\n            else:\n                return \"edges\"\n        elif location_type[0] == \"N\":\n            return \"nodes\"\n        elif location_type[0] == \"C\":\n            if len(location_type) > 2:\n                return \"cell_centers_\" + location_type[-1]\n            else:\n                return \"cell_centers\"\n        else:\n            return location_type\n\n    # DEPRECATED\n    normals = deprecate_property(\n        \"face_normals\", \"normals\", removal_version=\"1.0.0\", error=True\n    )\n    tangents = deprecate_property(\n        \"edge_tangents\", \"tangents\", removal_version=\"1.0.0\", error=True\n    )\n    projectEdgeVector = deprecate_method(\n        \"project_edge_vector\",\n        \"projectEdgeVector\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    projectFaceVector = deprecate_method(\n        \"project_face_vector\",\n        \"projectFaceVector\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    getInterpolationMat = deprecate_method(\n        \"get_interpolation_matrix\",\n        \"getInterpolationMat\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    nodalGrad = deprecate_property(\n        \"nodal_gradient\", \"nodalGrad\", removal_version=\"1.0.0\", error=True\n    )\n    nodalLaplacian = deprecate_property(\n        \"nodal_laplacian\", \"nodalLaplacian\", removal_version=\"1.0.0\", error=True\n    )\n    faceDiv = deprecate_property(\n        \"face_divergence\", \"faceDiv\", removal_version=\"1.0.0\", error=True\n    )\n    edgeCurl = deprecate_property(\n        \"edge_curl\", \"edgeCurl\", removal_version=\"1.0.0\", error=True\n    )\n    getFaceInnerProduct = deprecate_method(\n        \"get_face_inner_product\",\n        \"getFaceInnerProduct\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    getEdgeInnerProduct = deprecate_method(\n        \"get_edge_inner_product\",\n        \"getEdgeInnerProduct\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    getFaceInnerProductDeriv = deprecate_method(\n        \"get_face_inner_product_deriv\",\n        \"getFaceInnerProductDeriv\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    getEdgeInnerProductDeriv = deprecate_method(\n        \"get_edge_inner_product_deriv\",\n        \"getEdgeInnerProductDeriv\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    vol = deprecate_property(\"cell_volumes\", \"vol\", removal_version=\"1.0.0\", error=True)\n    area = deprecate_property(\"face_areas\", \"area\", removal_version=\"1.0.0\", error=True)\n    edge = deprecate_property(\n        \"edge_lengths\", \"edge\", removal_version=\"1.0.0\", error=True\n    )\n"
  },
  {
    "path": "discretize/base/base_regular_mesh.py",
    "content": "\"\"\"Base classes for all regular shaped meshes supported in ``discretize``.\"\"\"\n\nimport numpy as np\nfrom discretize.utils import mkvc, Identity\nfrom discretize.base.base_mesh import BaseMesh\nfrom discretize.utils.code_utils import deprecate_method\n\n\nclass BaseRegularMesh(BaseMesh):\n    \"\"\"Base Regular mesh class for the ``discretize`` package.\n\n    The ``BaseRegularMesh`` class does all the basic counting and organizing\n    you wouldn't want to do manually. ``BaseRegularMesh`` is a class that should\n    always be inherited by meshes with a regular structure; e.g.\n    :class:`~discretize.TensorMesh`, :class:`~discretize.CylindricalMesh`,\n    :class:`~discretize.TreeMesh` or :class:`~discretize.CurvilinearMesh`.\n\n    Parameters\n    ----------\n    shape_cells : array_like of int\n        number of cells in each dimension\n    origin : array_like of float, optional\n        origin of the bottom south west corner of the mesh, defaults to 0.\n    orientation : discretize.utils.Identity or array_like of float, optional\n        Orientation of the three major axes of the mesh; defaults to :class:`~discretize.utils.Identity`.\n        If provided, this must be an orthogonal matrix with the correct dimension.\n    reference_system : {'cartesian', 'cylindrical', 'spherical'}\n        Can also be a shorthand version of these, e.g. {'car[t]', 'cy[l]', 'sph'}\n    \"\"\"\n\n    _aliases = {\n        **BaseMesh._aliases,\n        \"nEx\": \"n_edges_x\",\n        \"nEy\": \"n_edges_y\",\n        \"nEz\": \"n_edges_z\",\n        \"vnE\": \"n_edges_per_direction\",\n        \"nFx\": \"n_faces_x\",\n        \"nFy\": \"n_faces_y\",\n        \"nFz\": \"n_faces_z\",\n        \"vnF\": \"n_faces_per_direction\",\n        \"vnC\": \"shape_cells\",\n    }\n\n    _items = {\"shape_cells\", \"origin\", \"orientation\", \"reference_system\"}\n\n    # Instantiate the class\n    def __init__(\n        self,\n        shape_cells,\n        origin=None,\n        orientation=None,\n        reference_system=None,\n        **kwargs,\n    ):\n        if \"n\" in kwargs:\n            shape_cells = kwargs.pop(\"n\")\n        if \"x0\" in kwargs:\n            origin = kwargs.pop(\"x0\")\n        axis_u = kwargs.pop(\"axis_u\", None)\n        axis_v = kwargs.pop(\"axis_v\", None)\n        axis_w = kwargs.pop(\"axis_w\", None)\n        if axis_u is not None and axis_v is not None and axis_w is not None:\n            orientation = np.array([axis_u, axis_v, axis_w])\n\n        shape_cells = tuple((int(val) for val in shape_cells))\n        self._shape_cells = shape_cells\n        # some default values\n        if origin is None:\n            origin = np.zeros(self.dim)\n        self.origin = origin\n\n        if orientation is None:\n            orientation = Identity()\n\n        self.orientation = orientation\n        if reference_system is None:\n            reference_system = \"cartesian\"\n        self.reference_system = reference_system\n        super().__init__(**kwargs)\n\n    @property\n    def origin(self):\n        \"\"\"Origin or 'anchor point' of the mesh.\n\n        For a mesh defined in Cartesian coordinates (e.g.\n        :class:`~discretize.TensorMesh`, :class:`~discretize.CylindricalMesh`,\n        :class:`~discretize.TreeMesh`), *origin* is the\n        bottom southwest corner. For a :class:`~discretize.CylindricalMesh`,\n        *origin* is the bottom of the axis of rotational symmetry\n        for the mesh (i.e. bottom of z-axis).\n\n        Returns\n        -------\n        (dim) numpy.ndarray of float\n            origin location\n        \"\"\"\n        return self._origin\n\n    @origin.setter\n    def origin(self, value):\n        # ensure the value is a numpy array\n        value = np.asarray(value, dtype=np.float64)\n        value = np.atleast_1d(value)\n        if len(value) != self.dim:\n            raise ValueError(\n                f\"origin and shape must be the same length, got {len(value)} and {self.dim}\"\n            )\n        self._origin = value\n\n    @property\n    def shape_cells(self):\n        \"\"\"Number of cells in each coordinate direction.\n\n        For meshes of class :class:`~discretize.TensorMesh`,\n        :class:`~discretize.CylindricalMesh` or :class:`~discretize.CurvilinearMesh`,\n        **shape_cells** returns the number of cells along each coordinate axis direction.\n        For mesh of class :class:`~discretize.TreeMesh`, *shape_cells* returns\n        the number of underlying tensor mesh cells along each coordinate direction.\n\n        Returns\n        -------\n        (dim) tuple of int\n            the number of cells in each coordinate direcion\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnC**\n        \"\"\"\n        return self._shape_cells\n\n    @property\n    def orientation(self):\n        \"\"\"Rotation matrix defining mesh axes relative to Cartesian.\n\n        This property returns a rotation matrix between the local coordinate\n        axes of the mesh and the standard Cartesian axes. For a 3D mesh, this\n        would define the x, y and z axes of the mesh relative to the Easting,\n        Northing and elevation directions. The *orientation* property can\n        be used to transform locations from a local coordinate\n        system to a conventional Cartesian system. By default, *orientation*\n        is an identity matrix of shape (mesh.dim, mesh.dim).\n\n        Returns\n        -------\n        (dim, dim) numpy.ndarray of float\n            Square rotation matrix defining orientation\n\n        Examples\n        --------\n        For a visual example of this, please see the figure in the\n        docs for :class:`~discretize.mixins.InterfaceVTK`.\n        \"\"\"\n        return self._orientation\n\n    @orientation.setter\n    def orientation(self, value):\n        if isinstance(value, Identity):\n            self._orientation = np.identity(self.dim)\n        else:\n            R = np.atleast_2d(np.asarray(value, dtype=np.float64))\n            dim = self.dim\n            if R.shape != (dim, dim):\n                raise ValueError(\n                    f\"Orientation matrix must be square and of shape {(dim, dim)}, got {R.shape}\"\n                )\n            # Ensure each row is unitary\n            R = R / np.linalg.norm(R, axis=1)[:, None]\n            # Check if matrix is orthogonal\n            if not np.allclose(R @ R.T, np.identity(self.dim), rtol=1.0e-5, atol=1e-6):\n                raise ValueError(\"Orientation matrix is not orthogonal\")\n            self._orientation = R\n\n    @property\n    def reference_system(self):\n        \"\"\"Coordinate reference system.\n\n        The type of coordinate reference frame. Will be one of the values \"cartesian\",\n        \"cylindrical\", or \"spherical\".\n\n        Returns\n        -------\n        str {'cartesian', 'cylindrical', 'spherical'}\n            The coordinate system associated with the mesh.\n        \"\"\"\n        return self._reference_system\n\n    @reference_system.setter\n    def reference_system(self, value):\n        \"\"\"Check if the reference system is of a known type.\"\"\"\n        choices = [\"cartesian\", \"cylindrical\", \"spherical\"]\n        # Here are a few abbreviations that users can harnes\n        abrevs = {\n            \"car\": choices[0],\n            \"cart\": choices[0],\n            \"cy\": choices[1],\n            \"cyl\": choices[1],\n            \"sph\": choices[2],\n        }\n        # Get the name and fix it if it is abbreviated\n        value = value.lower()\n        value = abrevs.get(value, value)\n        if value not in choices:\n            raise ValueError(\n                \"Coordinate system ({}) unknown.\".format(self.reference_system)\n            )\n        self._reference_system = value\n\n    @property\n    def x0(self):\n        \"\"\"Alias for the :py:attr:`~.BaseRegularMesh.origin`.\n\n        See Also\n        --------\n        origin\n        \"\"\"\n        return self.origin\n\n    @x0.setter\n    def x0(self, val):\n        self.origin = val\n\n    @property\n    def dim(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return len(self.shape_cells)\n\n    @property\n    def n_cells(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return int(np.prod(self.shape_cells))\n\n    @property\n    def n_nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return int(np.prod([x + 1 for x in self.shape_cells]))\n\n    @property\n    def n_edges_x(self):\n        \"\"\"Number of x-edges in the mesh.\n\n        This property returns the number of edges that\n        are parallel to the x-axis; i.e. x-edges.\n\n        Returns\n        -------\n        int\n            Number of x-edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nEx**\n        \"\"\"\n        return int(np.prod([x + y for x, y in zip(self.shape_cells, (0, 1, 1))]))\n\n    @property\n    def n_edges_y(self):\n        \"\"\"Number of y-edges in the mesh.\n\n        This property returns the number of edges that\n        are parallel to the y-axis; i.e. y-edges.\n\n        Returns\n        -------\n        int\n            Number of y-edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nEy**\n        \"\"\"\n        if self.dim < 2:\n            return None\n        return int(np.prod([x + y for x, y in zip(self.shape_cells, (1, 0, 1))]))\n\n    @property\n    def n_edges_z(self):\n        \"\"\"Number of z-edges in the mesh.\n\n        This property returns the number of edges that\n        are parallel to the z-axis; i.e. z-edges.\n\n        Returns\n        -------\n        int\n            Number of z-edges in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nEz**\n        \"\"\"\n        if self.dim < 3:\n            return None\n        return int(np.prod([x + y for x, y in zip(self.shape_cells, (1, 1, 0))]))\n\n    @property\n    def n_edges_per_direction(self):\n        \"\"\"The number of edges in each direction.\n\n        This property returns a tuple with the number of edges\n        in each axis direction of the mesh. For a 3D mesh,\n        *n_edges_per_direction* would return a tuple of the form\n        (nEx, nEy, nEz). Thus the length of the\n        tuple depends on the dimension of the mesh.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of edges in each direction\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnE**\n\n        Examples\n        --------\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import numpy as np\n        >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]])\n        >>> M.plot_grid(edges=True)\n        >>> plt.show()\n        \"\"\"\n        return tuple(\n            x for x in [self.n_edges_x, self.n_edges_y, self.n_edges_z] if x is not None\n        )\n\n    @property\n    def n_edges(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        n = self.n_edges_x\n        if self.dim > 1:\n            n += self.n_edges_y\n        if self.dim > 2:\n            n += self.n_edges_z\n        return n\n\n    @property\n    def n_faces_x(self):\n        \"\"\"Number of x-faces in the mesh.\n\n        This property returns the number of faces whose normal\n        vector is parallel to the x-axis; i.e. x-faces.\n\n        Returns\n        -------\n        int\n            Number of x-faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nFx**\n        \"\"\"\n        return int(np.prod([x + y for x, y in zip(self.shape_cells, (1, 0, 0))]))\n\n    @property\n    def n_faces_y(self):\n        \"\"\"Number of y-faces in the mesh.\n\n        This property returns the number of faces whose normal\n        vector is parallel to the y-axis; i.e. y-faces.\n\n        Returns\n        -------\n        int\n            Number of y-faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nFy**\n        \"\"\"\n        if self.dim < 2:\n            return None\n        return int(np.prod([x + y for x, y in zip(self.shape_cells, (0, 1, 0))]))\n\n    @property\n    def n_faces_z(self):\n        \"\"\"Number of z-faces in the mesh.\n\n        This property returns the number of faces whose normal\n        vector is parallel to the z-axis; i.e. z-faces.\n\n        Returns\n        -------\n        int\n            Number of z-faces in the mesh\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **nFz**\n        \"\"\"\n        if self.dim < 3:\n            return None\n        return int(np.prod([x + y for x, y in zip(self.shape_cells, (0, 0, 1))]))\n\n    @property\n    def n_faces_per_direction(self):\n        \"\"\"The number of faces in each axis direction.\n\n        This property returns a tuple with the number of faces\n        in each axis direction of the mesh. For a 3D mesh,\n        *n_faces_per_direction* would return a tuple of the form\n        (nFx, nFy, nFz). Thus the length of the\n        tuple depends on the dimension of the mesh.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of faces in each axis direction\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnF**\n\n        Examples\n        --------\n        >>> import discretize\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]])\n        >>> M.plot_grid(faces=True)\n        >>> plt.show()\n        \"\"\"\n        return tuple(\n            x for x in [self.n_faces_x, self.n_faces_y, self.n_faces_z] if x is not None\n        )\n\n    @property\n    def n_faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        n = self.n_faces_x\n        if self.dim > 1:\n            n += self.n_faces_y\n        if self.dim > 2:\n            n += self.n_faces_z\n        return n\n\n    @property\n    def face_normals(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            nX = np.c_[np.ones(self.n_faces_x), np.zeros(self.n_faces_x)]\n            nY = np.c_[np.zeros(self.n_faces_y), np.ones(self.n_faces_y)]\n            return np.r_[nX, nY]\n        elif self.dim == 3:\n            nX = np.c_[\n                np.ones(self.n_faces_x),\n                np.zeros(self.n_faces_x),\n                np.zeros(self.n_faces_x),\n            ]\n            nY = np.c_[\n                np.zeros(self.n_faces_y),\n                np.ones(self.n_faces_y),\n                np.zeros(self.n_faces_y),\n            ]\n            nZ = np.c_[\n                np.zeros(self.n_faces_z),\n                np.zeros(self.n_faces_z),\n                np.ones(self.n_faces_z),\n            ]\n            return np.r_[nX, nY, nZ]\n\n    @property\n    def edge_tangents(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            tX = np.c_[np.ones(self.n_edges_x), np.zeros(self.n_edges_x)]\n            tY = np.c_[np.zeros(self.n_edges_y), np.ones(self.n_edges_y)]\n            return np.r_[tX, tY]\n        elif self.dim == 3:\n            tX = np.c_[\n                np.ones(self.n_edges_x),\n                np.zeros(self.n_edges_x),\n                np.zeros(self.n_edges_x),\n            ]\n            tY = np.c_[\n                np.zeros(self.n_edges_y),\n                np.ones(self.n_edges_y),\n                np.zeros(self.n_edges_y),\n            ]\n            tZ = np.c_[\n                np.zeros(self.n_edges_z),\n                np.zeros(self.n_edges_z),\n                np.ones(self.n_edges_z),\n            ]\n            return np.r_[tX, tY, tZ]\n\n    @property\n    def reference_is_rotated(self):\n        \"\"\"Indicate whether mesh uses standard coordinate axes.\n\n        The standard basis vectors defining the x, y, and z axes of\n        a mesh are :math:`(1,0,0)`, :math:`(0,1,0)` and :math:`(0,0,1)`,\n        respectively. However, the :py:attr:`~BaseRegularMesh.orientation` property\n        can be used to define rotated coordinate axes for our mesh.\n\n        The *reference_is_rotated* property determines\n        whether the mesh is using standard coordinate axes.\n        If the coordinate axes are standard, *mesh.orientation* is\n        the identity matrix and *reference_is_rotated* returns a value of *False*.\n        Otherwise, *reference_is_rotated* returns a value of *True*.\n\n        Returns\n        -------\n        bool\n            *False* is the mesh uses the standard coordinate axes and *True* otherwise.\n        \"\"\"\n        return not np.allclose(self.orientation, np.identity(self.dim))\n\n    @property\n    def rotation_matrix(self):\n        \"\"\"Alias for :py:attr:`~.BaseRegularMesh.orientation`.\n\n        See Also\n        --------\n        orientation\n        \"\"\"\n        return self.orientation  # np.array([self.axis_u, self.axis_v, self.axis_w])\n\n    @property\n    def axis_u(self):\n        \"\"\"Orientation of the first axis.\n\n        .. deprecated:: 0.7.0\n          `axis_u` will be removed in discretize 1.0.0. This functionality was replaced\n          by the :py:attr:`~.BaseRegularMesh.orientation`.\n        \"\"\"\n        raise NotImplementedError(\n            \"The axis_u property is rmoved, please access as self.orientation[0]. \"\n            \"This will be removed in discretize 1.0.0.\"\n        )\n\n    @axis_u.setter\n    def axis_u(self, value):\n        raise NotImplementedError(\n            \"The axis_u property is removed, please access as self.orientation[0]. \"\n            \"This will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def axis_v(self):\n        \"\"\"Orientation of the second axis.\n\n        .. deprecated:: 0.7.0\n          `axis_v` will be removed in discretize 1.0.0. This functionality was replaced\n          by the :py:attr:`~.BaseRegularMesh.orientation`.\n        \"\"\"\n        raise NotImplementedError(\n            \"The axis_v property is removed, please access as self.orientation[1]. \"\n            \"This will be removed in discretize 1.0.0.\"\n        )\n\n    @axis_v.setter\n    def axis_v(self, value):\n        raise NotImplementedError(\n            \"The axis_v property is removed, please access as self.orientation[1]. \"\n            \"This will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def axis_w(self):\n        \"\"\"Orientation of the third axis.\n\n        .. deprecated:: 0.7.0\n          `axis_w` will be removed in discretize 1.0.0. This functionality was replaced\n          by the :py:attr:`~.BaseRegularMesh.orientation`.\n        \"\"\"\n        raise NotImplementedError(\n            \"The axis_w property is removed, please access as self.orientation[2]. \"\n            \"This will be removed in discretize 1.0.0.\"\n        )\n\n    @axis_w.setter\n    def axis_w(self, value):\n        raise NotImplementedError(\n            \"The axis_w property is removed, please access as self.orientation[2]. \"\n            \"This will be removed in discretize 1.0.0.\"\n        )\n\n\nclass BaseRectangularMesh(BaseRegularMesh):\n    \"\"\"Base rectangular mesh class for the ``discretize`` package.\n\n    The ``BaseRectangularMesh`` class acts as an extension of the\n    :class:`~discretize.base.BaseRegularMesh` classes with a regular structure.\n    \"\"\"\n\n    _aliases = {\n        **BaseRegularMesh._aliases,\n        **{\n            \"vnN\": \"shape_nodes\",\n            \"vnEx\": \"shape_edges_x\",\n            \"vnEy\": \"shape_edges_y\",\n            \"vnEz\": \"shape_edges_z\",\n            \"vnFx\": \"shape_faces_x\",\n            \"vnFy\": \"shape_faces_y\",\n            \"vnFz\": \"shape_faces_z\",\n        },\n    }\n\n    @property\n    def shape_nodes(self):\n        \"\"\"The number of nodes along each axis direction.\n\n        This property returns a tuple containing the number of nodes along\n        each axis direction. The length of the tuple is equal to the\n        dimension of the mesh; i.e. 1, 2 or 3.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of nodes along each axis direction\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnN**\n        \"\"\"\n        return tuple(x + 1 for x in self.shape_cells)\n\n    @property\n    def shape_edges_x(self):\n        \"\"\"Number of x-edges along each axis direction.\n\n        This property returns a tuple containing the number of x-edges\n        along each axis direction. The length of the tuple is equal to the\n        dimension of the mesh; i.e. 1, 2 or 3.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of x-edges along each axis direction\n\n            - *1D mesh:* `(n_cells_x)`\n            - *2D mesh:* `(n_cells_x, n_nodes_y)`\n            - *3D mesh:* `(n_cells_x, n_nodes_y, n_nodes_z)`\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnEx**\n        \"\"\"\n        return self.shape_cells[:1] + self.shape_nodes[1:]\n\n    @property\n    def shape_edges_y(self):\n        \"\"\"Number of y-edges along each axis direction.\n\n        This property returns a tuple containing the number of y-edges\n        along each axis direction. If `dim` is 1, there are no y-edges.\n\n        Returns\n        -------\n        None or (dim) tuple of int\n            Number of y-edges along each axis direction\n\n            - *1D mesh: None*\n            - *2D mesh:* `(n_nodes_x, n_cells_y)`\n            - *3D mesh:* `(n_nodes_x, n_cells_y, n_nodes_z)`\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnEy**\n        \"\"\"\n        if self.dim < 2:\n            return None\n        sc = self.shape_cells\n        sn = self.shape_nodes\n        return (sn[0], sc[1]) + sn[2:]  # conditionally added if dim == 3!\n\n    @property\n    def shape_edges_z(self):\n        \"\"\"Number of z-edges along each axis direction.\n\n        This property returns a tuple containing the number of z-edges\n        along each axis direction. There are only z-edges if `dim` is 3.\n\n        Returns\n        -------\n        None or (dim) tuple of int\n            Number of z-edges along each axis direction.\n\n            - *1D mesh: None*\n            - *2D mesh: None*\n            - *3D mesh:* `(n_nodes_x, n_nodes_y, n_cells_z)`\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnEz**\n        \"\"\"\n        if self.dim < 3:\n            return None\n        return self.shape_nodes[:2] + self.shape_cells[2:]\n\n    @property\n    def shape_faces_x(self):\n        \"\"\"Number of x-faces along each axis direction.\n\n        This property returns a tuple containing the number of x-faces\n        along each axis direction.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of x-faces along each axis direction\n\n            - *1D mesh:* `(n_nodes_x)`\n            - *2D mesh:* `(n_nodes_x, n_cells_y)`\n            - *3D mesh:* `(n_nodes_x, n_cells_y, n_cells_z)`\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnFx**\n        \"\"\"\n        return self.shape_nodes[:1] + self.shape_cells[1:]\n\n    @property\n    def shape_faces_y(self):\n        \"\"\"Number of y-faces along each axis direction.\n\n        This property returns a tuple containing the number of y-faces\n        along each axis direction. If `dim` is 1, there are no y-edges.\n\n        Returns\n        -------\n        None or (dim) tuple of int\n            Number of y-faces along each axis direction\n\n            - *1D mesh: None*\n            - *2D mesh:* `(n_cells_x, n_nodes_y)`\n            - *3D mesh:* `(n_cells_x, n_nodes_y, n_cells_z)`\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnFy**\n        \"\"\"\n        if self.dim < 2:\n            return None\n        sc = self.shape_cells\n        sn = self.shape_nodes\n        return (sc[0], sn[1]) + sc[2:]\n\n    @property\n    def shape_faces_z(self):\n        \"\"\"Number of z-faces along each axis direction.\n\n        This property returns a tuple containing the number of z-faces\n        along each axis direction. There are only z-faces if `dim` is 3.\n\n        Returns\n        -------\n        None or (dim) tuple of int\n            Number of z-faces along each axis direction.\n\n                - *1D mesh: None*\n                - *2D mesh: None*\n                - *3D mesh:* (n_cells_x, n_cells_y, n_nodes_z)\n\n        Notes\n        -----\n        Property also accessible as using the shorthand **vnFz**\n        \"\"\"\n        if self.dim < 3:\n            return None\n        return self.shape_cells[:2] + self.shape_nodes[2:]\n\n    ##################################\n    # Redo the numbering so they are dependent of the shape tuples\n    # these should all inherit the parent's docstrings\n    ##################################\n\n    @property\n    def n_cells(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return int(np.prod(self.shape_cells))\n\n    @property\n    def n_nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return int(np.prod(self.shape_nodes))\n\n    @property\n    def n_edges_x(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseRegularMesh\n        return int(np.prod(self.shape_edges_x))\n\n    @property\n    def n_edges_y(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseRegularMesh\n        if self.dim < 2:\n            return\n        return int(np.prod(self.shape_edges_y))\n\n    @property\n    def n_edges_z(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseRegularMesh\n        if self.dim < 3:\n            return\n        return int(np.prod(self.shape_edges_z))\n\n    @property\n    def n_faces_x(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseRegularMesh\n        return int(np.prod(self.shape_faces_x))\n\n    @property\n    def n_faces_y(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseRegularMesh\n        if self.dim < 2:\n            return\n        return int(np.prod(self.shape_faces_y))\n\n    @property\n    def n_faces_z(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseRegularMesh\n        if self.dim < 3:\n            return\n        return int(np.prod(self.shape_faces_z))\n\n    def reshape(\n        self,\n        x,\n        x_type=\"cell_centers\",\n        out_type=\"cell_centers\",\n        return_format=\"V\",\n        **kwargs,\n    ):\n        \"\"\"Reshape tensor quantities.\n\n        **Reshape** is a quick command that will do its best to reshape discrete\n        quantities living on meshes than inherit the :class:`discretize.base_mesh.RectangularMesh`\n        class. For example, you may have a 1D array defining a vector on mesh faces, and you would\n        like to extract the x-component and reshaped it to a 3D matrix.\n\n        Parameters\n        ----------\n        x : numpy.ndarray or list of numpy.ndarray\n            The input quantity. , ndarray (tensor) or a list\n        x_type : {'CC', 'N', 'F', 'Fx', 'Fy', 'Fz', 'E', 'Ex', 'Ey', 'Ez'}\n            Defines the locations on the mesh where input parameter *x* lives.\n        out_type : str\n            Defines the output quantity. Choice depends on your input for *x_type*:\n\n            - *x_type* = 'CC' ---> *out_type* = 'CC'\n            - *x_type* = 'N' ---> *out_type* = 'N'\n            - *x_type* = 'F' ---> *out_type* = {'F', 'Fx', 'Fy', 'Fz'}\n            - *x_type* = 'E' ---> *out_type* = {'E', 'Ex', 'Ey', 'Ez'}\n        return_format : str\n            The dimensions of quantity being returned\n\n            - *V:* return a vector (1D array) or a list of vectors\n            - *M:* return matrix (nD array) or a list of matrices\n\n        \"\"\"\n        if \"xType\" in kwargs:\n            raise TypeError(\n                \"The xType keyword argument has been removed, please use x_type. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n            x_type = kwargs[\"xType\"]\n        if \"outType\" in kwargs:\n            raise TypeError(\n                \"The outType keyword argument has been removed, please use out_type. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"format\" in kwargs:\n            raise TypeError(\n                \"The format keyword argument has been removed, please use return_format. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n            return_format = kwargs[\"format\"]\n\n        x_type = self._parse_location_type(x_type)\n        out_type = self._parse_location_type(out_type)\n\n        allowed_x_type = [\n            \"cell_centers\",\n            \"nodes\",\n            \"faces\",\n            \"faces_x\",\n            \"faces_y\",\n            \"faces_z\",\n            \"edges\",\n            \"edges_x\",\n            \"edges_y\",\n            \"edges_z\",\n        ]\n        if not (isinstance(x, list) or isinstance(x, np.ndarray)):\n            raise TypeError(\"x must be either a list or a ndarray\")\n        if x_type not in allowed_x_type:\n            raise ValueError(\n                \"x_type must be either '\" + \"', '\".join(allowed_x_type) + \"'\"\n            )\n        if out_type not in allowed_x_type:\n            raise ValueError(\n                \"out_type must be either '\" + \"', '\".join(allowed_x_type) + \"'\"\n            )\n        if return_format not in [\"M\", \"V\"]:\n            raise ValueError(\"return_format must be either 'M' or 'V'\")\n        if out_type[: len(x_type)] != x_type:\n            raise ValueError(\"You cannot change types when reshaping.\")\n        if x_type not in out_type:\n            raise ValueError(\"You cannot change type of components.\")\n\n        if isinstance(x, list):\n            for i, xi in enumerate(x):\n                if not isinstance(x, np.ndarray):\n                    raise TypeError(\"x[{0:d}] must be a numpy array\".format(i))\n                if xi.size != x[0].size:\n                    raise ValueError(\"Number of elements in list must not change.\")\n\n            x_array = np.ones((x.size, len(x)))\n            # Unwrap it and put it in a np array\n            for i, xi in enumerate(x):\n                x_array[:, i] = mkvc(xi)\n            x = x_array\n\n        if not isinstance(x, np.ndarray):\n            raise TypeError(\"x must be a numpy array\")\n\n        x = x[:]  # make a copy.\n        x_type_is_FE_xyz = (\n            len(x_type) > 1\n            and x_type[0] in [\"f\", \"e\"]\n            and x_type[-1] in [\"x\", \"y\", \"z\"]\n        )\n\n        def outKernal(xx, nn):\n            \"\"\"Return xx as either a matrix (shape == nn) or a vector.\"\"\"\n            if return_format == \"M\":\n                return xx.reshape(nn, order=\"F\")\n            elif return_format == \"V\":\n                return mkvc(xx)\n\n        def switchKernal(xx):\n            \"\"\"Switch over the different options.\"\"\"\n            if x_type in [\"cell_centers\", \"nodes\"]:\n                nn = self.shape_cells if x_type == \"cell_centers\" else self.shape_nodes\n                if xx.size != np.prod(nn):\n                    raise ValueError(\"Number of elements must not change.\")\n                return outKernal(xx, nn)\n            elif x_type in [\"faces\", \"edges\"]:\n                # This will only deal with components of fields,\n                # not full 'F' or 'E'\n                xx = mkvc(xx)  # unwrap it in case it is a matrix\n                if x_type == \"faces\":\n                    nn = (self.nFx, self.nFy, self.nFz)[: self.dim]\n                else:\n                    nn = (self.nEx, self.nEy, self.nEz)[: self.dim]\n                nn = np.r_[0, nn]\n\n                nx = [0, 0, 0]\n                nx[0] = self.shape_faces_x if x_type == \"faces\" else self.shape_edges_x\n                nx[1] = self.shape_faces_y if x_type == \"faces\" else self.shape_edges_y\n                nx[2] = self.shape_faces_z if x_type == \"faces\" else self.shape_edges_z\n\n                for dim, dimName in enumerate([\"x\", \"y\", \"z\"]):\n                    if dimName in out_type:\n                        if self.dim <= dim:\n                            raise ValueError(\n                                \"Dimensions of mesh not great enough for \"\n                                \"{}_{}\".format(x_type, dimName)\n                            )\n                        if xx.size != np.sum(nn):\n                            raise ValueError(\"Vector is not the right size.\")\n                        start = np.sum(nn[: dim + 1])\n                        end = np.sum(nn[: dim + 2])\n                        return outKernal(xx[start:end], nx[dim])\n\n            elif x_type_is_FE_xyz:\n                # This will deal with partial components (x, y or z)\n                # lying on edges or faces\n                if \"x\" in x_type:\n                    nn = self.shape_faces_x if \"f\" in x_type else self.shape_edges_x\n                elif \"y\" in x_type:\n                    nn = self.shape_faces_y if \"f\" in x_type else self.shape_edges_y\n                elif \"z\" in x_type:\n                    nn = self.shape_faces_z if \"f\" in x_type else self.shape_edges_z\n                if xx.size != np.prod(nn):\n                    raise ValueError(\n                        f\"Vector is not the right size. Expected {np.prod(nn)}, got {xx.size}\"\n                    )\n                return outKernal(xx, nn)\n\n        # Check if we are dealing with a vector quantity\n        isVectorQuantity = len(x.shape) == 2 and x.shape[1] == self.dim\n\n        if out_type in [\"faces\", \"edges\"]:\n            if isVectorQuantity:\n                raise ValueError(\"Not sure what to do with a vector vector quantity..\")\n            outTypeCopy = out_type\n            out = ()\n            for dirName in [\"x\", \"y\", \"z\"][: self.dim]:\n                out_type = outTypeCopy + \"_\" + dirName\n                out += (switchKernal(x),)\n            return out\n        elif isVectorQuantity:\n            out = ()\n            for ii in range(x.shape[1]):\n                out += (switchKernal(x[:, ii]),)\n            return out\n        else:\n            return switchKernal(x)\n\n    # DEPRECATED\n    r = deprecate_method(\"reshape\", \"r\", removal_version=\"1.0.0\", error=True)\n\n    @property\n    def nCx(self):\n        \"\"\"Number of cells in the x direction.\n\n        `nCx` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.shape_cells[0]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The nCx property is removed, please access as mesh.shape_cells[0]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def nCy(self):\n        \"\"\"Number of cells in the y direction.\n\n        `nCy` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.shape_cells[1]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The nCy property is removed, please access as mesh.shape_cells[1]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def nCz(self):\n        \"\"\"Number of cells in the z direction.\n\n        `nCz` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.shape_cells[2]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The nCz property is removed, please access as mesh.shape_cells[2]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def nNx(self):\n        \"\"\"Number of nodes in the x-direction.\n\n        `nNx` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.shape_nodes[0]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The nNx property is removed, please access as mesh.shape_nodes[0]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def nNy(self):\n        \"\"\"Number of nodes in the y-direction.\n\n        `nNy` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.shape_nodes[1]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The nNy property is removed, please access as mesh.shape_nodes[1]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def nNz(self):\n        \"\"\"Number of nodes in the z-direction.\n\n        `nNz` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.shape_nodes[2]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The nNz property is removed, please access as mesh.shape_nodes[2]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n"
  },
  {
    "path": "discretize/base/base_tensor_mesh.py",
    "content": "\"\"\"Base class for tensor-product style meshes.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom discretize.base.base_regular_mesh import BaseRegularMesh\nfrom discretize.utils import (\n    is_scalar,\n    as_array_n_by_dim,\n    unpack_widths,\n    mkvc,\n    ndgrid,\n    spzeros,\n    sdiag,\n    sdinv,\n    TensorType,\n    interpolation_matrix,\n    make_boundary_bool,\n)\nfrom discretize.utils.code_utils import deprecate_method, deprecate_property\nimport warnings\n\n\nclass BaseTensorMesh(BaseRegularMesh):\n    \"\"\"Base class for tensor-product style meshes.\n\n    This class contains properites and methods that are common to Cartesian\n    and cylindrical meshes. That is, meshes whose cell centers, nodes, faces\n    and edges can be constructed with tensor-products of vectors.\n\n    Do not use this class directly! Practical tensor meshes supported in\n    discretize will inherit this class; i.e. :class:`discretize.TensorMesh`\n    and :class:`~discretize.CylindricalMesh`. Inherit this class if you plan\n    to develop a new tensor-style mesh class (e.g. a spherical mesh).\n\n    Parameters\n    ----------\n    h : (dim) iterable of int, numpy.ndarray, or tuple\n        Defines the cell widths along each axis. The length of the iterable object is\n        equal to the dimension of the mesh (1, 2 or 3). For a 3D mesh, the list would\n        have the form *[hx, hy, hz]* .\n\n        Along each axis, the user has 3 choices for defining the cells widths:\n\n        - :class:`int` -> A unit interval is equally discretized into `N` cells.\n        - :class:`numpy.ndarray` -> The widths are explicity given for each cell\n        - the widths are defined as a :class:`list` of :class:`tuple` of the form *(dh, nc, [npad])*\n          where *dh* is the cell width, *nc* is the number of cells, and *npad* (optional)\n          is a padding factor denoting exponential increase/decrease in the cell width\n          for each cell; e.g. *[(2., 10, -1.3), (2., 50), (2., 10, 1.3)]*\n\n    origin : (dim) iterable, default: 0\n        Define the origin or 'anchor point' of the mesh; i.e. the bottom-left-frontmost\n        corner. By default, the mesh is anchored such that its origin is at\n        ``[0, 0, 0]``.\n\n        For each dimension (x, y or z), The user may set the origin 2 ways:\n\n        - a ``scalar`` which explicitly defines origin along that dimension.\n        - **{'0', 'C', 'N'}** a :class:`str` specifying whether the zero coordinate along\n          each axis is the first node location ('0'), in the center ('C') or the last\n          node location ('N').\n\n    See Also\n    --------\n    utils.unpack_widths :\n        The function used to expand a ``list`` or ``tuple`` to generate widths.\n    \"\"\"\n\n    _meshType = \"BASETENSOR\"\n    _aliases = {\n        **BaseRegularMesh._aliases,\n        **{\n            \"gridFx\": \"faces_x\",\n            \"gridFy\": \"faces_y\",\n            \"gridFz\": \"faces_z\",\n            \"gridEx\": \"edges_x\",\n            \"gridEy\": \"edges_y\",\n            \"gridEz\": \"edges_z\",\n        },\n    }\n\n    _unitDimensions = [1, 1, 1]\n    _items = {\"h\"} | BaseRegularMesh._items\n\n    def __init__(self, h, origin=None, **kwargs):\n        if \"x0\" in kwargs:\n            origin = kwargs.pop(\"x0\")\n\n        try:\n            h = list(h)  # ensure value is a list (and make a copy)\n        except TypeError:\n            raise TypeError(\"h must be an iterable object, not {}\".format(type(h)))\n        if len(h) == 0 or len(h) > 3:\n            raise ValueError(\"h must be of dimension 1, 2, or 3 not {}\".format(len(h)))\n        # expand value\n        for i, h_i in enumerate(h):\n            if is_scalar(h_i) and not isinstance(h_i, np.ndarray):\n                # This gives you something over the unit cube.\n                h_i = self._unitDimensions[i] * np.ones(int(h_i)) / int(h_i)\n            elif isinstance(h_i, (list, tuple)):\n                h_i = unpack_widths(h_i)\n            if not isinstance(h_i, np.ndarray):\n                raise TypeError(\"h[{0:d}] is not a numpy array.\".format(i))\n            if len(h_i.shape) != 1:\n                raise ValueError(\"h[{0:d}] must be a 1D numpy array.\".format(i))\n            h[i] = h_i[:]  # make a copy.\n        self._h = tuple(h)\n\n        shape_cells = tuple([len(h_i) for h_i in h])\n        kwargs.pop(\"shape_cells\", None)\n        super().__init__(shape_cells=shape_cells, **kwargs)  # do not pass origin here\n        if origin is not None:\n            self.origin = origin\n\n    @property\n    def h(self):\n        r\"\"\"Cell widths along each axis direction.\n\n        The widths of the cells along each axis direction are returned\n        as a tuple of 1D arrays; e.g. (hx, hy, hz) for a 3D mesh.\n        The lengths of the 1D arrays in the tuple are given by\n        :py:attr:`~discretize.base.BaseRegularMesh.shape_cells`. Ordering\n        begins at the bottom southwest corner. These are the\n        cell widths used when creating the mesh.\n\n        Returns\n        -------\n        (dim) tuple of numpy.ndarray\n            Cell widths along each axis direction. This depends on the mesh class:\n\n            - :class:`~discretize.TensorMesh`: cell widths along the *x* , [*y* and *z* ] directions\n            - :class:`~discretize.CylindricalMesh`: cell widths along the *r*, :math:`\\phi` and *z* directions\n            - :class:`~discretize.TreeMesh`: cells widths of the *underlying tensor mesh* along the *x* , *y* [and *z* ] directions\n        \"\"\"\n        return self._h\n\n    @BaseRegularMesh.origin.setter\n    def origin(self, value):  # NOQA D102\n        # ensure value is a 1D array at all times\n        try:\n            value = list(value)\n        except TypeError:\n            raise TypeError(\"origin must be iterable\")\n        if len(value) != self.dim:\n            raise ValueError(\"Dimension mismatch. len(origin) != len(h)\")\n        for i, (val, h_i) in enumerate(zip(value, self.h)):\n            if val == \"C\":\n                value[i] = -h_i.sum() * 0.5\n            elif val == \"N\":\n                value[i] = -h_i.sum()\n        value = np.asarray(value, dtype=np.float64)\n        self._origin = value\n\n    @property\n    def nodes_x(self):\n        \"\"\"Return x-coordinates of the nodes along the x-direction.\n\n        This property returns a vector containing the x-coordinate values of\n        the nodes along the x-direction. For instances of\n        :class:`~discretize.TensorMesh` or :class:`~discretize.CylindricalMesh`,\n        this is equivalent to the node positions which define the tensor along\n        the x-axis. For instances of :class:`~discretize.TreeMesh` however, this\n        property returns the x-coordinate values of the nodes along the x-direction\n        for the underlying tensor mesh.\n\n        Returns\n        -------\n        (n_nodes_x) numpy.ndarray of float\n            A 1D array containing the x-coordinates of the nodes along\n            the x-direction.\n\n        \"\"\"\n        return np.r_[self.origin[0], self.h[0]].cumsum()\n\n    @property\n    def nodes_y(self):\n        \"\"\"Return y-coordinates of the nodes along the y-direction.\n\n        For 2D and 3D meshes, this property returns a vector\n        containing the y-coordinate values of the nodes along the\n        y-direction. For instances of :class:`~discretize.TensorMesh` or\n        :class:`~discretize.CylindricalMesh`, this is equivalent to\n        the node positions which define the tensor along the y-axis.\n        For instances of :class:`~discretize.TreeMesh` however, this property\n        returns the y-coordinate values of the nodes along the y-direction\n        for the underlying tensor mesh.\n\n        Returns\n        -------\n        (n_nodes_y) numpy.ndarray of float or None\n            A 1D array containing the y-coordinates of the nodes along\n            the y-direction. Returns *None* for 1D meshes.\n\n        \"\"\"\n        return None if self.dim < 2 else np.r_[self.origin[1], self.h[1]].cumsum()\n\n    @property\n    def nodes_z(self):\n        \"\"\"Return z-coordinates of the nodes along the z-direction.\n\n        For 3D meshes, this property returns a 1D vector\n        containing the z-coordinate values of the nodes along the\n        z-direction. For instances of :class:`~discretize.TensorMesh` or\n        :class:`~discretize.CylindricalMesh`, this is equivalent to\n        the node positions which define the tensor along the z-axis.\n        For instances of :class:`~discretize.TreeMesh` however, this property\n        returns the z-coordinate values of the nodes along the z-direction\n        for the underlying tensor mesh.\n\n        Returns\n        -------\n        (n_nodes_z) numpy.ndarray of float or None\n            A 1D array containing the z-coordinates of the nodes along\n            the z-direction. Returns *None* for 1D and 2D meshes.\n\n        \"\"\"\n        return None if self.dim < 3 else np.r_[self.origin[2], self.h[2]].cumsum()\n\n    @property\n    def cell_centers_x(self):\n        \"\"\"Return x-coordinates of the cell centers along the x-direction.\n\n        For 1D, 2D and 3D meshes, this property returns a 1D vector\n        containing the x-coordinate values of the cell centers along the\n        x-direction. For instances of :class:`~discretize.TensorMesh` or\n        :class:`~discretize.CylindricalMesh`, this is equivalent to\n        the cell center positions which define the tensor along the x-axis.\n        For instances of :class:`~discretize.TreeMesh` however, this property\n        returns the x-coordinate values of the cell centers along the x-direction\n        for the underlying tensor mesh.\n\n        Returns\n        -------\n        (n_cells_x) numpy.ndarray of float\n            A 1D array containing the x-coordinates of the cell centers along\n            the x-direction.\n        \"\"\"\n        nodes = self.nodes_x\n        return (nodes[1:] + nodes[:-1]) / 2\n\n    @property\n    def cell_centers_y(self):\n        \"\"\"Return y-coordinates of the cell centers along the y-direction.\n\n        For 2D and 3D meshes, this property returns a 1D vector\n        containing the y-coordinate values of the cell centers along the\n        y-direction. For instances of :class:`~discretize.TensorMesh` or\n        :class:`~discretize.CylindricalMesh`, this is equivalent to\n        the cell center positions which define the tensor along the y-axis.\n        For instances of :class:`~discretize.TreeMesh` however, this property\n        returns the y-coordinate values of the cell centers along the y-direction\n        for the underlying tensor mesh .\n\n        Returns\n        -------\n        (n_cells_y) numpy.ndarray of float or None\n            A 1D array containing the y-coordinates of the cell centers along\n            the y-direction. Returns *None* for 1D meshes.\n\n        \"\"\"\n        if self.dim < 2:\n            return None\n        nodes = self.nodes_y\n        return (nodes[1:] + nodes[:-1]) / 2\n\n    @property\n    def cell_centers_z(self):\n        \"\"\"Return z-coordinates of the cell centers along the z-direction.\n\n        For 3D meshes, this property returns a 1D vector\n        containing the z-coordinate values of the cell centers along the\n        z-direction. For instances of :class:`~discretize.TensorMesh` or\n        :class:`~discretize.CylindricalMesh`, this is equivalent to\n        the cell center positions which define the tensor along the z-axis.\n        For instances of :class:`~discretize.TreeMesh` however, this property\n        returns the z-coordinate values of the cell centers along the z-direction\n        for the underlying tensor mesh .\n\n        Returns\n        -------\n        (n_cells_z) numpy.ndarray of float or None\n            A 1D array containing the z-coordinates of the cell centers along\n            the z-direction. Returns *None* for 1D and 2D meshes.\n\n        \"\"\"\n        if self.dim < 3:\n            return None\n        nodes = self.nodes_z\n        return (nodes[1:] + nodes[:-1]) / 2\n\n    @property\n    def cell_centers(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self._getTensorGrid(\"cell_centers\")\n\n    @property\n    def nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self._getTensorGrid(\"nodes\")\n\n    @property\n    def boundary_nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        dim = self.dim\n        if dim == 1:\n            return self.nodes_x[[0, -1]]\n        return self.nodes[make_boundary_bool(self.shape_nodes)]\n\n    @property\n    def h_gridded(self):\n        \"\"\"Return dimensions of all mesh cells as staggered grid.\n\n        This property returns a numpy array of shape (n_cells, dim)\n        containing gridded x, (y and z) dimensions for all cells in the mesh.\n        The first row corresponds to the bottom-front-leftmost cell.\n        The cells are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_cells, dim) numpy.ndarray of float\n            Dimensions of all mesh cells as staggered grid\n\n        Examples\n        --------\n        The following is a 1D example.\n\n        >>> from discretize import TensorMesh\n        >>> hx = np.ones(5)\n        >>> mesh_1D = TensorMesh([hx])\n        >>> mesh_1D.h_gridded\n        array([[1.],\n               [1.],\n               [1.],\n               [1.],\n               [1.]])\n\n        The following is a 3D example.\n\n        >>> hx, hy, hz = np.ones(2), 2*np.ones(2), 3*np.ones(2)\n        >>> mesh_3D = TensorMesh([hx, hy, hz])\n        >>> mesh_3D.h_gridded\n        array([[1., 2., 3.],\n               [1., 2., 3.],\n               [1., 2., 3.],\n               [1., 2., 3.],\n               [1., 2., 3.],\n               [1., 2., 3.],\n               [1., 2., 3.],\n               [1., 2., 3.]])\n\n        \"\"\"\n        if self.dim == 1:\n            return self.h[0][:, None]\n        return ndgrid(*self.h)\n\n    @property\n    def faces_x(self):\n        \"\"\"Gridded x-face locations.\n\n        This property returns a numpy array of shape (n_faces_x, dim)\n        containing gridded locations for all x-faces in the\n        mesh. The first row corresponds to the bottom-front-leftmost x-face.\n        The x-faces are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_faces_x, dim) numpy.ndarray of float\n            Gridded x-face locations\n        \"\"\"\n        if self.nFx == 0:\n            return\n        return self._getTensorGrid(\"faces_x\")\n\n    @property\n    def faces_y(self):\n        \"\"\"Gridded y-face locations.\n\n        This property returns a numpy array of shape (n_faces_y, dim)\n        containing gridded locations for all y-faces in the\n        mesh. The first row corresponds to the bottom-front-leftmost y-face.\n        The y-faces are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        n_faces_y, dim) numpy.ndarray of float or None\n            Gridded y-face locations for 2D and 3D mesh. Returns *None* for 1D meshes.\n        \"\"\"\n        if self.nFy == 0 or self.dim < 2:\n            return\n        return self._getTensorGrid(\"faces_y\")\n\n    @property\n    def faces_z(self):\n        \"\"\"Gridded z-face locations.\n\n        This property returns a numpy array of shape (n_faces_z, dim)\n        containing gridded locations for all z-faces in the\n        mesh. The first row corresponds to the bottom-front-leftmost z-face.\n        The z-faces are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_faces_z, dim) numpy.ndarray of float or None\n            Gridded z-face locations for 3D mesh. Returns *None* for 1D and 2D meshes.\n        \"\"\"\n        if self.nFz == 0 or self.dim < 3:\n            return\n        return self._getTensorGrid(\"faces_z\")\n\n    @property\n    def faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.faces_x is not None:\n            faces = self.faces_x\n        else:\n            faces = np.empty((0, self.dim))\n        if self.dim > 1 and self.faces_y is not None:\n            faces = np.r_[faces, self.faces_y]\n        if self.dim > 2 and self.faces_z is not None:\n            faces = np.r_[faces, self.faces_z]\n        return faces\n\n    @property\n    def boundary_faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        dim = self.dim\n        if dim == 1:\n            return self.nodes_x[[0, -1]]\n        if dim == 2:\n            fx = ndgrid(self.nodes_x[[0, -1]], self.cell_centers_y)\n            fy = ndgrid(self.cell_centers_x, self.nodes_y[[0, -1]])\n            return np.r_[fx, fy]\n        if dim == 3:\n            fx = ndgrid(self.nodes_x[[0, -1]], self.cell_centers_y, self.cell_centers_z)\n            fy = ndgrid(self.cell_centers_x, self.nodes_y[[0, -1]], self.cell_centers_z)\n            fz = ndgrid(self.cell_centers_x, self.cell_centers_y, self.nodes_z[[0, -1]])\n            return np.r_[fx, fy, fz]\n\n    @property\n    def boundary_face_outward_normals(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        dim = self.dim\n        if dim == 1:\n            return np.array([-1, 1])\n        if dim == 2:\n            nx = ndgrid(np.r_[-1, 1], np.zeros(self.shape_cells[1]))\n            ny = ndgrid(np.zeros(self.shape_cells[0]), np.r_[-1, 1])\n            return np.r_[nx, ny]\n        if dim == 3:\n            nx = ndgrid(\n                np.r_[-1, 1],\n                np.zeros(self.shape_cells[1]),\n                np.zeros(self.shape_cells[2]),\n            )\n            ny = ndgrid(\n                np.zeros(self.shape_cells[0]),\n                np.r_[-1, 1],\n                np.zeros(self.shape_cells[2]),\n            )\n            nz = ndgrid(\n                np.zeros(self.shape_cells[0]),\n                np.zeros(self.shape_cells[1]),\n                np.r_[-1, 1],\n            )\n            return np.r_[nx, ny, nz]\n\n    @property\n    def edges_x(self):\n        \"\"\"Gridded x-edge locations.\n\n        This property returns a numpy array of shape (n_edges_x, dim)\n        containing gridded locations for all x-edges in the mesh.\n        The first row corresponds to the bottom-front-leftmost x-edge.\n        The x-edges are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_edges_x, dim) numpy.ndarray of float or None\n            Gridded x-edge locations. Returns *None* if `shape_edges_x[0]` is 0.\n        \"\"\"\n        if self.nEx == 0:\n            return\n        return self._getTensorGrid(\"edges_x\")\n\n    @property\n    def edges_y(self):\n        \"\"\"Gridded y-edge locations.\n\n        This property returns a numpy array of shape (n_edges_y, dim)\n        containing gridded locations for all y-edges in the mesh.\n        The first row corresponds to the bottom-front-leftmost y-edge.\n        The y-edges are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_edges_y, dim) numpy.ndarray of float\n            Gridded y-edge locations. Returns *None* for 1D meshes.\n        \"\"\"\n        if self.nEy == 0 or self.dim < 2:\n            return\n        return self._getTensorGrid(\"edges_y\")\n\n    @property\n    def edges_z(self):\n        \"\"\"Gridded z-edge locations.\n\n        This property returns a numpy array of shape (n_edges_z, dim)\n        containing gridded locations for all z-edges in the mesh.\n        The first row corresponds to the bottom-front-leftmost z-edge.\n        The z-edges are ordered along the x, then y, then z directions.\n\n        Returns\n        -------\n        (n_edges_z, dim) numpy.ndarray of float\n            Gridded z-edge locations. Returns *None* for 1D and 2D meshes.\n        \"\"\"\n        if self.nEz == 0 or self.dim < 3:\n            return\n        return self._getTensorGrid(\"edges_z\")\n\n    @property\n    def edges(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.edges_x is not None:\n            edges = self.edges_x\n        else:\n            edges = np.empty((0, self.dim))\n        if self.dim > 1 and self.edges_y is not None:\n            edges = np.r_[edges, self.edges_y]\n        if self.dim > 2 and self.edges_z is not None:\n            edges = np.r_[edges, self.edges_z]\n        return edges\n\n    @property\n    def boundary_edges(self):\n        \"\"\"Boundary edge locations.\n\n        This property returns the locations of the edges on\n        the boundary of the mesh as a numpy array. The shape\n        of the numpy array is the number of boundary edges by\n        the dimension of the mesh.\n\n        Returns\n        -------\n        (n_boundary_edges, dim) numpy.ndarray of float\n            Boundary edge locations\n        \"\"\"\n        dim = self.dim\n        if dim == 1:\n            return None  # no boundary edges in 1D\n        if dim == 2:\n            ex = ndgrid(self.cell_centers_x, self.nodes_y[[0, -1]])\n            ey = ndgrid(self.nodes_x[[0, -1]], self.cell_centers_y)\n            return np.r_[ex, ey]\n        if dim == 3:\n            ex = self.edges_x[make_boundary_bool(self.shape_edges_x, bdir=\"yz\")]\n            ey = self.edges_y[make_boundary_bool(self.shape_edges_y, bdir=\"xz\")]\n            ez = self.edges_z[make_boundary_bool(self.shape_edges_z, bdir=\"xy\")]\n            return np.r_[ex, ey, ez]\n\n    def _getTensorGrid(self, key):\n        if getattr(self, \"_\" + key, None) is None:\n            setattr(self, \"_\" + key, ndgrid(self.get_tensor(key)))\n        return getattr(self, \"_\" + key)\n\n    def get_tensor(self, key):\n        \"\"\"Return the base 1D arrays for a specified mesh tensor.\n\n        The cell-centers, nodes, x-faces, z-edges, etc... of a tensor mesh\n        can be constructed by applying tensor products to the set of base\n        1D arrays; i.e. (vx, vy, vz). These 1D arrays define the gridded\n        locations for the mesh tensor along each axis. For a given mesh tensor\n        (i.e. cell centers, nodes, x/y/z faces or x/y/z edges),\n        **get_tensor** returns a list containing the base 1D arrays.\n\n        Parameters\n        ----------\n        key : str\n            Specifies the tensor being returned. Please choose from::\n\n                'CC', 'cell_centers' -> location of cell centers\n                'N', 'nodes'         -> location of nodes\n                'Fx', 'faces_x'      -> location of faces with an x normal\n                'Fy', 'faces_y'      -> location of faces with an y normal\n                'Fz', 'faces_z'      -> location of faces with an z normal\n                'Ex', 'edges_x'      -> location of edges with an x tangent\n                'Ey', 'edges_y'      -> location of edges with an y tangent\n                'Ez', 'edges_z'      -> location of edges with an z tangent\n\n        Returns\n        -------\n        (dim) list of 1D numpy.ndarray\n            list of base 1D arrays for the tensor.\n\n        \"\"\"\n        key = self._parse_location_type(key)\n\n        if key == \"faces_x\":\n            ten = [\n                self.nodes_x,\n                self.cell_centers_y,\n                self.cell_centers_z,\n            ]\n        elif key == \"faces_y\":\n            ten = [\n                self.cell_centers_x,\n                self.nodes_y,\n                self.cell_centers_z,\n            ]\n        elif key == \"faces_z\":\n            ten = [\n                self.cell_centers_x,\n                self.cell_centers_y,\n                self.nodes_z,\n            ]\n        elif key == \"edges_x\":\n            ten = [self.cell_centers_x, self.nodes_y, self.nodes_z]\n        elif key == \"edges_y\":\n            ten = [self.nodes_x, self.cell_centers_y, self.nodes_z]\n        elif key == \"edges_z\":\n            ten = [self.nodes_x, self.nodes_y, self.cell_centers_z]\n        elif key == \"cell_centers\":\n            ten = [\n                self.cell_centers_x,\n                self.cell_centers_y,\n                self.cell_centers_z,\n            ]\n        elif key == \"nodes\":\n            ten = [self.nodes_x, self.nodes_y, self.nodes_z]\n        else:\n            raise KeyError(r\"Unrecognized key {key}\")\n\n        return [t for t in ten if t is not None]\n\n    # --------------- Methods ---------------------\n\n    def is_inside(self, pts, location_type=\"nodes\", **kwargs):\n        \"\"\"Determine which points lie within the mesh.\n\n        For an arbitrary set of points, **is_indside** returns a\n        boolean array identifying which points lie within the mesh.\n\n        Parameters\n        ----------\n        pts : (n_pts, dim) numpy.ndarray\n            Locations of input points. Must have same dimension as the mesh.\n        location_type : str, optional\n            Use *N* to determine points lying within the cluster of mesh\n            nodes. Use *CC* to determine points lying within the cluster\n            of mesh cell centers.\n\n        Returns\n        -------\n        (n_pts) numpy.ndarray of bool\n            Boolean array identifying points which lie within the mesh\n\n        \"\"\"\n        if \"locType\" in kwargs:\n            raise TypeError(\n                \"The locType keyword argument has been removed, please use location_type. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        pts = as_array_n_by_dim(pts, self.dim)\n\n        tensors = self.get_tensor(location_type)\n\n        if location_type[0].lower() == \"n\" and self._meshType == \"CYL\":\n            # NOTE: for a CYL mesh we add a node to check if we are inside in\n            # the radial direction!\n            tensors[0] = np.r_[0.0, tensors[0]]\n            tensors[1] = np.r_[tensors[1], 2.0 * np.pi]\n\n        inside = np.ones(pts.shape[0], dtype=bool)\n        for i, tensor in enumerate(tensors):\n            TOL = np.diff(tensor).min() * 1.0e-10\n            inside = (\n                inside\n                & (pts[:, i] >= tensor.min() - TOL)\n                & (pts[:, i] <= tensor.max() + TOL)\n            )\n        return inside\n\n    def _get_interpolation_matrix(\n        self, loc, location_type=\"cell_centers\", zeros_outside=False\n    ):\n        \"\"\"Produce an interpolation matrix.\n\n        Parameters\n        ----------\n        loc : numpy.ndarray\n            Location of points to interpolate to\n\n        location_type: str, optional\n            What to interpolate\n\n            location_type can be::\n\n                'Ex', 'edges_x'           -> x-component of field defined on x edges\n                'Ey', 'edges_y'           -> y-component of field defined on y edges\n                'Ez', 'edges_z'           -> z-component of field defined on z edges\n                'Fx', 'faces_x'           -> x-component of field defined on x faces\n                'Fy', 'faces_y'           -> y-component of field defined on y faces\n                'Fz', 'faces_z'           -> z-component of field defined on z faces\n                'N', 'nodes'              -> scalar field defined on nodes\n                'CC', 'cell_centers'      -> scalar field defined on cell centers\n                'CCVx', 'cell_centers_x'  -> x-component of vector field defined on cell centers\n                'CCVy', 'cell_centers_y'  -> y-component of vector field defined on cell centers\n                'CCVz', 'cell_centers_z'  -> z-component of vector field defined on cell centers\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            M, the interpolation matrix\n\n        \"\"\"\n        loc = as_array_n_by_dim(loc, self.dim)\n\n        if not zeros_outside:\n            if not np.all(self.is_inside(loc)):\n                raise ValueError(\"Points outside of mesh\")\n        else:\n            indZeros = np.logical_not(self.is_inside(loc))\n            loc = loc.copy()\n            loc[indZeros, :] = np.array([v.mean() for v in self.get_tensor(\"CC\")])\n\n        location_type = self._parse_location_type(location_type)\n\n        if location_type in [\n            \"faces_x\",\n            \"faces_y\",\n            \"faces_z\",\n            \"edges_x\",\n            \"edges_y\",\n            \"edges_z\",\n        ]:\n            ind = {\"x\": 0, \"y\": 1, \"z\": 2}[location_type[-1]]\n            if self.dim < ind:\n                raise ValueError(\"mesh is not high enough dimension.\")\n            if \"f\" in location_type.lower():\n                items = (self.nFx, self.nFy, self.nFz)[: self.dim]\n            else:\n                items = (self.nEx, self.nEy, self.nEz)[: self.dim]\n            components = [spzeros(loc.shape[0], n) for n in items]\n            components[ind] = interpolation_matrix(loc, *self.get_tensor(location_type))\n            # remove any zero blocks (hstack complains)\n            components = [comp for comp in components if comp.shape[1] > 0]\n            Q = sp.hstack(components)\n\n        elif location_type in [\"cell_centers\", \"nodes\"]:\n            Q = interpolation_matrix(loc, *self.get_tensor(location_type))\n\n        elif location_type in [\"cell_centers_x\", \"cell_centers_y\", \"cell_centers_z\"]:\n            Q = interpolation_matrix(loc, *self.get_tensor(\"CC\"))\n            Z = spzeros(loc.shape[0], self.nC)\n            if location_type[-1] == \"x\":\n                Q = sp.hstack([Q, Z, Z])\n            elif location_type[-1] == \"y\":\n                Q = sp.hstack([Z, Q, Z])\n            elif location_type[-1] == \"z\":\n                Q = sp.hstack([Z, Z, Q])\n\n        else:\n            raise NotImplementedError(\n                \"get_interpolation_matrix: location_type==\"\n                + location_type\n                + \" and mesh.dim==\"\n                + str(self.dim)\n            )\n\n        if zeros_outside:\n            Q[indZeros, :] = 0\n\n        return Q.tocsr()\n\n    def get_interpolation_matrix(  # NOQA D102\n        self, loc, location_type=\"cell_centers\", zeros_outside=False, **kwargs\n    ):\n        # Documentation inherited from discretize.base.BaseMesh\n        if \"locType\" in kwargs:\n            raise TypeError(\n                \"The locType keyword argument has been removed, please use location_type. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"zerosOutside\" in kwargs:\n            raise TypeError(\n                \"The zerosOutside keyword argument has been removed, please use zeros_outside. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        return self._get_interpolation_matrix(loc, location_type, zeros_outside)\n\n    def _fastInnerProduct(\n        self, projection_type, model=None, invert_model=False, invert_matrix=False\n    ):\n        \"\"\"Fast version of get_face_inner_product_deriv.\n\n        This does not handle the case of a full tensor property.\n\n        Parameters\n        ----------\n        projection_type : str\n            'edges' or 'faces'\n        model : numpy.ndarray\n            material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6))\n        invert_model : bool\n            inverts the material property\n        invert_matrix : bool\n            inverts the matrix\n\n        Returns\n        -------\n        (n_faces, n_faces) scipy.sparse.csr_matrix\n            M, the inner product matrix\n\n        \"\"\"\n        projection_type = projection_type[0].upper()\n        if projection_type not in [\"F\", \"E\"]:\n            raise ValueError(\"projection_type must be 'F' for faces or 'E' for edges\")\n\n        if model is None:\n            model = np.ones(self.nC)\n\n        if invert_model:\n            model = 1.0 / model\n\n        if is_scalar(model):\n            model = model * np.ones(self.nC)\n\n        # number of elements we are averaging (equals dim for regular\n        # meshes, but for cyl, where we use symmetry, it is 1 for edge\n        # variables and 2 for face variables)\n        if self._meshType == \"CYL\":\n            shape = getattr(self, \"vn\" + projection_type)\n            n_elements = sum([1 if x != 0 else 0 for x in shape])\n        else:\n            n_elements = self.dim\n\n        # Isotropic? or anisotropic?\n        if model.size == self.nC:\n            Av = getattr(self, \"ave\" + projection_type + \"2CC\")\n            Vprop = self.cell_volumes * mkvc(model)\n            M = n_elements * sdiag(Av.T * Vprop)\n\n        elif model.size == self.nC * self.dim:\n            Av = getattr(self, \"ave\" + projection_type + \"2CCV\")\n\n            # if cyl, then only certain components are relevant due to symmetry\n            # for faces, x, z matters, for edges, y (which is theta) matters\n            if self._meshType == \"CYL\" and self.is_symmetric:\n                if projection_type == \"E\":\n                    model = model[:, 1]  # this is the action of a projection mat\n                elif projection_type == \"F\":\n                    model = model[:, [0, 2]]\n\n            V = sp.kron(sp.identity(n_elements), sdiag(self.cell_volumes))\n            M = sdiag(Av.T * V * mkvc(model))\n        else:\n            return None\n\n        if invert_matrix:\n            return sdinv(M)\n        else:\n            return M\n\n    def _fastInnerProductDeriv(\n        self, projection_type, model, invert_model=False, invert_matrix=False\n    ):\n        \"\"\"Faster function for inner product derivatives on tensor meshes.\n\n        Parameters\n        ----------\n        projection_type : str\n            'edges' or 'faces'\n        model : numpy.ndarray\n            material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6))\n        invert_model : bool\n            inverts the material property\n        invert_matrix : bool\n            inverts the matrix\n\n        Returns\n        -------\n        function\n            dMdmu, the derivative of the inner product matrix\n        \"\"\"\n        projection_type = projection_type[0].upper()\n        if projection_type not in [\"F\", \"E\"]:\n            raise ValueError(\"projection_type must be 'F' for faces or 'E' for edges\")\n\n        tensorType = TensorType(self, model)\n\n        dMdprop = None\n\n        if invert_matrix or invert_model:\n            MI = self._fastInnerProduct(\n                projection_type,\n                model,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n\n        # number of elements we are averaging (equals dim for regular\n        # meshes, but for cyl, where we use symmetry, it is 1 for edge\n        # variables and 2 for face variables)\n        if self._meshType == \"CYL\":\n            shape = getattr(self, \"vn\" + projection_type)\n            n_elements = sum([1 if x != 0 else 0 for x in shape])\n        else:\n            n_elements = self.dim\n\n        if tensorType == 0:  # isotropic, constant\n            Av = getattr(self, \"ave\" + projection_type + \"2CC\")\n            V = sdiag(self.cell_volumes)\n            ones = sp.csr_matrix(\n                (np.ones(self.nC), (range(self.nC), np.zeros(self.nC))),\n                shape=(self.nC, 1),\n            )\n            if not invert_matrix and not invert_model:\n                dMdprop = n_elements * Av.T * V * ones\n            elif invert_matrix and invert_model:\n                dMdprop = n_elements * (\n                    sdiag(MI.diagonal() ** 2) * Av.T * V * ones * sdiag(1.0 / model**2)\n                )\n            elif invert_model:\n                dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = n_elements * (sdiag(-MI.diagonal() ** 2) * Av.T * V)\n\n        elif tensorType == 1:  # isotropic, variable in space\n            Av = getattr(self, \"ave\" + projection_type + \"2CC\")\n            V = sdiag(self.cell_volumes)\n            if not invert_matrix and not invert_model:\n                dMdprop = n_elements * Av.T * V\n            elif invert_matrix and invert_model:\n                dMdprop = n_elements * (\n                    sdiag(MI.diagonal() ** 2) * Av.T * V * sdiag(1.0 / model**2)\n                )\n            elif invert_model:\n                dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = n_elements * (sdiag(-MI.diagonal() ** 2) * Av.T * V)\n\n        elif tensorType == 2:  # anisotropic\n            Av = getattr(self, \"ave\" + projection_type + \"2CCV\")\n            V = sp.kron(sp.identity(self.dim), sdiag(self.cell_volumes))\n\n            if self._meshType == \"CYL\" and self.is_symmetric:\n                Zero = sp.csr_matrix((self.nC, self.nC))\n                Eye = sp.eye(self.nC)\n                if projection_type == \"E\":\n                    P = sp.hstack([Zero, Eye, Zero])\n                    # print(P.todense())\n                elif projection_type == \"F\":\n                    P = sp.vstack(\n                        [sp.hstack([Eye, Zero, Zero]), sp.hstack([Zero, Zero, Eye])]\n                    )\n                    # print(P.todense())\n            else:\n                P = sp.eye(self.nC * self.dim)\n\n            if not invert_matrix and not invert_model:\n                dMdprop = Av.T * P * V\n            elif invert_matrix and invert_model:\n                dMdprop = (\n                    sdiag(MI.diagonal() ** 2) * Av.T * P * V * sdiag(1.0 / model**2)\n                )\n            elif invert_model:\n                dMdprop = Av.T * P * V * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = sdiag(-MI.diagonal() ** 2) * Av.T * P * V\n\n        if dMdprop is not None:\n\n            def innerProductDeriv(v=None):\n                if v is None:\n                    warnings.warn(\n                        \"Depreciation Warning: TensorMesh.innerProductDeriv.\"\n                        \" You should be supplying a vector. \"\n                        \"Use: sdiag(u)*dMdprop\",\n                        FutureWarning,\n                        stacklevel=2,\n                    )\n                    return dMdprop\n                return sdiag(v) * dMdprop\n\n            return innerProductDeriv\n        else:\n            return None\n\n    # DEPRECATED\n    @property\n    def hx(self):\n        \"\"\"Width of cells in the x direction.\n\n        `hx` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.h[0]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The hx property is removed, please access as mesh.h[0]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def hy(self):\n        \"\"\"Width of cells in the y direction.\n\n        `hy` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.h[1]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The hy property is removed, please access as mesh.h[1]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    @property\n    def hz(self):\n        \"\"\"Width of cells in the z direction.\n\n        `hz` will be removed in discretize 1.0.0, it is replaced by\n        `mesh.h[2]` to reduce namespace clutter.\n        \"\"\"\n        raise NotImplementedError(\n            \"The hz property is removed, please access as mesh.h[2]. \"\n            \"This message will be removed in discretize 1.0.0.\"\n        )\n\n    vectorNx = deprecate_property(\n        \"nodes_x\", \"vectorNx\", removal_version=\"1.0.0\", error=True\n    )\n    vectorNy = deprecate_property(\n        \"nodes_y\", \"vectorNy\", removal_version=\"1.0.0\", error=True\n    )\n    vectorNz = deprecate_property(\n        \"nodes_z\", \"vectorNz\", removal_version=\"1.0.0\", error=True\n    )\n    vectorCCx = deprecate_property(\n        \"cell_centers_x\", \"vectorCCx\", removal_version=\"1.0.0\", error=True\n    )\n    vectorCCy = deprecate_property(\n        \"cell_centers_y\", \"vectorCCy\", removal_version=\"1.0.0\", error=True\n    )\n    vectorCCz = deprecate_property(\n        \"cell_centers_z\", \"vectorCCz\", removal_version=\"1.0.0\", error=True\n    )\n    isInside = deprecate_method(\n        \"is_inside\", \"isInside\", removal_version=\"1.0.0\", error=True\n    )\n    getTensor = deprecate_method(\n        \"get_tensor\", \"getTensor\", removal_version=\"1.0.0\", error=True\n    )\n"
  },
  {
    "path": "discretize/base/meson.build",
    "content": "\npython_sources = [\n  '__init__.py',\n  'base_mesh.py',\n  'base_regular_mesh.py',\n  'base_tensor_mesh.py',\n]\n\npy.install_sources(\n  python_sources,\n  subdir: 'discretize/base'\n)\n"
  },
  {
    "path": "discretize/curvilinear_mesh.py",
    "content": "\"\"\"Module containing the curvilinear mesh implementation.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom discretize.utils import (\n    mkvc,\n    index_cube,\n    face_info,\n    volume_tetrahedron,\n    make_boundary_bool,\n)\nfrom discretize.base import BaseRectangularMesh\nfrom discretize.operators import DiffOperators, InnerProducts\nfrom discretize.mixins import InterfaceMixins\n\n\n# Some helper functions.\ndef _length2D(x):\n    return (x[:, 0] ** 2 + x[:, 1] ** 2) ** 0.5\n\n\ndef _length3D(x):\n    return (x[:, 0] ** 2 + x[:, 1] ** 2 + x[:, 2] ** 2) ** 0.5\n\n\ndef _normalize2D(x):\n    return x / np.kron(np.ones((1, 2)), mkvc(_length2D(x), 2))\n\n\ndef _normalize3D(x):\n    return x / np.kron(np.ones((1, 3)), mkvc(_length3D(x), 2))\n\n\nclass CurvilinearMesh(\n    DiffOperators, InnerProducts, BaseRectangularMesh, InterfaceMixins\n):\n    \"\"\"Curvilinear mesh class.\n\n    Curvilinear meshes are numerical grids whose cells are general quadrilaterals (2D)\n    or cuboid (3D); unlike tensor meshes (see :class:`~discretize.TensorMesh`) whose\n    cells are rectangles or rectangular prisms. That being said, the combinatorial\n    structure (i.e. connectivity of mesh cells) of curvilinear meshes is the same as\n    tensor meshes.\n\n    Parameters\n    ----------\n    node_list : list of array_like\n        List :class:`array_like` containing the gridded x, y (and z) node locations.\n\n        - For a 2D curvilinear mesh, *node_list* = [X, Y] where X and Y have shape\n          (``n_nodes_x``, ``n_nodes_y``)\n        - For a 3D curvilinear mesh, *node_list* = [X, Y, Z] where X, Y and Z have shape\n          (``n_nodes_x``, ``n_nodes_y``, ``n_nodes_z``)\n\n\n    Examples\n    --------\n    Using the :py:func:`~discretize.utils.example_curvilinear_grid` utility,\n    we provide an example of a curvilinear mesh.\n\n    >>> from discretize import CurvilinearMesh\n    >>> from discretize.utils import example_curvilinear_grid\n    >>> import matplotlib.pyplot as plt\n\n    The example grid slightly rotates the nodes in the center of the mesh,\n\n    >>> x, y = example_curvilinear_grid([10, 10], \"rotate\")\n    >>> x.shape\n    (11, 11)\n    >>> y.shape\n    (11, 11)\n    >>> curvilinear_mesh = CurvilinearMesh([x, y])\n    >>> curvilinear_mesh.shape_nodes\n    (11, 11)\n\n    >>> fig = plt.figure(figsize=(5,5))\n    >>> ax = fig.add_subplot(111)\n    >>> curvilinear_mesh.plot_grid(ax=ax)\n    >>> plt.show()\n    \"\"\"\n\n    _meshType = \"Curv\"\n    _aliases = {\n        **DiffOperators._aliases,\n        **BaseRectangularMesh._aliases,\n        **{\n            \"gridFx\": \"faces_x\",\n            \"gridFy\": \"faces_y\",\n            \"gridFz\": \"faces_z\",\n            \"gridEx\": \"edges_x\",\n            \"gridEy\": \"edges_y\",\n            \"gridEz\": \"edges_z\",\n        },\n    }\n    _items = {\"node_list\"}\n\n    def __init__(self, node_list, **kwargs):\n        if \"nodes\" in kwargs:\n            node_list = kwargs.pop(\"nodes\")\n\n        node_list = tuple(np.asarray(item, dtype=np.float64) for item in node_list)\n        # check shapes of each node array match\n        dim = len(node_list)\n        if dim not in [2, 3]:\n            raise ValueError(\n                f\"Only supports 2 and 3 dimensional meshes, saw a node_list of length {dim}\"\n            )\n        for nodes in node_list:\n            if len(nodes.shape) != dim:\n                raise ValueError(\n                    f\"Unexpected shape of item in node list, expect array with {dim} dimensions, got {len(nodes.shape)}\"\n                )\n            if node_list[0].shape != nodes.shape:\n                raise ValueError(\n                    f\"The shape of nodes are not consistent, saw {node_list[0].shape} and {nodes.shape}\"\n                )\n        self._node_list = tuple(node_list)\n\n        # Save nodes to private variable _nodes as vectors\n        self._nodes = np.ones((self.node_list[0].size, dim))\n        for i, nodes in enumerate(self.node_list):\n            self._nodes[:, i] = mkvc(nodes)\n\n        shape_cells = (n - 1 for n in self.node_list[0].shape)\n\n        # absorb the rest of kwargs, and do not pass to super\n        super().__init__(shape_cells, origin=self.nodes[0])\n\n    @property\n    def node_list(self):\n        \"\"\"The gridded x, y (and z) node locations used to create the mesh.\n\n        Returns\n        -------\n        (dim) list of numpy.ndarray\n            Gridded x, y (and z) node locations used to create the mesh.\n\n                - *2D:* return is a list [X, Y] where X and Y have shape (n_nodes_x, n_nodes_y)\n                - *3D:* return is a list [X, Y, Z] where X, Y and Z have shape (n_nodes_x, n_nodes_y, n_nodes_z)\n\n        \"\"\"\n        return self._node_list\n\n    @classmethod\n    def deserialize(cls, value, **kwargs):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if \"nodes\" in value:\n            value[\"node_list\"] = value.pop(\"nodes\")\n        return super().deserialize(value, **kwargs)\n\n    @property\n    def cell_centers(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_cell_centers\", None) is None:\n            self._cell_centers = np.concatenate(\n                [self.aveN2CC * self.gridN[:, i] for i in range(self.dim)]\n            ).reshape((-1, self.dim), order=\"F\")\n        return self._cell_centers\n\n    @property\n    def nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_nodes\", None) is None:\n            raise Exception(\"Someone deleted this. I blame you.\")\n        return self._nodes\n\n    @property\n    def faces_x(self):\n        \"\"\"Gridded x-face locations (staggered grid).\n\n        This property returns a numpy array of shape (n_faces_x, dim)\n        containing gridded locations for all x-faces in the\n        mesh (staggered grid). For curvilinear meshes whose structure\n        is minimally staggered, the x-faces are faces whose normal\n        vectors are primarily along the x-direction. For highly irregular\n        meshes however, this is not the case; see the examples below.\n\n        Returns\n        -------\n        (n_faces_x, dim) numpy.ndarray of float\n            Gridded x-face locations (staggered grid)\n\n        Examples\n        --------\n        Here, we provide an example of a minimally staggered curvilinear mesh.\n        In this case, the x-faces have normal vectors that are\n        primarily along the x-direction.\n\n        >>> from discretize import CurvilinearMesh\n        >>> from discretize.utils import example_curvilinear_grid, mkvc\n        >>> from matplotlib import pyplot as plt\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"rotate\")\n        >>> mesh1 = CurvilinearMesh([x, y])\n        >>> x_faces = mesh1.faces_x\n\n        >>> fig1 = plt.figure(figsize=(5, 5))\n        >>> ax1 = fig1.add_subplot(111)\n        >>> mesh1.plot_grid(ax=ax1)\n        >>> ax1.scatter(x_faces[:, 0], x_faces[:, 1], 30, 'r')\n        >>> ax1.legend(['Mesh', 'X-faces'], fontsize=16)\n        >>> plt.show()\n\n        Here, we provide an example of a highly irregular curvilinear mesh.\n        In this case, the x-faces are not defined by normal vectors along\n        a particular direction.\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"sphere\")\n        >>> mesh2 = CurvilinearMesh([x, y])\n        >>> x_faces = mesh2.faces_x\n\n        >>> fig2 = plt.figure(figsize=(5, 5))\n        >>> ax2 = fig2.add_subplot(111)\n        >>> mesh2.plot_grid(ax=ax2)\n        >>> ax2.scatter(x_faces[:, 0], x_faces[:, 1], 30, 'r')\n        >>> ax2.legend(['Mesh', 'X-faces'], fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_faces_x\", None) is None:\n            N = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n            if self.dim == 2:\n                XY = [mkvc(0.5 * (n[:, :-1] + n[:, 1:])) for n in N]\n                self._faces_x = np.c_[XY[0], XY[1]]\n            elif self.dim == 3:\n                XYZ = [\n                    mkvc(\n                        0.25\n                        * (\n                            n[:, :-1, :-1]\n                            + n[:, :-1, 1:]\n                            + n[:, 1:, :-1]\n                            + n[:, 1:, 1:]\n                        )\n                    )\n                    for n in N\n                ]\n                self._faces_x = np.c_[XYZ[0], XYZ[1], XYZ[2]]\n        return self._faces_x\n\n    @property\n    def faces_y(self):\n        \"\"\"Gridded y-face locations (staggered grid).\n\n        This property returns a numpy array of shape (n_faces_y, dim)\n        containing gridded locations for all y-faces in the\n        mesh (staggered grid). For curvilinear meshes whose structure\n        is minimally staggered, the y-faces are faces whose normal\n        vectors are primarily along the y-direction. For highly irregular\n        meshes however, this is not the case; see the examples below.\n\n        Returns\n        -------\n        (n_faces_y, dim) numpy.ndarray of float\n            Gridded y-face locations (staggered grid)\n\n        Examples\n        --------\n        Here, we provide an example of a minimally staggered curvilinear mesh.\n        In this case, the y-faces have normal vectors that are\n        primarily along the x-direction.\n\n        >>> from discretize import CurvilinearMesh\n        >>> from discretize.utils import example_curvilinear_grid, mkvc\n        >>> from matplotlib import pyplot as plt\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"rotate\")\n        >>> mesh1 = CurvilinearMesh([x, y])\n        >>> y_faces = mesh1.faces_y\n\n        >>> fig1 = plt.figure(figsize=(5, 5))\n        >>> ax1 = fig1.add_subplot(111)\n        >>> mesh1.plot_grid(ax=ax1)\n        >>> ax1.scatter(y_faces[:, 0], y_faces[:, 1], 30, 'r')\n        >>> ax1.legend(['Mesh', 'Y-faces'], fontsize=16)\n        >>> plt.show()\n\n        Here, we provide an example of a highly irregular curvilinear mesh.\n        In this case, the y-faces are not defined by normal vectors along\n        a particular direction.\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"sphere\")\n        >>> mesh2 = CurvilinearMesh([x, y])\n        >>> y_faces = mesh2.faces_y\n\n        >>> fig2 = plt.figure(figsize=(5, 5))\n        >>> ax2 = fig2.add_subplot(111)\n        >>> mesh2.plot_grid(ax=ax2)\n        >>> ax2.scatter(y_faces[:, 0], y_faces[:, 1], 30, 'r')\n        >>> ax2.legend(['Mesh', 'Y-faces'], fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_faces_y\", None) is None:\n            N = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n            if self.dim == 2:\n                XY = [mkvc(0.5 * (n[:-1, :] + n[1:, :])) for n in N]\n                self._faces_y = np.c_[XY[0], XY[1]]\n            elif self.dim == 3:\n                XYZ = [\n                    mkvc(\n                        0.25\n                        * (\n                            n[:-1, :, :-1]\n                            + n[:-1, :, 1:]\n                            + n[1:, :, :-1]\n                            + n[1:, :, 1:]\n                        )\n                    )\n                    for n in N\n                ]\n                self._faces_y = np.c_[XYZ[0], XYZ[1], XYZ[2]]\n        return self._faces_y\n\n    @property\n    def faces_z(self):\n        \"\"\"Gridded z-face locations (staggered grid).\n\n        This property returns a numpy array of shape (n_faces_z, dim)\n        containing gridded locations for all z-faces in the\n        mesh (staggered grid). For curvilinear meshes whose structure\n        is minimally staggered, the z-faces are faces whose normal\n        vectors are primarily along the z-direction. For highly irregular\n        meshes however, this is not the case.\n\n        Returns\n        -------\n        (n_faces_z, dim) numpy.ndarray of float\n            Gridded z-face locations (staggered grid)\n        \"\"\"\n        if getattr(self, \"_faces_z\", None) is None:\n            N = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n            XYZ = [\n                mkvc(\n                    0.25\n                    * (n[:-1, :-1, :] + n[:-1, 1:, :] + n[1:, :-1, :] + n[1:, 1:, :])\n                )\n                for n in N\n            ]\n            self._faces_z = np.c_[XYZ[0], XYZ[1], XYZ[2]]\n        return self._faces_z\n\n    @property\n    def faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        faces = np.r_[self.faces_x, self.faces_y]\n        if self.dim > 2:\n            faces = np.r_[faces, self.faces_z]\n        return faces\n\n    @property\n    def edges_x(self):\n        \"\"\"Gridded x-edge locations (staggered grid).\n\n        This property returns a numpy array of shape (n_edges_x, dim)\n        containing gridded locations for all x-edges in the\n        mesh (staggered grid). For curvilinear meshes whose structure\n        is minimally staggered, the x-edges are edges oriented\n        primarily along the x-direction. For highly irregular\n        meshes however, this is not the case; see the examples below.\n\n        Returns\n        -------\n        (n_edges_x, dim) numpy.ndarray of float\n            Gridded x-edge locations (staggered grid)\n\n        Examples\n        --------\n        Here, we provide an example of a minimally staggered curvilinear mesh.\n        In this case, the x-edges are primarily oriented along the x-direction.\n\n        >>> from discretize import CurvilinearMesh\n        >>> from discretize.utils import example_curvilinear_grid, mkvc\n        >>> from matplotlib import pyplot as plt\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"rotate\")\n        >>> mesh1 = CurvilinearMesh([x, y])\n        >>> x_edges = mesh1.edges_x\n\n        >>> fig1 = plt.figure(figsize=(5, 5))\n        >>> ax1 = fig1.add_subplot(111)\n        >>> mesh1.plot_grid(ax=ax1)\n        >>> ax1.scatter(x_edges[:, 0], x_edges[:, 1], 30, 'r')\n        >>> ax1.legend(['Mesh', 'X-edges'], fontsize=16)\n        >>> plt.show()\n\n        Here, we provide an example of a highly irregular curvilinear mesh.\n        In this case, the x-edges are not aligned primarily along\n        a particular direction.\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"sphere\")\n        >>> mesh2 = CurvilinearMesh([x, y])\n        >>> x_edges = mesh2.edges_x\n\n        >>> fig2 = plt.figure(figsize=(5, 5))\n        >>> ax2 = fig2.add_subplot(111)\n        >>> mesh2.plot_grid(ax=ax2)\n        >>> ax2.scatter(x_edges[:, 0], x_edges[:, 1], 30, 'r')\n        >>> ax2.legend(['Mesh', 'X-edges'], fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_edges_x\", None) is None:\n            N = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n            if self.dim == 2:\n                XY = [mkvc(0.5 * (n[:-1, :] + n[1:, :])) for n in N]\n                self._edges_x = np.c_[XY[0], XY[1]]\n            elif self.dim == 3:\n                XYZ = [mkvc(0.5 * (n[:-1, :, :] + n[1:, :, :])) for n in N]\n                self._edges_x = np.c_[XYZ[0], XYZ[1], XYZ[2]]\n        return self._edges_x\n\n    @property\n    def edges_y(self):\n        \"\"\"Gridded y-edge locations (staggered grid).\n\n        This property returns a numpy array of shape (n_edges_y, dim)\n        containing gridded locations for all y-edges in the\n        mesh (staggered grid). For curvilinear meshes whose structure\n        is minimally staggered, the y-edges are edges oriented\n        primarily along the y-direction. For highly irregular\n        meshes however, this is not the case; see the examples below.\n\n        Returns\n        -------\n        (n_edges_y, dim) numpy.ndarray of float\n            Gridded y-edge locations (staggered grid)\n\n        Examples\n        --------\n        Here, we provide an example of a minimally staggered curvilinear mesh.\n        In this case, the y-edges are primarily oriented along the y-direction.\n\n        >>> from discretize import CurvilinearMesh\n        >>> from discretize.utils import example_curvilinear_grid, mkvc\n        >>> from matplotlib import pyplot as plt\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"rotate\")\n        >>> mesh1 = CurvilinearMesh([x, y])\n        >>> y_edges = mesh1.edges_y\n\n        >>> fig1 = plt.figure(figsize=(5, 5))\n        >>> ax1 = fig1.add_subplot(111)\n        >>> mesh1.plot_grid(ax=ax1)\n        >>> ax1.scatter(y_edges[:, 0], y_edges[:, 1], 30, 'r')\n        >>> ax1.legend(['Mesh', 'Y-edges'], fontsize=16)\n        >>> plt.show()\n\n        Here, we provide an example of a highly irregular curvilinear mesh.\n        In this case, the y-edges are not aligned primarily along\n        a particular direction.\n\n        >>> x, y = example_curvilinear_grid([10, 10], \"sphere\")\n        >>> mesh2 = CurvilinearMesh([x, y])\n        >>> y_edges = mesh2.edges_y\n\n        >>> fig2 = plt.figure(figsize=(5, 5))\n        >>> ax2 = fig2.add_subplot(111)\n        >>> mesh2.plot_grid(ax=ax2)\n        >>> ax2.scatter(y_edges[:, 0], y_edges[:, 1], 30, 'r')\n        >>> ax2.legend(['Mesh', 'X-edges'], fontsize=16)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_edges_y\", None) is None:\n            N = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n            if self.dim == 2:\n                XY = [mkvc(0.5 * (n[:, :-1] + n[:, 1:])) for n in N]\n                self._edges_y = np.c_[XY[0], XY[1]]\n            elif self.dim == 3:\n                XYZ = [mkvc(0.5 * (n[:, :-1, :] + n[:, 1:, :])) for n in N]\n                self._edges_y = np.c_[XYZ[0], XYZ[1], XYZ[2]]\n        return self._edges_y\n\n    @property\n    def edges_z(self):\n        \"\"\"Gridded z-edge locations (staggered grid).\n\n        This property returns a numpy array of shape (n_edges_z, dim)\n        containing gridded locations for all z-edges in the\n        mesh (staggered grid). For curvilinear meshes whose structure\n        is minimally staggered, the z-edges are faces whose normal\n        vectors are primarily along the z-direction. For highly irregular\n        meshes however, this is not the case.\n\n        Returns\n        -------\n        (n_edges_z, dim) numpy.ndarray of float\n            Gridded z-edge locations (staggered grid)\n        \"\"\"\n        if getattr(self, \"_edges_z\", None) is None and self.dim == 3:\n            N = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n            XYZ = [mkvc(0.5 * (n[:, :, :-1] + n[:, :, 1:])) for n in N]\n            self._edges_z = np.c_[XYZ[0], XYZ[1], XYZ[2]]\n        return self._edges_z\n\n    @property\n    def edges(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        edges = np.r_[self.edges_x, self.edges_y]\n        if self.dim > 2:\n            edges = np.r_[edges, self.edges_z]\n        return edges\n\n    @property\n    def boundary_nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.nodes[make_boundary_bool(self.shape_nodes)]\n\n    @property\n    def boundary_edges(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            ex = self.edges_x[make_boundary_bool(self.shape_edges_x, bdir=\"y\")]\n            ey = self.edges_y[make_boundary_bool(self.shape_edges_y, bdir=\"x\")]\n            return np.r_[ex, ey]\n        elif self.dim == 3:\n            ex = self.edges_x[make_boundary_bool(self.shape_edges_x, bdir=\"yz\")]\n            ey = self.edges_y[make_boundary_bool(self.shape_edges_y, bdir=\"xz\")]\n            ez = self.edges_z[make_boundary_bool(self.shape_edges_z, bdir=\"xy\")]\n            return np.r_[ex, ey, ez]\n\n    @property\n    def boundary_faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        fx = self.faces_x[make_boundary_bool(self.shape_faces_x, bdir=\"x\")]\n        fy = self.faces_y[make_boundary_bool(self.shape_faces_y, bdir=\"y\")]\n        if self.dim == 2:\n            return np.r_[fx, fy]\n        elif self.dim == 3:\n            fz = self.faces_z[make_boundary_bool(self.shape_faces_z, bdir=\"z\")]\n            return np.r_[fx, fy, fz]\n\n    @property\n    def boundary_face_outward_normals(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        is_bxm = np.zeros(self.shape_faces_x, order=\"F\", dtype=bool)\n        is_bxm[0, :] = True\n        is_bxm = is_bxm.reshape(-1, order=\"F\")\n\n        is_bym = np.zeros(self.shape_faces_y, order=\"F\", dtype=bool)\n        is_bym[:, 0] = True\n        is_bym = is_bym.reshape(-1, order=\"F\")\n\n        is_b = np.r_[\n            make_boundary_bool(self.shape_faces_x, bdir=\"x\"),\n            make_boundary_bool(self.shape_faces_y, bdir=\"y\"),\n        ]\n        switch = np.r_[is_bxm, is_bym]\n        if self.dim == 3:\n            is_bzm = np.zeros(self.shape_faces_z, order=\"F\", dtype=bool)\n            is_bzm[:, :, 0] = True\n            is_bzm = is_bzm.reshape(-1, order=\"F\")\n\n            is_b = np.r_[is_b, make_boundary_bool(self.shape_faces_z, bdir=\"z\")]\n            switch = np.r_[switch, is_bzm]\n        face_normals = self.face_normals.copy()\n        face_normals[switch] *= -1\n        return face_normals[is_b]\n\n    # --------------- Geometries ---------------------\n    #\n    #\n    # ------------------- 2D -------------------------\n    #\n    #         node(i,j)          node(i,j+1)\n    #              A -------------- B\n    #              |                |\n    #              |    cell(i,j)   |\n    #              |        I       |\n    #              |                |\n    #             D -------------- C\n    #         node(i+1,j)        node(i+1,j+1)\n    #\n    # ------------------- 3D -------------------------\n    #\n    #\n    #             node(i,j,k+1)       node(i,j+1,k+1)\n    #                 E --------------- F\n    #                /|               / |\n    #               / |              /  |\n    #              /  |             /   |\n    #       node(i,j,k)         node(i,j+1,k)\n    #            A -------------- B     |\n    #            |    H ----------|---- G\n    #            |   /cell(i,j)   |   /\n    #            |  /     I       |  /\n    #            | /              | /\n    #            D -------------- C\n    #       node(i+1,j,k)      node(i+1,j+1,k)\n\n    @property\n    def cell_volumes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_cell_volumes\", None) is None:\n            if self.dim == 2:\n                A, B, C, D = index_cube(\"ABCD\", self.vnN)\n                normal, area = face_info(\n                    np.c_[self.gridN, np.zeros((self.nN, 1))], A, B, C, D\n                )\n                self._cell_volumes = area\n            elif self.dim == 3:\n                # Each polyhedron can be decomposed into 5 tetrahedrons\n                # However, this presents a choice so we may as well divide in\n                # two ways and average.\n                A, B, C, D, E, F, G, H = index_cube(\"ABCDEFGH\", self.vnN)\n\n                vol1 = (\n                    volume_tetrahedron(self.gridN, A, B, D, E)\n                    + volume_tetrahedron(self.gridN, B, E, F, G)  # cutted edge top\n                    + volume_tetrahedron(self.gridN, B, D, E, G)  # cutted edge top\n                    + volume_tetrahedron(self.gridN, B, C, D, G)  # middle\n                    + volume_tetrahedron(self.gridN, D, E, G, H)  # cutted edge bottom\n                )  # cutted edge bottom\n\n                vol2 = (\n                    volume_tetrahedron(self.gridN, A, F, B, C)\n                    + volume_tetrahedron(self.gridN, A, E, F, H)  # cutted edge top\n                    + volume_tetrahedron(self.gridN, A, H, F, C)  # cutted edge top\n                    + volume_tetrahedron(self.gridN, C, H, D, A)  # middle\n                    + volume_tetrahedron(self.gridN, C, G, H, F)  # cutted edge bottom\n                )  # cutted edge bottom\n\n                self._cell_volumes = (vol1 + vol2) / 2\n        return self._cell_volumes\n\n    @property\n    def face_areas(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if (\n            getattr(self, \"_face_areas\", None) is None\n            or getattr(self, \"_normals\", None) is None\n        ):\n            # Compute areas of cell faces\n            if self.dim == 2:\n                xy = self.gridN\n                A, B = index_cube(\"AB\", self.vnN, self.vnFx)\n                edge1 = xy[B, :] - xy[A, :]\n                normal1 = np.c_[edge1[:, 1], -edge1[:, 0]]\n                area1 = _length2D(edge1)\n                A, D = index_cube(\"AD\", self.vnN, self.vnFy)\n                # Note that we are doing A-D to make sure the normal points the\n                # right way.\n                # Think about it. Look at the picture. Normal points towards C\n                # iff you do this.\n                edge2 = xy[A, :] - xy[D, :]\n                normal2 = np.c_[edge2[:, 1], -edge2[:, 0]]\n                area2 = _length2D(edge2)\n                self._face_areas = np.r_[mkvc(area1), mkvc(area2)]\n                self._normals = [_normalize2D(normal1), _normalize2D(normal2)]\n\n            elif self.dim == 3:\n                A, E, F, B = index_cube(\"AEFB\", self.vnN, self.vnFx)\n                normal1, area1 = face_info(\n                    self.gridN, A, E, F, B, average=False, normalize_normals=False\n                )\n\n                A, D, H, E = index_cube(\"ADHE\", self.vnN, self.vnFy)\n                normal2, area2 = face_info(\n                    self.gridN, A, D, H, E, average=False, normalize_normals=False\n                )\n\n                A, B, C, D = index_cube(\"ABCD\", self.vnN, self.vnFz)\n                normal3, area3 = face_info(\n                    self.gridN, A, B, C, D, average=False, normalize_normals=False\n                )\n\n                self._face_areas = np.r_[mkvc(area1), mkvc(area2), mkvc(area3)]\n                self._normals = [normal1, normal2, normal3]\n        return self._face_areas\n\n    @property\n    def face_normals(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        # For 3D meshes, there are 4 nodes in which\n        # cross-products can be used to compute the normal vector.\n        # In this case, the average normal vector is returned so there\n        # is only 1 vector per face.\n        if getattr(self, \"_normals\", None) is None:\n            self.face_areas  # calling .face_areas will create the face normals\n        if self.dim == 2:\n            return _normalize2D(np.r_[self._normals[0], self._normals[1]])\n        elif self.dim == 3:\n            normal1 = (\n                self._normals[0][0]\n                + self._normals[0][1]\n                + self._normals[0][2]\n                + self._normals[0][3]\n            ) / 4\n            normal2 = (\n                self._normals[1][0]\n                + self._normals[1][1]\n                + self._normals[1][2]\n                + self._normals[1][3]\n            ) / 4\n            normal3 = (\n                self._normals[2][0]\n                + self._normals[2][1]\n                + self._normals[2][2]\n                + self._normals[2][3]\n            ) / 4\n            return _normalize3D(np.r_[normal1, normal2, normal3])\n\n    @property\n    def edge_lengths(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_edge_lengths\", None) is None:\n            if self.dim == 2:\n                xy = self.gridN\n                A, D = index_cube(\"AD\", self.vnN, self.vnEx)\n                edge1 = xy[D, :] - xy[A, :]\n                A, B = index_cube(\"AB\", self.vnN, self.vnEy)\n                edge2 = xy[B, :] - xy[A, :]\n                self._edge_lengths = np.r_[\n                    mkvc(_length2D(edge1)), mkvc(_length2D(edge2))\n                ]\n                self._edge_tangents = (\n                    np.r_[edge1, edge2] / np.c_[self._edge_lengths, self._edge_lengths]\n                )\n            elif self.dim == 3:\n                xyz = self.gridN\n                A, D = index_cube(\"AD\", self.vnN, self.vnEx)\n                edge1 = xyz[D, :] - xyz[A, :]\n                A, B = index_cube(\"AB\", self.vnN, self.vnEy)\n                edge2 = xyz[B, :] - xyz[A, :]\n                A, E = index_cube(\"AE\", self.vnN, self.vnEz)\n                edge3 = xyz[E, :] - xyz[A, :]\n                self._edge_lengths = np.r_[\n                    mkvc(_length3D(edge1)),\n                    mkvc(_length3D(edge2)),\n                    mkvc(_length3D(edge3)),\n                ]\n                self._edge_tangents = (\n                    np.r_[edge1, edge2, edge3]\n                    / np.c_[self._edge_lengths, self._edge_lengths, self._edge_lengths]\n                )\n        return self._edge_lengths\n\n    @property\n    def edge_tangents(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_edge_tangents\", None) is None:\n            self.edge_lengths  # calling .edge_lengths will create the tangents\n        return self._edge_tangents\n\n    def _get_edge_surf_int_proj_mats(self, only_boundary=False, with_area=True):\n        \"\"\"Return the projection operators for integrating edges on each face.\n\n        Parameters\n        ----------\n        only_boundary : bool, optional\n            Whether to only operate on the boundary faces or not.\n        with_area : bool, optional\n            Whether to include the face area.\n\n        Returns\n        -------\n        list of (3 * n_faces, n_edges) scipy.sparse.csr_matrix\n        \"\"\"\n        # edges associated with each face... can just get the indices of the curl...\n        face_edges = self.edge_curl.indices.reshape(-1, 4)\n        face_areas = self.face_areas\n        if only_boundary:\n            bf_inds = self.project_face_to_boundary_face.indices\n            face_edges = face_edges[bf_inds]\n            face_areas = face_areas[bf_inds]\n            face_normals = self.boundary_face_outward_normals\n        else:\n            face_normals = self.face_normals\n\n        # face_edges is edge_x1m, edge_x1p, edge_x2m, edge_x2p for each of them so...\n        edge_inds = [[0, 2], [1, 2], [0, 3], [1, 3]]\n\n        n_f = face_edges.shape[0]\n\n        ones = np.ones(n_f * 2)\n        P_indptr = np.arange(2 * n_f + 1)\n\n        d = np.ones(3, dtype=int)[:, None] * np.arange(2)\n        t = np.arange(n_f)\n        T_col_inds = (d + t[:, None, None] * 2).reshape(-1)\n        T_ind_ptr = 2 * np.arange(3 * n_f + 1)\n\n        Ps = []\n\n        # translate c to fortran ordering\n        C2F_col_inds = np.arange(n_f * 3).reshape((-1, 3), order=\"C\").reshape(-1)\n        C2F_row_inds = np.arange(n_f * 3).reshape((-1, 3), order=\"F\").reshape(-1)\n        C2F = sp.csr_matrix(\n            (np.ones(n_f * 3), (C2F_row_inds, C2F_col_inds)), shape=(n_f * 3, n_f * 3)\n        )\n\n        for i in range(4):\n            # matrix which selects the edges associate with each of the nodes of each boundary face\n            node_edges = face_edges[:, edge_inds[i]]\n            P = sp.csr_matrix(\n                (ones, node_edges.reshape(-1), P_indptr), shape=(2 * n_f, self.n_edges)\n            )\n\n            edge_dirs = self.edge_tangents[node_edges]\n            t_for = np.concatenate((edge_dirs, face_normals[:, None, :]), axis=1)\n            t_inv = np.linalg.inv(t_for)\n            t_inv = t_inv[:, :, :-1] / 4  # n_edges_per_thing\n\n            if with_area:\n                t_inv *= face_areas[:, None, None]\n\n            T = C2F @ sp.csr_matrix(\n                (t_inv.reshape(-1), T_col_inds, T_ind_ptr),\n                shape=(3 * n_f, 2 * n_f),\n            )\n            Ps.append((T @ P))\n        return Ps\n\n    @property\n    def boundary_edge_vector_integral(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            return super().boundary_edge_vector_integral\n\n        Ps = self._get_edge_surf_int_proj_mats(only_boundary=True, with_area=True)\n        # cross product matrix:\n        # cx = mesh.boundary_face_outward_normals[:, 0]\n        # cy = mesh.boundary_face_outward_normals[:, 1]\n        # cz = mesh.boundary_face_outward_normals[:, 2]\n        # z = np.zeros(n_bf)\n        #\n        # vs = np.stack([np.c_[z, cz, -cy], np.c_[-cz, z, cx], np.c_[cy, -cx, z]], axis=-1)\n        # the cross product of a vector defined on each face with the face outward normal...\n        # so V cross n = -n cross V\n\n        cx = sp.diags(self.boundary_face_outward_normals[:, 0])\n        cy = sp.diags(self.boundary_face_outward_normals[:, 1])\n        cz = sp.diags(self.boundary_face_outward_normals[:, 2])\n        # the negative cross mat\n        cross_mat = sp.bmat([[None, cz, -cy], [-cz, None, cx], [cy, -cx, None]])\n\n        Pf = self.project_face_to_boundary_face\n        Pe = self.project_edge_to_boundary_edge\n        Av = (Pf @ self.average_edge_to_face) @ Pe.T\n        Av = cross_mat @ sp.block_diag((Av, Av, Av))\n\n        Me = np.sum(Ps).T @ Av\n        return Me\n"
  },
  {
    "path": "discretize/cylindrical_mesh.py",
    "content": "\"\"\"Module containing the cylindrical mesh implementation.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as sp\nfrom scipy.constants import pi\n\nfrom discretize.utils import (\n    kron3,\n    ndgrid,\n    av,\n    av_extrap,\n    speye,\n    ddx,\n    sdiag,\n    spzeros,\n    interpolation_matrix,\n    cyl2cart,\n    as_array_n_by_dim,\n    Identity,\n)\nfrom discretize.base import BaseTensorMesh, BaseRectangularMesh\nfrom discretize.operators import DiffOperators, InnerProducts\nfrom discretize.mixins import InterfaceMixins\nfrom discretize.utils.code_utils import (\n    deprecate_class,\n    deprecate_property,\n    deprecate_method,\n)\n\n\nclass CylindricalMesh(\n    InnerProducts, DiffOperators, BaseTensorMesh, BaseRectangularMesh, InterfaceMixins\n):\n    r\"\"\"\n    Class for cylindrical meshes.\n\n    ``CylindricalMesh`` is a mesh class for problems with rotational symmetry.\n    It supports both cylindrically symmetric and 3D cylindrical meshes where the azimuthal\n    discretization is user-defined. In discretize, the coordinates for cylindrical\n    meshes are as follows:\n\n    - **x:** radial direction (:math:`r`)\n    - **y:** azimuthal direction (:math:`\\phi`)\n    - **z:** vertical direction (:math:`z`)\n\n    Parameters\n    ----------\n    h : (dim) iterable of int, numpy.ndarray, or tuple\n        Defines the cell widths along each axis. The length of the iterable object is\n        equal to the dimension of the mesh (1, 2 or 3). For a 3D mesh, the list would\n        have the form *[hr, hphi, hz]* . Note that the sum of cell widths in the phi\n        direction **must** equal :math:`2\\pi`. You can also use a flat value of\n        *hphi* = *1* to define a cylindrically symmetric mesh.\n\n        Along each axis, the user has 3 choices for defining the cells widths:\n\n        - :class:`int` -> A unit interval is equally discretized into `N` cells.\n        - :class:`numpy.ndarray` -> The widths are explicity given for each cell\n        - the widths are defined as a :class:`list` of :class:`tuple` of the form\n          *(dh, nc, [npad])* where *dh* is the cell width, *nc* is the number of cells,\n          and *npad* (optional)is a padding factor denoting exponential\n          increase/decrease in the cell width or each cell; e.g.\n          *[(2., 10, -1.3), (2., 50), (2., 10, 1.3)]*\n\n    origin : (dim) iterable, default: 0\n        Define the origin or 'anchor point' of the mesh; i.e. the bottom-left-frontmost\n        corner. By default, the mesh is anchored such that its origin is at\n        ``[0, 0, 0]``.\n\n        For each dimension (r, theta or z), The user may set the origin 2 ways:\n\n        - a ``scalar`` which explicitly defines origin along that dimension.\n        - **{'0', 'C', 'N'}** a :class:`str` specifying whether the zero coordinate\n          along each axis is the first node location ('0'), in the center ('C') or the\n          last node location ('N').\n\n    Examples\n    --------\n    To create a general 3D cylindrical mesh, we discretize along the radial,\n    azimuthal and vertical axis. For example:\n\n    >>> from discretize import CylindricalMesh\n    >>> import matplotlib.pyplot as plt\n    >>> import numpy as np\n\n    >>> ncr = 10  # number of mesh cells in r\n    >>> ncp = 8   # number of mesh cells in phi\n    >>> ncz = 15  # number of mesh cells in z\n    >>> dr = 15   # cell width r\n    >>> dz = 10   # cell width z\n\n    >>> hr = dr * np.ones(ncr)\n    >>> hp = (2 * np.pi / ncp) * np.ones(ncp)\n    >>> hz = dz * np.ones(ncz)\n    >>> mesh1 = CylindricalMesh([hr, hp, hz])\n\n    >>> mesh1.plot_grid()\n    >>> plt.show()\n\n    For a cylindrically symmetric mesh, the disretization along the\n    azimuthal direction is set with a flag of *1*. This reduces the\n    size of numerical systems given that the derivative along the\n    azimuthal direction is 0. For example:\n\n    >>> ncr = 10      # number of mesh cells in r\n    >>> ncz = 15      # number of mesh cells in z\n    >>> dr = 15       # cell width r\n    >>> dz = 10       # cell width z\n    >>> npad_r = 4    # number of padding cells in r\n    >>> npad_z = 4    # number of padding cells in z\n    >>> exp_r = 1.25  # expansion rate of padding cells in r\n    >>> exp_z = 1.25  # expansion rate of padding cells in z\n\n    A value of 1 is used to define the discretization in phi for this case.\n\n    >>> hr = [(dr, ncr), (dr, npad_r, exp_r)]\n    >>> hz = [(dz, npad_z, -exp_z), (dz, ncz), (dz, npad_z, exp_z)]\n    >>> mesh2 = CylindricalMesh([hr, 1, hz])\n\n    >>> mesh2.plot_grid()\n    >>> plt.show()\n    \"\"\"\n\n    _meshType = \"CYL\"\n    _unitDimensions = [1, 2 * np.pi, 1]\n    _aliases = {\n        **DiffOperators._aliases,\n        **BaseRectangularMesh._aliases,\n        **BaseTensorMesh._aliases,\n    }\n\n    _items = BaseTensorMesh._items | {\"cartesian_origin\"}\n\n    def __init__(self, h, origin=None, cartesian_origin=None, **kwargs):\n        kwargs.pop(\"reference_system\", None)  # reference system must be cylindrical\n        if \"cartesianOrigin\" in kwargs.keys():\n            cartesian_origin = kwargs.pop(\"cartesianOrigin\")\n        super().__init__(h=h, origin=origin, reference_system=\"cylindrical\", **kwargs)\n\n        if self.h[1].sum() > 2 * np.pi + 1e-10:\n            raise ValueError(\"The 2nd dimension must cannot sum to more than 2*pi.\")\n\n        if self.dim == 2:\n            print(\"Warning, a disk mesh has not been tested thoroughly.\")\n\n        if cartesian_origin is None:\n            cartesian_origin = np.zeros(self.dim)\n        self.cartesian_origin = cartesian_origin\n\n    @property\n    def cartesian_origin(self):\n        \"\"\"Cartesian origin of the mesh.\n\n        Returns the origin or 'anchor point' of the cylindrical mesh\n        in Cartesian coordinates; i.e. [x0, y0, z0]. For cylindrical\n        meshes, the origin is the bottom of the z-axis which defines\n        the mesh's rotational symmetry.\n\n        Returns\n        -------\n        (dim) numpy.ndarray\n            The Cartesian origin (or anchor point) of the mesh\n        \"\"\"\n        return self._cartesian_origin\n\n    @cartesian_origin.setter\n    def cartesian_origin(self, value):\n        # ensure the value is a numpy array\n        value = np.asarray(value, dtype=np.float64)\n        value = np.atleast_1d(value)\n        if len(value) != self.dim:\n            raise ValueError(\n                f\"cartesian origin and shape must be the same length, got {len(value)} and {self.dim}\"\n            )\n        self._cartesian_origin = value\n\n    @property\n    def is_wrapped(self):\n        \"\"\"Whether the mesh discretizes the full azimuthal space.\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        return np.allclose(self.h[1].sum(), 2 * np.pi, atol=1e-10)\n\n    @property\n    def is_symmetric(self):\n        \"\"\"Validate whether mesh is symmetric.\n\n        Symmetric cylindrical meshes have useful mathematical\n        properties that allow us to reduce the computational cost\n        of solving radially symmetric 3D problems.\n        When constructing cylindrical meshes in discretize, we almost\n        always use a flag of *1* when defining the discretization in\n        the azimuthal direction. By doing so, we define a mesh that is\n        symmetric. In this case, the *is_symmetric* returns a value of\n        *True* . If the discretization in the azimuthal direction was\n        defined explicitly, the mesh would not be symmetric and\n        *is_symmetric* would return a value of *False* .\n\n        Returns\n        -------\n        bool\n            *True* if the mesh is symmetric, *False* otherwise\n        \"\"\"\n        return self.is_wrapped and self.shape_cells[1] == 1\n\n    @property\n    def includes_zero(self):\n        \"\"\"Whether the radial dimension starts at 0.\n\n        Returns\n        -------\n        bool\n        \"\"\"\n        return self.origin[0] == 0.0\n\n    @property\n    def shape_nodes(self):\n        \"\"\"Return the number of nodes along each axis.\n\n        This property returns a tuple containing the number of nodes along\n        the :math:`x` (radial), :math:`y` (azimuthal) and :math:`z` (vertical)\n        directions, respectively.\n        In the case where the mesh is symmetric, the number of nodes\n        defining the discretization in the azimuthal direction is *0* ;\n        see :py:attr:`~.CylindricalMesh.is_symmetric`.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of nodes in the :math:`x` (radial),\n            :math:`y` (azimuthal) and :math:`z` (vertical) directions, respectively.\n        \"\"\"\n        vnC = self.shape_cells\n        if self.is_symmetric:\n            if self.includes_zero:\n                return (vnC[0], 0, vnC[2] + 1)\n            return (vnC[0] + 1, 0, vnC[2] + 1)\n        elif self.is_wrapped:\n            return (vnC[0] + 1, vnC[1], vnC[2] + 1)\n        else:\n            return super().shape_nodes\n\n    @property\n    def _shape_total_nodes(self):\n        vnC = self.shape_cells\n        if self.is_symmetric:\n            if self.includes_zero:\n                return (vnC[0], 1, vnC[2] + 1)\n            return (vnC[0] + 1, 1, vnC[2] + 1)\n        else:\n            return tuple(x + 1 for x in vnC)\n\n    @property\n    def n_nodes(self):\n        \"\"\"Return total number of mesh nodes.\n\n        For non-symmetric cylindrical meshes, this property returns\n        the total number of nodes. For symmetric meshes, this property\n        returns a value of 0; see :py:attr:`~.CylindricalMesh.is_symmetric`.\n        The symmetric mesh case is unique because the azimuthal position\n        of the nodes is undefined.\n\n        Returns\n        -------\n        int\n            Total number of nodes for non-symmetric meshes, 0 for symmetric meshes\n        \"\"\"\n        if self.is_symmetric:\n            return 0\n        nx, ny, nz = self.shape_nodes\n        if self.includes_zero:\n            return (nx - 1) * ny * nz + nz\n        else:\n            return nx * ny * nz\n\n    @property\n    def _n_total_nodes(self):\n        return int(np.prod(self._shape_total_nodes))\n\n    @property\n    def _shape_total_faces_x(self):\n        \"\"\"Vector number of total Fx (prior to deflating).\"\"\"\n        return self._shape_total_nodes[:1] + self.shape_cells[1:]\n\n    @property\n    def _n_total_faces_x(self):\n        \"\"\"Number of total Fx (prior to deflating).\"\"\"\n        return int(np.prod(self._shape_total_faces_x))\n\n    @property\n    def _n_hanging_faces_x(self):\n        \"\"\"Number of hanging Fx.\"\"\"\n        if self.includes_zero:\n            return int(np.prod(self.shape_cells[1:]))\n        else:\n            return 0\n\n    @property\n    def shape_faces_x(self):\n        \"\"\"Number of x-faces along each axis direction.\n\n        This property returns the number of x-faces along the\n        :math:`x` (radial), :math:`y` (azimuthal) and :math:`z` (vertical)\n        directions, respectively. Note that for symmetric meshes, the number of x-faces along\n        the azimuthal direction is 1; see :py:attr:`~.CylindricalMesh.is_symmetric`.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of x-faces along the :math:`x` (radial),\n            :math:`y` (azimuthal) and :math:`z` (vertical) directions, respectively.\n        \"\"\"\n        if self.includes_zero:\n            return self.shape_cells\n        else:\n            return super().shape_faces_x\n\n    @property\n    def _shape_total_faces_y(self):\n        \"\"\"Vector number of total Fy (prior to deflating).\"\"\"\n        vnC = self.shape_cells\n        return (vnC[0], self._shape_total_nodes[1]) + vnC[2:]\n\n    @property\n    def _n_total_faces_y(self):\n        \"\"\"Number of total Fy (prior to deflating).\"\"\"\n        return int(np.prod(self._shape_total_faces_y))\n\n    @property\n    def _n_hanging_faces_y(self):\n        \"\"\"Number of hanging y-faces.\"\"\"\n        if self.is_wrapped:\n            return int(np.prod(self.shape_cells[::2]))\n        else:\n            return 0\n\n    @property\n    def _shape_total_faces_z(self):\n        \"\"\"Vector number of total Fz (prior to deflating).\"\"\"\n        return self.shape_cells[:-1] + self._shape_total_nodes[-1:]\n\n    @property\n    def _n_total_faces_z(self):\n        \"\"\"Number of total Fz (prior to deflating).\"\"\"\n        return int(np.prod(self._shape_total_faces_z))\n\n    @property\n    def _n_hanging_faces_z(self):\n        \"\"\"Number of hanging Fz.\"\"\"\n        return 0\n\n    @property\n    def _shape_total_edges_x(self):\n        \"\"\"Vector number of total Ex (prior to deflating).\"\"\"\n        return self.shape_cells[:1] + self._shape_total_nodes[1:]\n\n    @property\n    def _n_total_edges_x(self):\n        \"\"\"Number of total Ex (prior to deflating).\"\"\"\n        return int(np.prod(self._shape_total_edges_x))\n\n    @property\n    def _shape_total_edges_y(self):\n        \"\"\"Vector number of total Ey (prior to deflating).\"\"\"\n        _shape_total_nodes = self._shape_total_nodes\n        return (_shape_total_nodes[0], self.shape_cells[1], _shape_total_nodes[2])\n\n    @property\n    def _n_total_edges_y(self):\n        \"\"\"Number of total Ey (prior to deflating).\"\"\"\n        return int(np.prod(self._shape_total_edges_y))\n\n    @property\n    def shape_edges_y(self):\n        \"\"\"Number of y-edges along each axis direction.\n\n        This property returns the number of y-edges along the\n        :math:`x` (radial), :math:`y` (azimuthal) and :math:`z` (vertical)\n        directions, respectively. Note that for symmetric meshes, the number of y-edges along\n        the azimuthal direction is 1; see :py:attr:`~.CylindricalMesh.is_symmetric`.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of y-edges along the :math:`x` (radial),\n            :math:`y` (azimuthal) and :math:`z` (vertical) directions, respectively.\n        \"\"\"\n        if self.includes_zero:\n            return tuple(x + y for x, y in zip(self.shape_cells, [0, 0, 1]))\n        else:\n            return tuple(x + y for x, y in zip(self.shape_cells, [1, 0, 1]))\n\n    @property\n    def _shape_total_edges_z(self):\n        \"\"\"Vector number of total Ez (prior to deflating).\"\"\"\n        return self._shape_total_nodes[:-1] + self.shape_cells[-1:]\n\n    @property\n    def _n_total_edges_z(self):\n        \"\"\"Number of total Ez (prior to deflating).\"\"\"\n        return int(np.prod(self._shape_total_edges_z))\n\n    @property\n    def shape_edges_z(self):\n        \"\"\"Number of z-edges along each axis direction.\n\n        This property returns the number of z-edges along the\n        :math:`x` (radial), :math:`y` (azimuthal) and :math:`z` (vertical)\n        directions, respectively. Note that for symmetric meshes, the number of z-edges along\n        the azimuthal direction is 0; see :py:attr:`~.CylindricalMesh.is_symmetric`.\n        The symmetric mesh case is unique because the azimuthal position\n        of the z-edges is undefined.\n\n        Returns\n        -------\n        (dim) tuple of int\n            Number of z-edges along the :math:`x` (radial),\n            :math:`y` (azimuthal) and :math:`z` (vertical) direction, respectively.\n        \"\"\"\n        return self.shape_nodes[:-1] + self.shape_cells[-1:]\n\n    @property\n    def n_edges_z(self):\n        \"\"\"Total number of z-edges in the mesh.\n\n        This property returns the total number of z-edges for\n        non-symmetric cyindrical meshes; see :py:attr:`~.CylindricalMesh.is_symmetric`.\n        If the mesh is symmetric, the property returns *0* because the azimuthal\n        position of the z-edges is undefined.\n\n        Returns\n        -------\n        int\n            Number of z-edges for non-symmetric meshes and *0* for symmetric meshes\n        \"\"\"\n        z_shape = self.shape_edges_z\n        cell_shape = self.shape_cells\n        if self.is_symmetric:\n            return int(np.prod(z_shape))\n        if self.includes_zero:\n            return (\n                int(np.prod([z_shape[0] - 1, z_shape[1], cell_shape[2]]))\n                + cell_shape[2]\n            )\n        return int(np.prod(z_shape))\n\n    @property\n    def cell_centers_x(self):\n        \"\"\"Return the x-positions of cell centers along the x-direction.\n\n        This property returns a 1D vector containing the x-position values\n        of the cell centers along the x-direction (radial). The length of the vector\n        is equal to the number of cells in the x-direction.\n\n        Returns\n        -------\n        (n_cells_x) numpy.ndarray\n            x-positions of cell centers along the x-direction\n        \"\"\"\n        nodes = self.nodes_x\n        ccx = 0.5 * (nodes[1:] + nodes[:-1])\n        if self.is_symmetric and self.includes_zero:\n            return np.r_[self.h[0][0] * 0.5, ccx]\n        return ccx\n        # return np.r_[self.origin[0], self.h[0][:-1].cumsum()] + self.h[0] * 0.5\n\n    @property\n    def cell_centers_y(self):\n        \"\"\"Return the y-positions of cell centers along the y-direction (azimuthal).\n\n        This property returns a 1D vector containing the y-position values\n        of the cell centers along the y-direction (azimuthal). The length of the vector\n        is equal to the number of cells in the y-direction. If the mesh is symmetric,\n        this property returns a numpy array with a single entry of *0* ;\n        indicating all cell-centers have a y-position of 0.\n        see :py:attr:`~.CylindricalMesh.is_symmetric`.\n\n        Returns\n        -------\n        (n_cells_y) numpy.ndarray\n            y-positions of cell centers along the y-direction\n        \"\"\"\n        if self.is_symmetric:\n            return np.r_[self.origin[1]]\n        nodes = self._nodes_y_full\n        return (nodes[1:] + nodes[:-1]) / 2\n\n    @property\n    def nodes_x(self):\n        \"\"\"Return the x-positions of nodes along the x-direction (radial).\n\n        This property returns a 1D vector containing the x-position values\n        of the nodes along the x-direction (radial). The length of the vector\n        is equal to the number of nodes in the x-direction.\n\n        Returns\n        -------\n        (n_nodes_x) numpy.ndarray\n            x-positions of nodes along the x-direction\n        \"\"\"\n        nodes = np.r_[self.origin[0], self.h[0]].cumsum()\n        if self.is_symmetric and self.includes_zero:\n            return nodes[1:]\n        return nodes\n\n    @property\n    def _nodes_y_full(self):\n        \"\"\"Full nodal y vector (prior to deflating).\"\"\"\n        if self.is_symmetric:\n            return np.r_[self.origin[1]]\n        return self.origin[1] + np.r_[0, self.h[1].cumsum()]\n\n    @property\n    def nodes_y(self):\n        \"\"\"Return the y-positions of nodes along the y-direction (azimuthal).\n\n        This property returns a 1D vector containing the y-position values\n        of the nodes along the y-direction (azimuthal). If the mesh is symmetric,\n        this property returns a numpy array with a single entry of *0* ;\n        indicating all nodes have a y-position of 0. See :py:attr:`~.CylindricalMesh.is_symmetric`.\n\n        Returns\n        -------\n        (n_nodes_y) numpy.ndarray\n            y-positions of nodes along the y-direction\n        \"\"\"\n        if self.is_wrapped:\n            return self.origin[1] + np.r_[0, self.h[1][:-1].cumsum()]\n        return super().nodes_y\n\n    @property\n    def _edge_x_lengths_full(self):\n        \"\"\"Full x-edge lengths (prior to deflating).\"\"\"\n        nx, ny, nz = self._shape_total_nodes\n        return np.kron(np.ones(nz), np.kron(np.ones(ny), self.h[0]))\n\n    @property\n    def edge_x_lengths(self):\n        \"\"\"Lengths of each x edge for the entire mesh.\n\n        If the mesh is not symmetric, this property returns a 1D vector\n        containing the lengths of all x-edges in the mesh. If the mesh\n        is symmetric, this property returns an empty numpy array since\n        there are no x-edges;\n        see :py:attr:`~CylindricalMesh.is_symmetric`.\n\n        Returns\n        -------\n        (n_edges_x) numpy.ndarray\n            A 1D array containing the x-edge lengths for the entire mesh\n        \"\"\"\n        if getattr(self, \"_edge_lengths_x\", None) is None:\n            self._edge_lengths_x = self._edge_x_lengths_full[~self._ishanging_edges_x]\n        return self._edge_lengths_x\n\n    @property\n    def _edge_y_lengths_full(self):\n        \"\"\"Full vector of y-edge lengths (prior to deflating).\"\"\"\n        if self.is_symmetric:\n            return 2 * pi * self.nodes[:, 0]\n        return np.kron(\n            np.ones(self._shape_total_nodes[2]), np.kron(self.h[1], self.nodes_x)\n        )\n\n    @property\n    def edge_y_lengths(self):\n        r\"\"\"Arc-lengths of each y-edge for the entire mesh.\n\n        This property returns a 1D vector containing the arc-lengths\n        of all y-edges in the mesh. For a single y-edge at radial location\n        :math:`r` with azimuthal width :math:`\\\\Delta \\\\phi`, the arc-length\n        is given by:\n\n        .. math::\n            \\Delta y = r \\Delta \\phi\n\n        Returns\n        -------\n        (n_edges_y) numpy.ndarray\n            A 1D array containing the y-edge arc-lengths for the entire mesh\n        \"\"\"\n        if getattr(self, \"_edge_lengths_y\", None) is None:\n            if self.is_symmetric:\n                self._edge_lengths_y = self._edge_y_lengths_full\n            else:\n                self._edge_lengths_y = self._edge_y_lengths_full[\n                    ~self._ishanging_edges_y\n                ]\n        return self._edge_lengths_y\n\n    @property\n    def _edge_z_lengths_full(self):\n        \"\"\"Full z-edge lengths (prior to deflation).\"\"\"\n        nx, ny, nz = self._shape_total_nodes\n        return np.kron(self.h[2], np.kron(np.ones(ny), np.ones(nx)))\n\n    @property\n    def edge_z_lengths(self):\n        \"\"\"Lengths of each z-edges for the entire mesh.\n\n        If the mesh is not symmetric, this property returns a 1D vector\n        containing the lengths of all z-edges in the mesh. If the mesh\n        is symmetric, this property returns an empty numpy array\n        since there are no z-edges; see :py:attr:`~CylindricalMesh.is_symmetric`.\n\n        Returns\n        -------\n        (n_edges_z) numpy.ndarray\n            A 1D array containing the z-edge lengths for the entire mesh\n        \"\"\"\n        if getattr(self, \"_edge_lengths_z\", None) is None:\n            self._edge_lengths_z = self._edge_z_lengths_full[~self._ishanging_edges_z]\n        return self._edge_lengths_z\n\n    @property\n    def _edge_lengths_full(self):\n        \"\"\"Full edge lengths [r-edges, theta-edges z-edges] (prior to deflation).\"\"\"\n        if self.is_symmetric:\n            raise NotImplementedError\n        else:\n            return np.r_[\n                self._edge_x_lengths_full,\n                self._edge_y_lengths_full,\n                self._edge_z_lengths_full,\n            ]\n\n    @property\n    def edge_lengths(self):\n        \"\"\"Lengths of all mesh edges.\n\n        This property returns a 1D vector containing the lengths\n        of all edges in the mesh organized by x-edges, y-edges, then z-edges;\n        i.e. radial, azimuthal, then vertical. However if the mesh\n        is symmetric, there are no x or z-edges and calling the property\n        returns the y-edge lengths; see :py:attr:`~CylindricalMesh.is_symmetric`.\n        Note that y-edge lengths take curvature into account; see\n        :py:attr:`~.CylindricalMesh.edge_y_lengths`.\n\n        Returns\n        -------\n        (n_edges) numpy.ndarray\n            Edge lengths of all mesh edges organized x (radial), y (azimuthal), then z (vertical)\n        \"\"\"\n        if self.is_symmetric:\n            return self.edge_y_lengths\n        else:\n            return np.r_[self.edge_x_lengths, self.edge_y_lengths, self.edge_z_lengths]\n\n    @property\n    def _face_x_areas_full(self):\n        \"\"\"Areas of x-faces prior to deflation.\"\"\"\n        if self.is_symmetric:\n            return np.kron(self.h[2], 2 * pi * self.nodes_x)\n        return np.kron(self.h[2], np.kron(self.h[1], self.nodes_x))\n\n    @property\n    def face_x_areas(self):\n        r\"\"\"Areas of each x-face for the entire mesh.\n\n        This property returns a 1D vector containing the areas of the\n        x-faces of the mesh. The surface area takes into account curvature.\n        For a single x-face at radial location\n        :math:`r` with azimuthal width :math:`\\Delta \\phi` and vertical\n        width :math:`h_z`, the area is given by:\n\n        .. math::\n            A_x = r \\Delta \\phi h_z\n\n        Returns\n        -------\n        (n_faces_x) numpy.ndarray\n            A 1D array containing the x-face areas for the entire mesh\n        \"\"\"\n        if getattr(self, \"_face_x_areas\", None) is None:\n            if self.is_symmetric:\n                self._face_x_areas = self._face_x_areas_full\n            else:\n                self._face_x_areas = self._face_x_areas_full[~self._ishanging_faces_x]\n        return self._face_x_areas\n\n    @property\n    def _face_y_areas_full(self):\n        \"\"\"Area of y-faces (Azimuthal faces), prior to deflation.\"\"\"\n        return np.kron(\n            self.h[2], np.kron(np.ones(self._shape_total_nodes[1]), self.h[0])\n        )\n\n    @property\n    def face_y_areas(self):\n        \"\"\"Areas of each y-face for the entire mesh.\n\n        This property returns a 1D vector containing the areas of the\n        y-faces of the mesh. For a single y-face with edge lengths\n        :math:`h_x` and :math:`h_z`, the area is given by:\n\n        .. math::\n            A_y = h_x h_z\n\n        *Note that for symmetric meshes* , there are no y-faces and calling\n        this property will return an error.\n\n        Returns\n        -------\n        (n_faces_y) numpy.ndarray\n            A 1D array containing the y-face areas in the case of non-symmetric meshes.\n            Returns an error for symmetric meshes.\n        \"\"\"\n        if getattr(self, \"_face_y_areas\", None) is None:\n            if self.is_symmetric:\n                raise AttributeError(\"There are no y-faces on the Cyl Symmetric mesh\")\n            self._face_y_areas = self._face_y_areas_full[~self._ishanging_faces_y]\n        return self._face_y_areas\n\n    @property\n    def _face_z_areas_full(self):\n        \"\"\"Area of z-faces prior to deflation.\"\"\"\n        if self.is_symmetric and self.includes_zero:\n            return np.kron(\n                np.ones_like(self.nodes_z),\n                pi * (self.nodes_x**2 - np.r_[0, self.nodes_x[:-1]] ** 2),\n            )\n        return np.kron(\n            np.ones(self._shape_total_nodes[2]),\n            np.kron(\n                self.h[1],\n                0.5 * (self.nodes_x[1:] ** 2 - self.nodes_x[:-1] ** 2),\n            ),\n        )\n\n    @property\n    def face_z_areas(self):\n        r\"\"\"Areas of each z-face for the entire mesh.\n\n        This property returns a 1D vector containing the areas of the\n        z-faces of the mesh. The surface area takes into account curvature.\n        For a single z-face at between :math:`r_1` and :math:`r_2`\n        with azimuthal width :math:`\\Delta \\phi`, the area is given by:\n\n        .. math::\n            A_z = \\frac{\\Delta \\phi}{2} (r_2^2 - r_1^2)\n\n        Returns\n        -------\n        (n_faces_z) numpy.ndarray\n            A 1D array containing the z-face areas for the entire mesh\n        \"\"\"\n        if getattr(self, \"_face_z_areas\", None) is None:\n            if self.is_symmetric:\n                self._face_z_areas = self._face_z_areas_full\n            else:\n                self._face_z_areas = self._face_z_areas_full[~self._ishanging_faces_z]\n        return self._face_z_areas\n\n    @property\n    def _face_areas_full(self):\n        \"\"\"Area of all faces (prior to deflation).\"\"\"\n        return np.r_[\n            self._face_x_areas_full, self._face_y_areas_full, self._face_z_areas_full\n        ]\n\n    @property\n    def face_areas(self):\n        \"\"\"Face areas for the entire mesh.\n\n        This property returns a 1D vector containing the areas of all\n        mesh faces organized by x-faces, y-faces, then z-faces;\n        i.e. faces normal to the radial, azimuthal, then vertical direction.\n        Note that for symmetric meshes, there are no y-faces and calling the\n        property will return only the x and z-faces. To see how the face\n        areas corresponding to each component are computed, see\n        :py:attr:`~.CylindricalMesh.face_x_areas`,\n        :py:attr:`~.CylindricalMesh.face_y_areas` and\n        :py:attr:`~.CylindricalMesh.face_z_areas`.\n\n        Returns\n        -------\n        (n_faces) numpy.ndarray\n            Areas of all faces in the mesh\n        \"\"\"\n        # if getattr(self, '_area', None) is None:\n        if self.is_symmetric:\n            return np.r_[self.face_x_areas, self.face_z_areas]\n        else:\n            return np.r_[self.face_x_areas, self.face_y_areas, self.face_z_areas]\n\n    @property\n    def cell_volumes(self):\n        r\"\"\"Volumes of all mesh cells.\n\n        This property returns a 1D vector containing the volumes of\n        all cells in the mesh. When computing the volume of each cell,\n        we take into account curvature. Thus a cell lying within\n        radial distance :math:`r_1` and :math:`r_2`, with height\n        :math:`h_z` and with azimuthal width :math:`\\Delta \\phi`,\n        the volume is given by:\n\n        .. math::\n            V = \\frac{\\Delta \\phi \\, h_z}{2} (r_2^2 - r_1^2)\n\n        Returns\n        -------\n        (n_cells numpy.ndarray\n            Volumes of all mesh cells\n        \"\"\"\n        if getattr(self, \"_cell_volumes\", None) is None:\n            if self.is_symmetric and self.includes_zero:\n                az = pi * (self.nodes_x**2 - np.r_[0, self.nodes_x[:-1]] ** 2)\n                self._cell_volumes = np.kron(self.h[2], az)\n            else:\n                self._cell_volumes = np.kron(\n                    self.h[2],\n                    np.kron(\n                        self.h[1],\n                        0.5 * (self.nodes_x[1:] ** 2 - self.nodes_x[:-1] ** 2),\n                    ),\n                )\n        return self._cell_volumes\n\n    @property\n    def _ishanging_faces_x(self):\n        \"\"\"Boolean vector indicating if an x-face is hanging or not.\"\"\"\n        if getattr(self, \"_ishanging_faces_x_bool\", None) is None:\n            hang_x = np.zeros(self._shape_total_faces_x, dtype=bool, order=\"F\")\n            if self.includes_zero and not self.is_symmetric:\n                hang_x[0] = True\n            self._ishanging_faces_x_bool = hang_x.reshape(-1, order=\"F\")\n        return self._ishanging_faces_x_bool\n\n    @property\n    def _hanging_faces_x(self):\n        \"\"\"Hanging x-faces dictionary mapping.\n\n        Dictionary of the indices of the hanging x-faces (keys) and a list\n        of indices that the eliminated faces map to (if applicable)\n        \"\"\"\n        if getattr(self, \"_hanging_faces_x_dict\", None) is None:\n            if self.includes_zero:\n                hanging_f = np.where(self._ishanging_faces_x)[0]\n                # Mark as None to remove them...\n                deflate_f = [None] * len(hanging_f)\n            else:\n                hanging_f = deflate_f = []\n            self._hanging_faces_x_dict = dict(zip(hanging_f, deflate_f))\n        return self._hanging_faces_x_dict\n\n    @property\n    def _ishanging_faces_y(self):\n        \"\"\"Boolean vector indicating if a y-face is hanging or not.\"\"\"\n        if getattr(self, \"_ishanging_faces_y_bool\", None) is None:\n            hang_y = np.zeros(self._shape_total_faces_y, dtype=bool, order=\"F\")\n            if self.is_wrapped:\n                hang_y[:, -1] = True\n            self._ishanging_faces_y_bool = hang_y.reshape(-1, order=\"F\")\n        return self._ishanging_faces_y_bool\n\n    @property\n    def _hanging_faces_y(self):\n        \"\"\"Hanging y-faces dictionary mapping.\n\n        Dictionary of the indices of the hanging y-faces (keys) and a list\n        of indices that the eliminated faces map to (if applicable).\n        \"\"\"\n        if getattr(self, \"_hanging_faces_y_dict\", None) is None:\n            hanging_f = np.where(self._ishanging_faces_y)[0]\n            nx, ny, nz = self._shape_total_faces_y\n            irs, its, izs = np.unravel_index(hanging_f, (nx, ny, nz), order=\"F\")\n            if self.is_wrapped:\n                ny = ny - 1\n                its %= ny\n            deflate_f = np.ravel_multi_index((irs, its, izs), (nx, ny, nz), order=\"F\")\n            self._hanging_faces_y_dict = dict(zip(hanging_f, deflate_f))\n        return self._hanging_faces_y_dict\n\n    @property\n    def _ishanging_faces_z(self):\n        \"\"\"Boolean vector indicating if a z-face is hanging or not.\"\"\"\n        if getattr(self, \"_ishanging_faces_z_bool\", None) is None:\n            self._ishanging_faces_z_bool = np.zeros(self._n_total_faces_z, dtype=bool)\n        return self._ishanging_faces_z_bool\n\n    @property\n    def _hanging_faces_z(self):\n        \"\"\"Hanging z-faces dictionary mapping.\n\n        Dictionary of the indices of the hanging z-faces (keys) and a list\n        of indices that the eliminated faces map to (if applicable).\n        \"\"\"\n        return {}\n\n    @property\n    def _ishanging_edges_x(self):\n        \"\"\"Boolean vector indicating if a x-edge is hanging or not.\"\"\"\n        if getattr(self, \"_ishanging_edges_x_bool\", None) is None:\n            hang_x = np.zeros(self._shape_total_edges_x, dtype=bool, order=\"F\")\n            if self.is_wrapped:\n                hang_x[:, -1] = True\n            self._ishanging_edges_x_bool = hang_x.reshape(-1, order=\"F\")\n        return self._ishanging_edges_x_bool\n\n    @property\n    def _hanging_edges_x(self):\n        \"\"\"Hanging x-edges dictionary mapping.\n\n        Dictionary of the indices of the hanging x-edges (keys) and a list\n        of indices that the eliminated faces map to (if applicable).\n        \"\"\"\n        if getattr(self, \"_hanging_edges_x_dict\", None) is None:\n            hanging_e = np.where(self._ishanging_edges_x)[0]\n            nx, ny, nz = self._shape_total_edges_x\n            irs, its, izs = np.unravel_index(hanging_e, (nx, ny, nz), order=\"F\")\n            if self.is_wrapped:\n                ny = ny - 1\n                its %= ny\n            deflated_e = np.ravel_multi_index((irs, its, izs), (nx, ny, nz), order=\"F\")\n            self._hanging_edges_x_dict = dict(zip(hanging_e, deflated_e))\n        return self._hanging_edges_x_dict\n\n    @property\n    def _ishanging_edges_y(self):\n        \"\"\"Boolean vector indicating if a y-edge is hanging or not.\"\"\"\n        if getattr(self, \"_ishanging_edges_y_bool\", None) is None:\n            hang_y = np.zeros(self._shape_total_edges_y, dtype=bool, order=\"F\")\n            if self.includes_zero or self.is_symmetric:\n                hang_y[0] = True\n            self._ishanging_edges_y_bool = hang_y.reshape(-1, order=\"F\")\n        return self._ishanging_edges_y_bool\n\n    @property\n    def _hanging_edges_y(self):\n        \"\"\"Hanging y-edges dictionary mapping.\n\n        Dictionary of the indices of the hanging y-edges (keys) and a list\n        of indices that the eliminated faces map to (if applicable).\n        \"\"\"\n        if getattr(self, \"_hanging_edges_y_dict\", None) is None:\n            if self.includes_zero:\n                hanging_e = np.where(self._ishanging_edges_y)[0]\n                # Mark as None to remove them...\n                deflate_e = [None] * len(hanging_e)\n            else:\n                hanging_e = deflate_e = []\n            self._hanging_edges_y_dict = dict(zip(hanging_e, deflate_e))\n        return self._hanging_edges_y_dict\n\n    @property\n    def _ishanging_edges(self):\n        if self.dim == 2:\n            return np.r_[self._ishanging_edges_x, self._ishanging_edges_y]\n        else:\n            return np.r_[\n                self._ishanging_edges_x,\n                self._ishanging_edges_y,\n                self._ishanging_edges_z,\n            ]\n\n    @property\n    def _ishanging_edges_z(self):\n        \"\"\"Boolean vector indicating if a z-edge is hanging or not.\"\"\"\n        if getattr(self, \"_ishanging_edges_z_bool\", None) is None:\n            if self.is_symmetric:\n                self._ishanging_edges_z_bool = np.ones(\n                    self._n_total_edges_z, dtype=bool\n                )\n            else:\n                is_hanging = np.zeros(self._shape_total_edges_z, dtype=bool, order=\"F\")\n                if self.includes_zero:\n                    is_hanging[0] = True  # axis of symmetry nodes are hanging\n                    is_hanging[0, 0] = (\n                        False  # axis of symmetry nodes which are not hanging\n                    )\n                if self.is_wrapped:\n                    is_hanging[:, -1] = (\n                        True  # nodes at maximum theta that are duplicated\n                    )\n                self._ishanging_edges_z_bool = is_hanging.reshape(-1, order=\"F\")\n\n        return self._ishanging_edges_z_bool\n\n    @property\n    def _hanging_edges_z(self):\n        \"\"\"Hanging z-edges dictionary mapping.\n\n        Dictionary of the indices of the hanging z-edges (keys) and a list\n        of indices that the eliminated faces map to (if applicable).\n        \"\"\"\n        if getattr(self, \"_hanging_edges_z_dict\", None) is None:\n            hanging_e = np.where(self._ishanging_edges_z)[0]\n            nx, ny, nz = self._shape_total_edges_z\n            irs, its, izs = np.unravel_index(hanging_e, (nx, ny, nz), order=\"F\")\n            # If wrapped, map max it to it=0.\n            if self.is_wrapped:\n                ny = ny - 1\n                its %= ny\n            # If I include zero, wrap all the thetas at the center together\n            if self.includes_zero:\n                centers = irs == 0\n                its[centers] = 0\n                deflated_e = irs + ny * its + ((nx - 1) * ny + 1) * izs\n            else:\n                deflated_e = np.ravel_multi_index(\n                    (irs, its, izs), (nx, ny, nz), order=\"F\"\n                )\n            self._hanging_edges_z_dict = dict(zip(hanging_e, deflated_e))\n        return self._hanging_edges_z_dict\n\n    @property\n    def _ishanging_nodes(self):\n        \"\"\"Boolean vector indicating if a node is hanging or not.\"\"\"\n        if getattr(self, \"_ishanging_nodes_bool\", None) is None:\n            if self.is_symmetric:\n                self._ishanging_nodes_bool = np.zeros(self._n_total_nodes, dtype=bool)\n            else:\n                is_hanging = np.zeros(self._shape_total_nodes, dtype=bool, order=\"F\")\n                if self.includes_zero:\n                    is_hanging[0, 1:] = True  # axis of symmetry nodes that are hanging\n                if self.is_wrapped:\n                    is_hanging[:, -1] = (\n                        True  # nodes at maximum theta that are duplicated\n                    )\n                self._ishanging_nodes_bool = is_hanging.reshape(-1, order=\"F\")\n\n        return self._ishanging_nodes_bool\n\n    @property\n    def _hanging_nodes(self):\n        \"\"\"Hanging nodes dictionary mapping.\n\n        Dictionary of the indices of the hanging nodes (keys) and a list\n        of indices that the eliminated nodes map to (if applicable).\n        \"\"\"\n        if getattr(self, \"_hanging_nodes_dict\", None) is None:\n            hanging_nodes = np.where(self._ishanging_nodes)[0]\n            nx, ny, nz = self._shape_total_nodes\n            irs, its, izs = np.unravel_index(hanging_nodes, (nx, ny, nz), order=\"F\")\n            # If wrapped, map max it to it=0.\n            if self.is_wrapped:\n                ny = ny - 1\n                its %= ny\n            # If I include zero, wrap all the thetas at the center together\n            if self.includes_zero:\n                centers = irs == 0\n                its[centers] = 0\n                deflated_n = irs + ny * its + ((nx - 1) * ny + 1) * izs\n            else:\n                deflated_n = np.ravel_multi_index(\n                    (irs, its, izs), (nx, ny, nz), order=\"F\"\n                )\n\n            self._hanging_nodes_dict = dict(zip(hanging_nodes, deflated_n))\n        return self._hanging_nodes_dict\n\n    ####################################################\n    # Grids\n    ####################################################\n\n    @property\n    def _nodes_full(self):\n        \"\"\"Full Nodal grid (including hanging nodes).\"\"\"\n        return ndgrid([self.nodes_x, self._nodes_y_full, self.nodes_z])\n\n    @property\n    def nodes(self):\n        r\"\"\"Gridded node locations.\n\n        This property outputs a numpy array containing the gridded\n        locations of all mesh nodes in cylindrical ccordinates;\n        i.e. :math:`(r, \\phi, z)`. Note that for symmetric meshes, the azimuthal\n        position of all nodes is set to :math:`\\phi = 0`.\n\n        Returns\n        -------\n        (n_nodes, dim) numpy.ndarray\n            gridded node locations\n        \"\"\"\n        if self.is_symmetric:\n            self._nodes = self._nodes_full\n        if getattr(self, \"_nodes\", None) is None:\n            self._nodes = self._nodes_full[~self._ishanging_nodes, :]\n        return self._nodes\n\n    @property\n    def _faces_x_full(self):\n        \"\"\"Full Fx grid (including hanging faces).\"\"\"\n        return ndgrid([self.nodes_x, self.cell_centers_y, self.cell_centers_z])\n\n    @property\n    def faces_x(self):\n        r\"\"\"Gridded x-face (radial face) locations.\n\n        This property outputs a numpy array containing the gridded\n        locations of all x-faces (radial faces) in cylindrical coordinates;\n        i.e. the :math:`(r, \\phi, z)` position of the center of each face.\n        The shape of the array is (n_faces_x, 3). Note that for symmetric meshes,\n        the azimuthal position of all x-faces is set to :math:`\\phi = 0`.\n\n        Returns\n        -------\n        (n_faces_x, dim) numpy.ndarray\n            gridded x-face (radial face) locations\n        \"\"\"\n        if getattr(self, \"_faces_x\", None) is None:\n            if self.is_symmetric:\n                return super().faces_x\n            else:\n                self._faces_x = self._faces_x_full[~self._ishanging_faces_x, :]\n        return self._faces_x\n\n    @property\n    def _edges_y_full(self):\n        \"\"\"Full grid of y-edges (including eliminated edges).\"\"\"\n        return super().edges_y\n\n    @property\n    def edges_y(self):\n        r\"\"\"Gridded y-edge (azimuthal edge) locations.\n\n        This property outputs a numpy array containing the gridded\n        locations of all y-edges (azimuthal edges) in cylindrical coordinates;\n        i.e. the :math:`(r, \\phi, z)` position of the middle of each y-edge.\n        The shape of the array is (n_edges_y, 3). Note that for symmetric meshes,\n        the azimuthal position of all y-edges is set to :math:`\\phi = 0`.\n\n        Returns\n        -------\n        (n_edges_y, dim) numpy.ndarray\n            gridded y-edge (azimuthal edge) locations\n        \"\"\"\n        if getattr(self, \"_edges_y\", None) is None:\n            if self.is_symmetric:\n                return self._edges_y_full\n            else:\n                self._edges_y = self._edges_y_full[~self._ishanging_edges_y, :]\n        return self._edges_y\n\n    @property\n    def _edges_z_full(self):\n        \"\"\"Full z-edge grid (including hanging edges).\"\"\"\n        return ndgrid([self.nodes_x, self._nodes_y_full, self.cell_centers_z])\n\n    @property\n    def edges_z(self):\n        r\"\"\"Gridded z-edge (vertical edge) locations.\n\n        This property outputs a numpy array containing the gridded\n        locations of all z-edges (vertical edges) in cylindrical coordinates;\n        i.e. the :math:`(r, \\phi, z)` position of the middle of each z-edge.\n        The shape of the array is (n_edges_z, 3). In the case of symmetric\n        meshes, there are no z-edges and this property returns *None*.\n\n        Returns\n        -------\n        (n_edges_z, dim) numpy.ndarray or None\n            gridded z-edge (vertical edge) locations. Returns *None* for symmetric meshes.\n        \"\"\"\n        if getattr(self, \"_edges_z\", None) is None:\n            if self.is_symmetric:\n                self._edges_z = None\n            else:\n                self._edges_z = self._edges_z_full[~self._ishanging_edges_z, :]\n        return self._edges_z\n\n    @property\n    def _is_boundary_face(self):\n        is_br = np.zeros(self._shape_total_faces_x, dtype=bool, order=\"F\")\n        # if I don't start at r=0, then the inner faces are boundary faces\n        if not self.includes_zero:\n            is_br[0] = True\n        # outer most radial faces are a boundaries\n        is_br[-1] = True\n        is_br = is_br.reshape(-1, order=\"F\")\n        # Theta face is on a boundary if not wrapped\n        is_bt = np.zeros(self._shape_total_faces_y, dtype=bool, order=\"F\")\n        if not self.is_wrapped:\n            is_bt[:, [0, -1]] = True\n        is_bt = is_bt.reshape(-1, order=\"F\")\n        # top and bottom faces are boundaries\n        is_bz = np.zeros(self.shape_faces_z, dtype=bool, order=\"F\")\n        is_bz[:, :, [0, -1]] = True\n        is_bz = is_bz.reshape(-1, order=\"F\")\n\n        is_b = np.r_[\n            is_br[~self._ishanging_faces_x], is_bt[~self._ishanging_faces_y], is_bz\n        ]\n        return is_b\n\n    @property\n    def boundary_faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.faces[self._is_boundary_face]\n\n    @property\n    def boundary_face_outward_normals(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        normals = self.face_normals[self._is_boundary_face]\n\n        # determine which to flip\n        neg_x = np.zeros(self._shape_total_faces_x, dtype=bool, order=\"F\")\n        neg_x[0] = True\n        neg_x = neg_x.reshape(-1, order=\"F\")\n        neg_y = np.zeros(self._shape_total_faces_y, dtype=bool, order=\"F\")\n        neg_y[:, 0] = True\n        neg_y = neg_y.reshape(-1, order=\"F\")\n        neg_z = np.zeros(self._shape_total_faces_z, dtype=bool, order=\"F\")\n        neg_z[:, :, 0] = True\n        neg_z = neg_z.reshape(-1, order=\"F\")\n        neg = np.r_[\n            neg_x[~self._ishanging_faces_x], neg_y[~self._ishanging_faces_y], neg_z\n        ][self._is_boundary_face]\n\n        # then n_cells_theta * n_cells_r, bottom faces,\n        normals[neg] = -normals[neg]\n        return normals\n\n    @property\n    def _is_boundary_node(self):\n        is_b = np.zeros(self._shape_total_nodes, dtype=bool, order=\"F\")\n        # outward rs are boundary:\n        is_b[-1] = True\n        if not self.includes_zero:\n            is_b[0] = True\n        if not self.is_wrapped:\n            is_b[:, [0, -1]] = True\n        # top and bottom zs are boundary\n        is_b[:, :, [0, -1]] = True\n        is_b = is_b.reshape(-1, order=\"F\")\n        is_b = is_b[~self._ishanging_nodes]\n        return is_b\n\n    @property\n    def boundary_nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.nodes[self._is_boundary_node]\n\n    @property\n    def _is_boundary_edge(self):\n        # top and bottom radial edges are on the boundary\n        is_br = np.zeros(self._shape_total_edges_x, dtype=bool, order=\"F\")\n        is_br[:, :, [0, -1]] = True\n        if not self.is_wrapped:\n            is_br[:, [0, -1]] = True\n        is_br = is_br.reshape(-1, order=\"F\")\n        is_bt = np.zeros(self._shape_total_edges_y, dtype=bool, order=\"F\")\n        # outside theta edges are on boundary\n        is_bt[-1] = True\n        if not self.includes_zero:\n            is_bt[0] = True\n        # top and bottom theta edges are on boundary\n        is_bt[:, :, [0, -1]] = True\n        is_bt = is_bt.reshape(-1, order=\"F\")\n        # outside z edges are on boundaries\n        is_bz = np.zeros(self._shape_total_edges_z, dtype=bool, order=\"F\")\n        is_bz[-1] = True\n        if not self.includes_zero or not self.is_wrapped:\n            is_bz[0] = True\n        is_bz = is_bz.reshape(-1, order=\"F\")\n\n        is_b = np.r_[\n            is_br[~self._ishanging_edges_x],\n            is_bt[~self._ishanging_edges_y],\n            is_bz[~self._ishanging_edges_z],\n        ]\n        return is_b\n\n    @property\n    def boundary_edges(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.edges[self._is_boundary_edge]\n\n    ####################################################\n    # Operators\n    ####################################################\n\n    @property\n    def face_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_face_divergence\", None) is None:\n            # Compute faceDivergence operator on faces\n            D1 = self.face_x_divergence\n            D3 = self.face_z_divergence\n            if self.is_symmetric:\n                D = sp.hstack((D1, D3), format=\"csr\")\n            elif self.shape_cells[1] > 1:\n                D2 = self.face_y_divergence\n                D = sp.hstack((D1, D2, D3), format=\"csr\")\n            self._face_divergence = D\n        return self._face_divergence\n\n    @property\n    def face_x_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseTensorMesh\n        if getattr(self, \"_face_x_divergence\", None) is None:\n            if self.is_symmetric and self.includes_zero:\n                ncx, ncy, ncz = self.shape_cells\n                D1 = kron3(speye(ncz), speye(ncy), ddx(ncx)[:, 1:])\n            else:\n                D1 = super()._face_x_divergence_stencil\n\n            S = self._face_x_areas_full\n            V = self.cell_volumes\n            self._face_x_divergence = sdiag(1 / V) * D1 * sdiag(S)\n\n            if not self.is_symmetric:\n                self._face_x_divergence = (\n                    self._face_x_divergence\n                    * self._deflation_matrix(\"Fx\", as_ones=True).T\n                )\n\n        return self._face_x_divergence\n\n    @property\n    def face_y_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseTensorMesh\n        if getattr(self, \"_face_y_divergence\", None) is None:\n            D2 = super()._face_y_divergence_stencil\n            S = self._face_y_areas_full  # self.reshape(self.face_areas, 'F', 'Fy', 'V')\n            V = self.cell_volumes\n            self._face_y_divergence = (\n                sdiag(1 / V)\n                * D2\n                * sdiag(S)\n                * self._deflation_matrix(\"Fy\", as_ones=True).T\n            )\n        return self._face_y_divergence\n\n    @property\n    def face_z_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseTensorMesh\n        if getattr(self, \"_face_z_divergence\", None) is None:\n            D3 = super()._face_z_divergence_stencil\n            S = self._face_z_areas_full\n            V = self.cell_volumes\n            self._face_z_divergence = sdiag(1 / V) * D3 * sdiag(S)\n        return self._face_z_divergence\n\n    @property\n    def cell_gradient_x(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseTensorMesh\n        raise NotImplementedError(\"Cell Grad is not yet implemented.\")\n        # if getattr(self, '_cellGradx', None) is None:\n        #     G1 = super(CylindricalMesh, self).stencil_cell_gradient_x\n        #     V = self._deflation_matrix('F', withHanging='True', as_ones='True')*self.aveCC2F*self.cell_volumes\n        #     A = self.face_areas\n        #     L = (A/V)[:self._n_total_faces_x]\n        #     # L = self.reshape(L, 'F', 'Fx', 'V')\n        #     # L = A[:self.nFx] / V\n        #     self._cellGradx = self._deflation_matrix('Fx')*sdiag(L)*G1\n        # return self._cellGradx\n\n    @property\n    def stencil_cell_gradient_y(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseTensorMesh\n        raise NotImplementedError(\"Cell Grad is not yet implemented.\")\n\n    @property\n    def stencil_cell_gradient_z(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseTensorMesh\n        raise NotImplementedError(\"Cell Grad is not yet implemented.\")\n\n    @property\n    def stencil_cell_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        raise NotImplementedError(\"Cell Grad is not yet implemented.\")\n\n    @property\n    def cell_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseTensorMesh\n        raise NotImplementedError(\"Cell Grad is not yet implemented.\")\n\n    @property\n    def nodal_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.is_symmetric:\n            return None\n        if getattr(self, \"_nodal_gradient\", None) is None:\n            if self.dim == 2:\n                Gr = sp.kron(\n                    speye(self._shape_total_nodes[1]), ddx(self.shape_cells[0])\n                )\n                Gphi = sp.kron(\n                    ddx(self.shape_cells[1]), speye(self._shape_total_nodes[0])\n                )\n                Gz = None\n            else:\n                Gr = kron3(\n                    speye(self._shape_total_nodes[2]),\n                    speye(self._shape_total_nodes[1]),\n                    ddx(self.shape_cells[0]),\n                )\n                Gphi = kron3(\n                    speye(self._shape_total_nodes[2]),\n                    ddx(self.shape_cells[1]),\n                    speye(self._shape_total_nodes[0]),\n                )\n                Gz = kron3(\n                    ddx(self.shape_cells[2]),\n                    speye(self._shape_total_nodes[1]),\n                    speye(self._shape_total_nodes[0]),\n                )\n            # remove the hanging edges\n            G = sp.vstack([Gr, Gphi, Gz])[~self._ishanging_edges]\n\n            # apply inflation to map true nodes to hanging nodes with the same values\n            G = (\n                sdiag(1 / self.edge_lengths)\n                @ G\n                @ self._deflation_matrix(\"nodes\", as_ones=True).T\n            )\n            self._nodal_gradient = G\n        return self._nodal_gradient\n\n    @property\n    def _edge_curl_stencil(self):\n        if self.is_symmetric:\n            nCx, nCy, nCz = self.shape_cells\n            # 1D Difference matricies\n            if self.includes_zero:\n                dr = sp.diags([-1, 1], [-1, 0], shape=(nCx, nCx), format=\"csr\")\n                dz = sp.diags([-1, 1], [0, 1], shape=(nCz, nCz + 1), format=\"csr\")\n            else:\n                dr = sp.diags([-1, 1], [0, 1], shape=(nCx, nCx + 1), format=\"csr\")\n                dz = sp.diags([-1, 1], [0, 1], shape=(nCz, nCz + 1), format=\"csr\")\n            # 2D Difference matricies\n            Dr = sp.kron(sp.identity(nCz + 1), dr)\n            Dz = -sp.kron(dz, sp.identity(dr.shape[-1]))\n            return sp.vstack((Dz, Dr))\n        else:\n            stencil = super()._edge_curl_stencil\n            P_f = self._deflation_matrix(\"faces\")\n            P_e = self._deflation_matrix(\"edges\", as_ones=True)\n            return P_f @ stencil @ P_e.T\n\n    @property\n    def edge_curl(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_edge_curl\", None) is None:\n            self._edge_curl = (\n                sdiag(1 / self.face_areas)\n                * self._edge_curl_stencil\n                * sdiag(self.edge_lengths)\n            )\n        return self._edge_curl\n\n    @property\n    def average_edge_x_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DiffOperators\n        if self.is_symmetric:\n            raise AttributeError(\"There are no x-edges on a cyl symmetric mesh\")\n        return (\n            kron3(\n                av(self.shape_cells[2]),\n                av(self.shape_cells[1]),\n                speye(self.shape_cells[0]),\n            )\n            * self._deflation_matrix(\"Ex\", as_ones=True).T\n        )\n\n    @property\n    def average_edge_y_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DiffOperators\n        if self.is_symmetric:\n            if self.includes_zero:\n                avR = av(self.shape_cells[0])[:, 1:]\n            else:\n                avR = av(self.shape_cells[0])\n            return sp.kron(av(self.shape_cells[2]), avR, format=\"csr\")\n        else:\n            return (\n                kron3(\n                    av(self.shape_cells[2]),\n                    speye(self.shape_cells[1]),\n                    av(self.shape_cells[0]),\n                )\n                * self._deflation_matrix(\"Ey\", as_ones=True).T\n            )\n\n    @property\n    def average_edge_z_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DiffOperators\n        if self.is_symmetric:\n            raise AttributeError(\"There are no z-edges on a cyl symmetric mesh\")\n        return (\n            kron3(\n                speye(self.shape_cells[2]),\n                av(self.shape_cells[1]),\n                av(self.shape_cells[0]),\n            )\n            * self._deflation_matrix(\"Ez\", as_ones=True).T\n        )\n\n    @property\n    def average_edge_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_edge_to_cell\", None) is None:\n            # The number of cell centers in each direction\n            # n = self.vnC\n            if self.is_symmetric:\n                self._average_edge_to_cell = self.aveEy2CC\n            else:\n                self._average_edge_to_cell = (\n                    1.0\n                    / self.dim\n                    * sp.hstack(\n                        (self.aveEx2CC, self.aveEy2CC, self.aveEz2CC), format=\"csr\"\n                    )\n                )\n        return self._average_edge_to_cell\n\n    @property\n    def average_edge_to_cell_vector(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.is_symmetric:\n            return self.average_edge_to_cell\n        else:\n            if getattr(self, \"_average_edge_to_cell_vector\", None) is None:\n                self._average_edge_to_cell_vector = sp.block_diag(\n                    (self.aveEx2CC, self.aveEy2CC, self.aveEz2CC), format=\"csr\"\n                )\n        return self._average_edge_to_cell_vector\n\n    @property\n    def average_edge_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.is_symmetric:\n            nx, _, nz = self.shape_edges_y\n\n            if self.includes_zero:\n                e_to_fx = sp.kron(av(nz - 1), sp.eye(nx))\n                e_to_fz = sp.kron(sp.eye(nz), av(nx).toarray()[:, 1:])\n            else:\n                e_to_fx = sp.kron(av(nz - 1), sp.eye(nx))\n                e_to_fz = sp.kron(sp.eye(nz), av(nx - 1))\n\n            return sp.vstack([e_to_fx, e_to_fz])\n        else:\n            Av = super().average_edge_to_face\n            # then need to deflate it...\n            De = self._deflation_matrix(\"edges\", as_ones=True)\n            Df = self._deflation_matrix(\"faces\", as_ones=False)\n            return Df @ Av @ De.T\n\n    @property\n    def average_face_x_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DiffOperators\n        if self.includes_zero:\n            avR = av(self.vnC[0])[:, 1:]\n            return kron3(speye(self.vnC[2]), speye(self.vnC[1]), avR)\n        return super().average_face_x_to_cell\n\n    @property\n    def average_face_y_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DiffOperators\n        return (\n            super().average_face_y_to_cell\n            * self._deflation_matrix(\"Fy\", as_ones=True).T\n        )\n\n    @property\n    def average_face_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_face_to_cell\", None) is None:\n            if self.is_symmetric:\n                self._average_face_to_cell = 0.5 * (\n                    sp.hstack((self.aveFx2CC, self.aveFz2CC), format=\"csr\")\n                )\n            else:\n                self._average_face_to_cell = (\n                    1.0\n                    / self.dim\n                    * (\n                        sp.hstack(\n                            (self.aveFx2CC, self.aveFy2CC, self.aveFz2CC), format=\"csr\"\n                        )\n                    )\n                )\n        return self._average_face_to_cell\n\n    @property\n    def average_face_to_cell_vector(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_face_to_cell_vector\", None) is None:\n            # n = self.vnC\n            if self.is_symmetric:\n                self._average_face_to_cell_vector = sp.block_diag(\n                    (self.aveFx2CC, self.aveFz2CC), format=\"csr\"\n                )\n            else:\n                self._average_face_to_cell_vector = sp.block_diag(\n                    (self.aveFx2CC, self.aveFy2CC, self.aveFz2CC), format=\"csr\"\n                )\n        return self._average_face_to_cell_vector\n\n    @property\n    def _average_node_to_face_x(self):\n        aveN2Fx = kron3(\n            av(self.shape_cells[2]),\n            av(self.shape_cells[1]),\n            speye(self._shape_total_nodes[0]),\n        )\n        return aveN2Fx[~self._ishanging_faces_x]\n\n    @property\n    def _average_node_to_face_y(self):\n        aveN2Fy = kron3(\n            av(self.shape_cells[2]),\n            speye(self._shape_total_nodes[1]),\n            av(self.shape_cells[0]),\n        )\n        return aveN2Fy[~self._ishanging_faces_y]\n\n    @property\n    def average_node_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_face\", None) is None:\n            ave = super().average_node_to_face\n            ave = ave @ self._deflation_matrix(\"nodes\", as_ones=True).T\n            self._average_node_to_face = ave\n        return self._average_node_to_face\n\n    @property\n    def average_node_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_cell\", None) is None:\n            ave = super().average_node_to_cell\n            ave = ave @ self._deflation_matrix(\"nodes\", as_ones=True).T\n            self._average_node_to_cell = ave\n        return self._average_node_to_cell\n\n    @property\n    def average_cell_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_cell_to_face\", None) is None:\n            if self.dim == 3:\n                nx, ny, nz = self.shape_cells\n                if self.is_symmetric and self.includes_zero:\n                    av_c2f_r = kron3(\n                        speye(nz),\n                        speye(ny),\n                        av_extrap(nx)[1:],\n                    )\n                else:\n                    av_c2f_r = kron3(\n                        speye(nz),\n                        speye(ny),\n                        av_extrap(nx),\n                    )[~self._ishanging_faces_x]\n\n                if not self.is_symmetric:\n                    av_c2f_t = self._deflation_matrix(\"faces_y\") @ kron3(\n                        speye(nz),\n                        av_extrap(ny),\n                        speye(nx),\n                    )\n                else:\n                    av_c2f_t = None\n\n                av_c2f_z = kron3(\n                    av_extrap(nz),\n                    speye(ny),\n                    speye(nx),\n                )\n\n                self._average_cell_to_face = sp.vstack(\n                    (av_c2f_r, av_c2f_t, av_c2f_z),\n                    format=\"csr\",\n                )\n        return self._average_cell_to_face\n\n    @property\n    def project_face_to_boundary_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        P = speye(self.n_faces)\n        return P[self._is_boundary_face]\n\n    @property\n    def project_node_to_boundary_node(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        P = speye(self.n_nodes)\n        return P[self._is_boundary_node]\n\n    @property\n    def project_edge_to_boundary_edge(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        P = speye(self.n_edges)\n        return P[self._is_boundary_edge]\n\n    ####################################################\n    # Deflation Matrices\n    ####################################################\n\n    def _deflation_matrix(self, location, as_ones=False):\n        \"\"\"Construct the deflation matrix.\n\n        Construct the deflation matrix to remove hanging edges / faces / nodes\n        from the operators.\n        \"\"\"\n        location = self._parse_location_type(location)\n        if location not in [\n            \"nodes\",\n            \"faces\",\n            \"faces_x\",\n            \"faces_y\",\n            \"faces_z\",\n            \"edges\",\n            \"edges_x\",\n            \"edges_y\",\n            \"edges_z\",\n            \"cell_centers\",\n        ]:\n            raise ValueError(\n                \"Location must be a grid location, not {}\".format(location)\n            )\n        if not (self.is_symmetric or self.is_wrapped or self.includes_zero):\n            return Identity()\n        if location == \"cell_centers\":\n            return speye(self.n_cells)\n        if self.is_symmetric and location == \"nodes\":\n            return sp.csr_matrix((self.n_nodes, self._n_total_nodes))\n\n        elif location in [\"edges\", \"faces\"]:\n            if self.is_symmetric:\n                if location == \"edges\":\n                    return self._deflation_matrix(\"edges_y\", as_ones=as_ones)\n                elif location == \"faces\":\n                    return sp.block_diag(\n                        [\n                            self._deflation_matrix(location + coord, as_ones=as_ones)\n                            for coord in [\"_x\", \"_z\"]\n                        ]\n                    )\n            return sp.block_diag(\n                [\n                    self._deflation_matrix(location + coord, as_ones=as_ones)\n                    for coord in [\"_x\", \"_y\", \"_z\"]\n                ]\n            )\n\n        n_total = getattr(self, \"_n_total_{}\".format(location))\n        n_items = getattr(self, \"n_{}\".format(location))\n        if n_total == n_items:\n            return sp.eye(n_total, format=\"csr\")\n        is_hanging = getattr(self, \"_ishanging_{}\".format(location))\n        hanging_dict = getattr(self, \"_hanging_{}\".format(location))\n\n        vs = np.ones(n_total)\n        inds = np.empty(n_total, dtype=int)\n        inds[~is_hanging] = np.arange(n_items)\n        for k, v in hanging_dict.items():\n            if v is not None:\n                inds[k] = v\n            else:\n                inds[k] = 0\n                vs[k] = 0\n\n        R = sp.csr_matrix((vs, (inds, np.arange(n_total))), shape=(n_items, n_total))\n        if not as_ones:\n            R = sdiag(1.0 / R.sum(1)) * R\n        return R\n\n    ####################################################\n    # Interpolation\n    ####################################################\n\n    def get_interpolation_matrix(\n        self, loc, location_type=\"cell_centers\", zeros_outside=False, **kwargs\n    ):\n        r\"\"\"Construct interpolation matrix from mesh.\n\n        This method allows the user to construct a sparse linear-interpolation\n        matrix which interpolates discrete quantities from mesh centers, nodes,\n        edges or faces to an arbitrary set of locations in 3D space.\n        Locations are defined in cylindrical coordinates; i.e. :math:`(r, \\phi, z)`.\n\n        Parameters\n        ----------\n        loc : (n_pts, dim) numpy.ndarray\n            Location of points to interpolate to in cylindrical coordinates ; i.e.\n            :math:`(r, \\phi, z)`\n        location_type : str\n            What discrete quantity on the mesh you are interpolating from. Options are:\n\n            - 'Ex', 'edges_x'           -> x-component of field defined on x edges\n            - 'Ey', 'edges_y'           -> y-component of field defined on y edges\n            - 'Ez', 'edges_z'           -> z-component of field defined on z edges\n            - 'Fx', 'faces_x'           -> x-component of field defined on x faces\n            - 'Fy', 'faces_y'           -> y-component of field defined on y faces\n            - 'Fz', 'faces_z'           -> z-component of field defined on z faces\n            - 'N', 'nodes'              -> scalar field defined on nodes\n            - 'CC', 'cell_centers'      -> scalar field defined on cell centers\n            - 'CCVx', 'cell_centers_x'  -> x-component of vector field defined on cell centers\n            - 'CCVy', 'cell_centers_y'  -> y-component of vector field defined on cell centers\n            - 'CCVz', 'cell_centers_z'  -> z-component of vector field defined on cell centers\n\n        zeros_outside : bool\n            If *False* , nearest neighbour is used to compute the value for\n            locations outside the mesh. If *True* , values outside the mesh\n            will be equal to zero.\n\n        Returns\n        -------\n        (n_pts, n_loc_type) scipy.sparse.csr_matrix\n            The interpolation matrix\n\n        \"\"\"\n        if \"locType\" in kwargs:\n            raise TypeError(\n                \"The locType keyword argument has been removed, please use location_type. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n        if \"zerosOutside\" in kwargs:\n            raise TypeError(\n                \"The zerosOutside keyword argument has been removed, please use zeros_outside. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n\n        location_type = self._parse_location_type(location_type)\n        if self.is_symmetric and location_type in [\"edges_x\", \"edges_z\", \"faces_y\"]:\n            raise ValueError(\n                \"Symmetric CylindricalMesh does not support {0!s} interpolation, \"\n                \"as this variable does not exist.\".format(location_type)\n            )\n\n        loc = as_array_n_by_dim(loc, self.dim).copy()\n        loc[:, 1] = loc[:, 1] % (2 * np.pi)\n\n        if location_type in [\"cell_centers_x\", \"cell_centers_y\", \"cell_centers_z\"]:\n            Q = interpolation_matrix(loc, *self.get_tensor(\"cell_centers\"))\n            Z = spzeros(loc.shape[0], self.nC)\n            if location_type[-1] == \"x\":\n                Q = sp.hstack([Q, Z])\n            elif location_type[-1] == \"y\":\n                Q = sp.hstack([Q])\n            elif location_type[-1] == \"z\":\n                Q = sp.hstack([Z, Q])\n            Q = Q.tocsr()\n        elif location_type == \"nodes\":\n            rtz = [self.nodes_x, self._nodes_y_full]\n            if self.dim == 3:\n                rtz.append(self.nodes_z)\n            Q = interpolation_matrix(loc, *rtz)\n            Q = Q @ self._deflation_matrix(\"nodes\", as_ones=True).T\n        elif location_type == \"cell_centers\":\n            rtz = [\n                self.cell_centers_x,\n            ]\n            # theta wrap around interpolation\n            if self.is_wrapped:\n                rtz.append(\n                    np.r_[\n                        self.cell_centers_y[-1] - 2 * np.pi,\n                        self.cell_centers_y,\n                        self.cell_centers_y[0] + 2 * np.pi,\n                    ]\n                )\n            else:\n                rtz.append(self.cell_centers_y)\n            if self.dim == 3:\n                rtz.append(self.cell_centers_z)\n            Q = interpolation_matrix(loc, *rtz)\n            if self.is_wrapped:\n                irs, its, izs = np.unravel_index(\n                    Q.indices, self.shape_cells + np.r_[0, 2, 0], order=\"F\"\n                )\n                its = (its - 1) % self.shape_cells[1]\n                new_indices = np.ravel_multi_index(\n                    (irs, its, izs), self.shape_cells, order=\"F\"\n                )\n                Q = sp.csr_matrix(\n                    (Q.data, new_indices, Q.indptr), shape=(Q.shape[0], self.n_cells)\n                )\n        elif location_type in [\n            \"edges_x\",\n            \"edges_y\",\n            \"edges_z\",\n            \"faces_x\",\n            \"faces_y\",\n            \"faces_z\",\n        ]:\n            ind = {\"x\": 0, \"y\": 1, \"z\": 2}[location_type[-1]]\n            if self.dim < ind:\n                raise ValueError(\"mesh is not high enough dimension.\")\n            if \"f\" in location_type.lower():\n                items = (self.nFx, self.nFy, self.nFz)[: self.dim]\n            else:\n                items = (self.nEx, self.nEy, self.nEz)[: self.dim]\n            components = [spzeros(loc.shape[0], n) for n in items]\n            if location_type == \"faces_x\":\n                if self.includes_zero and not self.is_symmetric:\n                    nodes_x = self.nodes_x[1:]\n                else:\n                    nodes_x = self.nodes_x\n                rtz = [\n                    nodes_x,\n                ]\n                # theta wrap around interpolation\n                if self.is_wrapped:\n                    rtz.append(\n                        np.r_[\n                            self.cell_centers_y[-1] - 2 * np.pi,\n                            self.cell_centers_y,\n                            self.cell_centers_y[0] + 2 * np.pi,\n                        ],\n                    )\n                else:\n                    rtz.append(self.cell_centers_y)\n                if self.dim == 3:\n                    rtz.append(self.cell_centers_z)\n                Q = interpolation_matrix(loc, *rtz)\n                # unwrap the theta indices\n                if self.is_wrapped:\n                    irs, its, izs = np.unravel_index(\n                        Q.indices, self.shape_faces_x + np.r_[0, 2, 0], order=\"F\"\n                    )\n                    its = (its - 1) % self.shape_faces_x[1]\n                    new_indices = np.ravel_multi_index(\n                        (irs, its, izs), self.shape_faces_x, order=\"F\"\n                    )\n                    Q = sp.csr_matrix(\n                        (Q.data, new_indices, Q.indptr),\n                        shape=(Q.shape[0], self.n_faces_x),\n                    )\n                components[0] = Q\n            elif location_type == \"faces_y\":\n                rtz = [\n                    self.cell_centers_x,\n                    self._nodes_y_full,\n                ]\n                if self.dim == 3:\n                    rtz.append(self.cell_centers_z)\n                Q = interpolation_matrix(loc, *rtz)\n                Q = Q @ self._deflation_matrix(\"faces_y\", as_ones=True).T\n                components[1] = Q\n            elif location_type == \"faces_z\":\n                rtz = [\n                    self.cell_centers_x,\n                ]\n                # theta wrap around interpolation\n                if self.is_wrapped:\n                    rtz.append(\n                        np.r_[\n                            self.cell_centers_y[-1] - 2 * np.pi,\n                            self.cell_centers_y,\n                            self.cell_centers_y[0] + 2 * np.pi,\n                        ],\n                    )\n                else:\n                    rtz.append(self.cell_centers_y)\n                rtz.append(self.nodes_z)\n                Q = interpolation_matrix(loc, *rtz)\n                if self.is_wrapped:\n                    # unwrap the theta indices\n                    irs, its, izs = np.unravel_index(\n                        Q.indices, self.shape_faces_z + np.r_[0, 2, 0], order=\"F\"\n                    )\n                    its = (its - 1) % self.shape_faces_z[1]\n                    new_indices = np.ravel_multi_index(\n                        (irs, its, izs), self.shape_faces_z, order=\"F\"\n                    )\n                    Q = sp.csr_matrix(\n                        (Q.data, new_indices, Q.indptr),\n                        shape=(Q.shape[0], self.n_faces_z),\n                    )\n                components[2] = Q\n            elif location_type == \"edges_x\":\n                rtz = [\n                    self.cell_centers_x,\n                    self._nodes_y_full,\n                ]\n                if self.dim == 3:\n                    rtz.append(self.nodes_z)\n                Q = interpolation_matrix(loc, *rtz)\n                Q = Q @ self._deflation_matrix(\"edges_x\", as_ones=True).T\n                components[0] = Q\n            elif location_type == \"edges_y\":\n                # theta wrap around\n                if self.is_symmetric or not self.includes_zero:\n                    nodes_x = self.nodes_x\n                else:\n                    nodes_x = self.nodes_x[1:]\n                rtz = [nodes_x]\n                if self.is_wrapped:\n                    rtz.append(\n                        np.r_[\n                            self.cell_centers_y[-1] - 2 * np.pi,\n                            self.cell_centers_y,\n                            self.cell_centers_y[0] + 2 * np.pi,\n                        ],\n                    )\n                else:\n                    rtz.append(self.cell_centers_y)\n                if self.dim == 3:\n                    rtz.append(self.nodes_z)\n                Q = interpolation_matrix(loc, *rtz)\n                if self.is_wrapped:\n                    irs, its, izs = np.unravel_index(\n                        Q.indices, self.shape_edges_y + np.r_[0, 2, 0], order=\"F\"\n                    )\n                    its = (its - 1) % self.shape_edges_y[1]\n                    new_indices = np.ravel_multi_index(\n                        (irs, its, izs), self.shape_edges_y, order=\"F\"\n                    )\n                    Q = sp.csr_matrix(\n                        (Q.data, new_indices, Q.indptr),\n                        shape=(Q.shape[0], self.n_edges_y),\n                    )\n                components[1] = Q\n            elif location_type == \"edges_z\":\n                rtz = [self.nodes_x, self._nodes_y_full, self.cell_centers_z]\n                Q = interpolation_matrix(loc, *rtz)\n                Q = Q @ self._deflation_matrix(\"edges_z\", as_ones=True).T\n                components[2] = Q\n            # remove any zero blocks (hstack complains)\n            Q = sp.hstack([comp for comp in components if comp.shape[1] > 0])\n        else:\n            raise ValueError(\"Unrecognized location type\")\n        if zeros_outside:\n            Q[~self.is_inside(loc), :] = 0\n        return Q\n\n    def cartesian_grid(self, location_type=\"cell_centers\", theta_shift=None, **kwargs):\n        \"\"\"Return the specified grid in cartesian coordinates.\n\n        Takes a grid location ('CC', 'N', 'Ex', 'Ey', 'Ez', 'Fx', 'Fy', 'Fz')\n        and returns that grid in cartesian coordinates\n\n        Parameters\n        ----------\n        location_type : {'CC', 'N', 'Ex', 'Ey', 'Ez', 'Fx', 'Fy', 'Fz'}\n            grid location\n        theta_shift : float, optional\n            shift for theta\n\n        Returns\n        -------\n        (n_items, dim) numpy.ndarray\n            cartesian coordinates for the cylindrical grid\n        \"\"\"\n        if \"locType\" in kwargs:\n            raise TypeError(\n                \"The locType keyword argument has been removed, please use location_type. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n        try:\n            grid = getattr(self, location_type).copy()\n        except AttributeError:\n            grid = getattr(self, f\"grid{location_type}\").copy()\n        if theta_shift is not None:\n            grid[:, 1] = grid[:, 1] - theta_shift\n        return cyl2cart(grid)  # TODO: account for cartesian origin\n\n    def get_interpolation_matrix_cartesian_mesh(\n        self, Mrect, location_type=\"cell_centers\", location_type_to=None, **kwargs\n    ):\n        \"\"\"Construct projection matrix from ``CylindricalMesh`` to other mesh.\n\n        This method is used to construct a sparse linear interpolation matrix from gridded\n        locations on the cylindrical mesh to gridded locations on a different mesh\n        type. That is, an interpolation from the centers, nodes, faces or edges of the\n        cylindrical mesh to the centers, nodes, faces or edges of another mesh.\n        This method is generally used to interpolate from cylindrical meshes to meshes\n        defined in Cartesian coordinates; e.g. :class:`~discretize.TensorMesh`,\n        :class:`~discretize.TreeMesh` or :class:`~discretize.CurvilinearMesh`.\n\n        Parameters\n        ----------\n        Mrect : discretize.base.BaseMesh\n            the mesh we are interpolating onto\n        location_type : {'CC', 'N', 'Ex', 'Ey', 'Ez', 'Fx', 'Fy', 'Fz'}\n            gridded locations of the cylindrical mesh.\n        location_type_to : {None, 'CC', 'N', 'Ex', 'Ey', 'Ez', 'Fx', 'Fy', 'Fz'}\n            gridded locations being interpolated to on the other mesh.\n            If *None*, this method will use the same type as *location_type*.\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            interpolation matrix from gridded locations on cylindrical mesh to gridded locations\n            on another mesh\n        \"\"\"\n        if \"locType\" in kwargs:\n            raise TypeError(\n                \"The locType keyword argument has been removed, please use location_type. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n        if \"locTypeTo\" in kwargs:\n            raise TypeError(\n                \"The locTypeTo keyword argument has been removed, please use location_type_to. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n\n        location_type = self._parse_location_type(location_type)\n\n        if not self.is_symmetric:\n            raise NotImplementedError(\n                \"Currently we have not taken into account other projections \"\n                \"for more complicated CylindricalMeshes\"\n            )\n\n        if location_type_to is None:\n            location_type_to = location_type\n        location_type_to = self._parse_location_type(location_type_to)\n\n        if location_type == \"faces\":\n            # do this three times for each component\n            X = self.get_interpolation_matrix_cartesian_mesh(\n                Mrect, location_type=\"faces_x\", location_type_to=location_type_to + \"_x\"\n            )\n            Y = self.get_interpolation_matrix_cartesian_mesh(\n                Mrect, location_type=\"faces_y\", location_type_to=location_type_to + \"_y\"\n            )\n            Z = self.get_interpolation_matrix_cartesian_mesh(\n                Mrect, location_type=\"faces_z\", location_type_to=location_type_to + \"_z\"\n            )\n            return sp.vstack((X, Y, Z))\n        if location_type == \"edges\":\n            X = self.get_interpolation_matrix_cartesian_mesh(\n                Mrect, location_type=\"edges_x\", location_type_to=location_type_to + \"_x\"\n            )\n            Y = self.get_interpolation_matrix_cartesian_mesh(\n                Mrect, location_type=\"edges_y\", location_type_to=location_type_to + \"_y\"\n            )\n            Z = spzeros(getattr(Mrect, \"n_\" + location_type_to + \"_z\"), self.n_edges)\n            return sp.vstack((X, Y, Z))\n\n        grid = getattr(Mrect, location_type_to)\n        # This is unit circle stuff, 0 to 2*pi, starting at x-axis, rotating\n        # counter clockwise in an x-y slice\n        theta = (\n            -np.arctan2(\n                grid[:, 0] - self.cartesian_origin[0],\n                grid[:, 1] - self.cartesian_origin[1],\n            )\n            + np.pi / 2\n        )\n        theta[theta < 0] += np.pi * 2.0\n        r = (\n            (grid[:, 0] - self.cartesian_origin[0]) ** 2\n            + (grid[:, 1] - self.cartesian_origin[1]) ** 2\n        ) ** 0.5\n\n        if location_type in [\"cell_centers\", \"nodes\", \"faces_z\", \"edges_z\"]:\n            G, proj = np.c_[r, theta, grid[:, 2]], np.ones(r.size)\n        else:\n            dotMe = {\n                \"faces_x\": Mrect.face_normals[: Mrect.nFx, :],\n                \"faces_y\": Mrect.face_normals[Mrect.nFx : (Mrect.nFx + Mrect.nFy), :],\n                \"faces_z\": Mrect.face_normals[-Mrect.nFz :, :],\n                \"edges_x\": Mrect.edge_tangents[: Mrect.nEx, :],\n                \"edges_y\": Mrect.edge_tangents[Mrect.nEx : (Mrect.nEx + Mrect.nEy), :],\n                \"edges_z\": Mrect.edge_tangents[-Mrect.nEz :, :],\n            }[location_type_to]\n            if \"faces\" in location_type:\n                normals = np.c_[np.cos(theta), np.sin(theta), np.zeros(theta.size)]\n                proj = (normals * dotMe).sum(axis=1)\n            elif \"edges\" in location_type:\n                tangents = np.c_[-np.sin(theta), np.cos(theta), np.zeros(theta.size)]\n                proj = (tangents * dotMe).sum(axis=1)\n            G = np.c_[r, theta, grid[:, 2]]\n\n        interp_type = location_type\n        if interp_type == \"faces_y\":\n            interp_type = \"faces_x\"\n        elif interp_type == \"edges_x\":\n            interp_type = \"edges_y\"\n\n        Pc2r = self.get_interpolation_matrix(G, interp_type)\n        Proj = sdiag(proj)\n        return Proj * Pc2r\n\n    # DEPRECATIONS\n    areaFx = deprecate_property(\n        \"face_x_areas\", \"areaFx\", removal_version=\"1.0.0\", error=True\n    )\n    areaFy = deprecate_property(\n        \"face_y_areas\", \"areaFy\", removal_version=\"1.0.0\", error=True\n    )\n    areaFz = deprecate_property(\n        \"face_z_areas\", \"areaFz\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEx = deprecate_property(\n        \"edge_x_lengths\", \"edgeEx\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEy = deprecate_property(\n        \"edge_y_lengths\", \"edgeEy\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEz = deprecate_property(\n        \"edge_z_lengths\", \"edgeEz\", removal_version=\"1.0.0\", error=True\n    )\n    isSymmetric = deprecate_property(\n        \"is_symmetric\", \"isSymmetric\", removal_version=\"1.0.0\", error=True\n    )\n    cartesianOrigin = deprecate_property(\n        \"cartesian_origin\", \"cartesianOrigin\", removal_version=\"1.0.0\", error=True\n    )\n    getInterpolationMatCartMesh = deprecate_method(\n        \"get_interpolation_matrix_cartesian_mesh\",\n        \"getInterpolationMatCartMesh\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    cartesianGrid = deprecate_method(\n        \"cartesian_grid\", \"cartesianGrid\", removal_version=\"1.0.0\", error=True\n    )\n\n\n@deprecate_class(removal_version=\"1.0.0\", error=True)\nclass CylMesh(CylindricalMesh):\n    \"\"\"Deprecated calling of `discretize.CylindricalMesh`.\"\"\"\n\n    pass\n"
  },
  {
    "path": "discretize/meson.build",
    "content": "\npython_sources = [\n  '__init__.py',\n  'curvilinear_mesh.py',\n  'cylindrical_mesh.py',\n  'tensor_cell.py',\n  'tensor_mesh.py',\n  'tests.py',\n  'tree_mesh.py',\n  'unstructured_mesh.py',\n  'View.py',\n]\n\npy.install_sources(\n  python_sources,\n  subdir: 'discretize'\n)\n\nsubdir('base')\nsubdir('_extensions')\nsubdir('mixins')\nsubdir('operators')\nsubdir('utils')\nsubdir('Tests')\n"
  },
  {
    "path": "discretize/mixins/__init__.py",
    "content": "\"\"\"\n==================================\nMixins (:mod:`discretize.mixins`)\n==================================\n.. currentmodule:: discretize.mixins\n\nThe ``mixins`` module provides a set of tools for interfacing ``discretize``\nwith external libraries such as VTK, OMF, and matplotlib. These modules are only\nimported if those external packages are available in the active Python environment and\nprovide extra functionality that different finite volume meshes can inherit.\n\nMixin Classes\n-------------\n.. autosummary::\n  :toctree: generated/\n\n  TensorMeshIO\n  TreeMeshIO\n  InterfaceMPL\n  InterfaceVTK\n  InterfaceOMF\n\nOther Optional Classes\n----------------------\n.. autosummary::\n  :toctree: generated/\n\n  Slicer\n\"\"\"\n\nimport importlib.util\nfrom .mesh_io import TensorMeshIO, TreeMeshIO, SimplexMeshIO\n\nAVAILABLE_MIXIN_CLASSES = []\nSIMPLEX_MIXIN_CLASSES = []\n\nif importlib.util.find_spec(\"vtk\"):\n    from .vtk_mod import InterfaceVTK\n\n    AVAILABLE_MIXIN_CLASSES.append(InterfaceVTK)\n\nif importlib.util.find_spec(\"omf\"):\n    from .omf_mod import InterfaceOMF\n\n    AVAILABLE_MIXIN_CLASSES.append(InterfaceOMF)\n\n# keep this one last in defaults in case anything else wants to overwrite\n# plot commands\nif importlib.util.find_spec(\"matplotlib\"):\n    from .mpl_mod import Slicer, InterfaceMPL\n\n    AVAILABLE_MIXIN_CLASSES.append(InterfaceMPL)\n\n\n# # Python 3 friendly\nclass InterfaceMixins(*AVAILABLE_MIXIN_CLASSES):\n    \"\"\"Class to handle all the avaialble mixins that can be inherrited\n    directly onto ``discretize.base.BaseMesh``\n    \"\"\"\n\n    pass\n"
  },
  {
    "path": "discretize/mixins/mesh_io.py",
    "content": "\"\"\"Module for reading and writing meshes to text files.\n\nThe text files representing meshes are often in the `UBC` format.\n\"\"\"\n\nimport os\nimport numpy as np\n\nfrom discretize.utils import mkvc\nfrom discretize.utils.code_utils import deprecate_method\n\ntry:\n    from discretize.mixins.vtk_mod import (\n        InterfaceTensorread_vtk,\n        InterfaceSimplexReadVTK,\n    )\nexcept ImportError:\n    InterfaceSimplexReadVTK = InterfaceTensorread_vtk = object\n\n\nclass TensorMeshIO(InterfaceTensorread_vtk):\n    \"\"\"Class for managing the input/output of tensor meshes and models.\n\n    The ``TensorMeshIO`` class contains a set of class methods specifically\n    for the :class:`~discretize.TensorMesh` class. These include:\n\n        - Read/write tensor meshes to file\n        - Read/write models defined on tensor meshes\n\n    \"\"\"\n\n    @classmethod\n    def _readUBC_3DMesh(cls, file_name):\n        \"\"\"Read 3D tensor mesh from UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path to the UBC-GIF formatted mesh file\n\n        Returns\n        -------\n        discretize.TensorMesh\n            The tensor mesh\n        \"\"\"\n        # Read the file as line strings, remove lines with comment = !\n        msh = np.genfromtxt(file_name, delimiter=\"\\n\", dtype=str, comments=\"!\")\n\n        # Interal function to read cell size lines for the UBC mesh files.\n        def readCellLine(line):\n            line_list = []\n            for seg in line.split():\n                if \"*\" in seg:\n                    sp = seg.split(\"*\")\n                    seg_arr = np.ones((int(sp[0]),)) * float(sp[1])\n                else:\n                    seg_arr = np.array([float(seg)], float)\n                line_list.append(seg_arr)\n            return np.concatenate(line_list)\n\n        # Fist line is the size of the model\n        # sizeM = np.array(msh[0].split(), dtype=float)\n        # Second line is the South-West-Top corner coordinates.\n        origin = np.array(msh[1].split(), dtype=float)\n        # Read the cell sizes\n        h1 = readCellLine(msh[2])\n        h2 = readCellLine(msh[3])\n        h3temp = readCellLine(msh[4])\n        # Invert the indexing of the vector to start from the bottom.\n        h3 = h3temp[::-1]\n        # Adjust the reference point to the bottom south west corner\n        origin[2] = origin[2] - np.sum(h3)\n        # Make the mesh\n        tensMsh = cls([h1, h2, h3], origin=origin)\n        return tensMsh\n\n    @classmethod\n    def _readUBC_2DMesh(cls, file_name):\n        \"\"\"Read 2D tensor mesh from UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path to the UBC-GIF formatted mesh file\n\n        Returns\n        -------\n        discretize.TensorMesh\n            The tensor mesh\n        \"\"\"\n        fopen = open(file_name, \"r\")\n\n        # Read down the file and unpack dx vector\n        def unpackdx(fid, nrows):\n            for ii in range(nrows):\n                line = fid.readline()\n                var = np.array(line.split(), dtype=float)\n                if ii == 0:\n                    x0 = var[0]\n                    xvec = np.ones(int(var[2])) * (var[1] - var[0]) / int(var[2])\n                    xend = var[1]\n                else:\n                    xvec = np.hstack(\n                        (xvec, np.ones(int(var[1])) * (var[0] - xend) / int(var[1]))\n                    )\n                    xend = var[0]\n            return x0, xvec\n\n        # Start with dx block\n        # First line specifies the number of rows for x-cells\n        line = fopen.readline()\n        # Strip comments lines\n        while line.startswith(\"!\"):\n            line = fopen.readline()\n        nl = np.array(line.split(), dtype=int)\n        [x0, dx] = unpackdx(fopen, nl[0])\n        # Move down the file until reaching the z-block\n        line = fopen.readline()\n        if not line:\n            line = fopen.readline()\n        # End with dz block\n        # First line specifies the number of rows for z-cells\n        line = fopen.readline()\n        nl = np.array(line.split(), dtype=int)\n        [z0, dz] = unpackdx(fopen, nl[0])\n        # Flip z0 to be the bottom of the mesh for SimPEG\n        z0 = -(z0 + sum(dz))\n        dz = dz[::-1]\n        # Make the mesh\n        tensMsh = cls([dx, dz], origin=(x0, z0))\n\n        fopen.close()\n\n        return tensMsh\n\n    @classmethod\n    def read_UBC(cls, file_name, directory=None):\n        \"\"\"Read 2D or 3D tensor mesh from UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path to the UBC-GIF formatted mesh file or just its name if directory is specified\n        directory : str, optional\n            directory where the UBC-GIF file lives\n\n        Returns\n        -------\n        discretize.TensorMesh\n            The tensor mesh\n        \"\"\"\n        # Check the expected mesh dimensions\n        if directory is None:\n            directory = \"\"\n        fname = os.path.join(directory, file_name)\n        # Read the file as line strings, remove lines with comment = !\n        msh = np.genfromtxt(fname, delimiter=\"\\n\", dtype=str, comments=\"!\", max_rows=1)\n        # Fist line is the size of the model\n        sizeM = np.array(msh.ravel()[0].split(), dtype=float)\n        # Check if the mesh is a UBC 2D mesh\n        if sizeM.shape[0] == 1:\n            Tnsmsh = cls._readUBC_2DMesh(fname)\n        # Check if the mesh is a UBC 3D mesh\n        elif sizeM.shape[0] == 3:\n            Tnsmsh = cls._readUBC_3DMesh(fname)\n        else:\n            raise Exception(\"File format not recognized\")\n        return Tnsmsh\n\n    def _readModelUBC_2D(mesh, file_name):\n        \"\"\"Read UBC-GIF formatted model file for 2D tensor mesh.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path to the UBC-GIF formatted model file\n\n        Returns\n        -------\n        (n_cells) numpy.ndarray\n            The model defined on the 2D tensor mesh\n        \"\"\"\n        # Open file and skip header... assume that we know the mesh already\n        obsfile = np.genfromtxt(file_name, delimiter=\" \\n\", dtype=str, comments=\"!\")\n\n        dim = tuple(np.array(obsfile[0].split(), dtype=int))\n        if mesh.shape_cells != dim:\n            raise Exception(\"Dimension of the model and mesh mismatch\")\n\n        model = []\n        for line in obsfile[1:]:\n            model.extend([float(val) for val in line.split()])\n        model = np.asarray(model)\n        if not len(model) == mesh.nC:\n            raise Exception(\n                \"\"\"Something is not right, expected size is {:d}\n                but unwrap vector is size {:d}\"\"\".format(\n                    mesh.nC, len(model)\n                )\n            )\n\n        return model.reshape(mesh.vnC, order=\"F\")[:, ::-1].reshape(-1, order=\"F\")\n\n    def _readModelUBC_3D(mesh, file_name):\n        \"\"\"Read UBC-GIF formatted model file for 3D tensor mesh.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path to the UBC-GIF formatted model file\n\n        Returns\n        -------\n        (n_cells) numpy.ndarray\n            The model defined on the 3D tensor mesh\n        \"\"\"\n        f = open(file_name, \"r\")\n        model = np.array(list(map(float, f.readlines())))\n        f.close()\n        nCx, nCy, nCz = mesh.shape_cells\n        model = np.reshape(model, (nCz, nCx, nCy), order=\"F\")\n        model = model[::-1, :, :]\n        model = np.transpose(model, (1, 2, 0))\n        model = mkvc(model)\n        return model\n\n    def read_model_UBC(mesh, file_name, directory=None):\n        \"\"\"Read UBC-GIF formatted model file for 2D or 3D tensor mesh.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path to the UBC-GIF formatted model file or just its name if directory is specified\n        directory : str, optional\n            directory where the UBC-GIF file lives\n\n        Returns\n        -------\n        (n_cells) numpy.ndarray\n            The model defined on the mesh\n        \"\"\"\n        if directory is None:\n            directory = \"\"\n        fname = os.path.join(directory, file_name)\n        if mesh.dim == 3:\n            model = mesh._readModelUBC_3D(fname)\n        elif mesh.dim == 2:\n            model = mesh._readModelUBC_2D(fname)\n        else:\n            raise Exception(\"mesh must be a Tensor Mesh 2D or 3D\")\n        return model\n\n    def write_model_UBC(mesh, file_name, model, directory=None):\n        \"\"\"Write 2D or 3D tensor model to UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path for the output mesh file or just its name if directory is specified\n        model : (n_cells) numpy.ndarray\n            The model to write out.\n        directory : str, optional\n            output directory\n        \"\"\"\n        if directory is None:\n            directory = \"\"\n        fname = os.path.join(directory, file_name)\n        if mesh.dim == 3:\n            # Reshape model to a matrix\n            modelMat = mesh.reshape(model, \"CC\", \"CC\", \"M\")\n            # Transpose the axes\n            modelMatT = modelMat.transpose((2, 0, 1))\n            # Flip z to positive down\n            modelMatTR = mkvc(modelMatT[::-1, :, :])\n            np.savetxt(fname, modelMatTR.ravel())\n\n        elif mesh.dim == 2:\n            modelMat = mesh.reshape(model, \"CC\", \"CC\", \"M\").T[::-1]\n            f = open(fname, \"w\")\n            f.write(\"{:d} {:d}\\n\".format(*mesh.shape_cells))\n            f.close()\n            f = open(fname, \"ab\")\n            np.savetxt(f, modelMat)\n            f.close()\n\n        else:\n            raise Exception(\"mesh must be a Tensor Mesh 2D or 3D\")\n\n    def _writeUBC_3DMesh(mesh, file_name, comment_lines=\"\"):\n        \"\"\"Write 3D tensor mesh to UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path for the output mesh file\n        comment_lines : str, optional\n            comment lines preceded are preceeded with '!'\n        \"\"\"\n        if not mesh.dim == 3:\n            raise Exception(\"Mesh must be 3D\")\n\n        s = comment_lines\n        s += \"{0:d} {1:d} {2:d}\\n\".format(*tuple(mesh.vnC))\n        # Have to it in the same operation or use mesh.origin.copy(),\n        # otherwise the mesh.origin is updated.\n        origin = mesh.origin + np.array([0, 0, mesh.h[2].sum()])\n\n        nCx, nCy, nCz = mesh.shape_cells\n        s += \"{0:.6f} {1:.6f} {2:.6f}\\n\".format(*tuple(origin))\n        s += (\"%.6f \" * nCx + \"\\n\") % tuple(mesh.h[0])\n        s += (\"%.6f \" * nCy + \"\\n\") % tuple(mesh.h[1])\n        s += (\"%.6f \" * nCz + \"\\n\") % tuple(mesh.h[2][::-1])\n        f = open(file_name, \"w\")\n        f.write(s)\n        f.close()\n\n    def _writeUBC_2DMesh(mesh, file_name, comment_lines=\"\"):\n        \"\"\"Write 2D tensor mesh to UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path for the output mesh file\n        comment_lines : str, optional\n            comment lines preceded are preceeded with '!'\n        \"\"\"\n        if not mesh.dim == 2:\n            raise Exception(\"Mesh must be 2D\")\n\n        def writeF(fx, outStr=\"\"):\n            # Init\n            i = 0\n            origin = True\n            x0 = fx[i]\n            f = fx[i]\n            number_segment = 0\n            auxStr = \"\"\n\n            while True:\n                i = i + 1\n                if i >= fx.size:\n                    break\n                dx = -f + fx[i]\n                f = fx[i]\n                n = 1\n\n                for j in range(i + 1, fx.size):\n                    if -f + fx[j] == dx:\n                        n += 1\n                        i += 1\n                        f = fx[j]\n                    else:\n                        break\n\n                number_segment += 1\n                if origin:\n                    auxStr += \"{:.10f} {:.10f} {:d} \\n\".format(x0, f, n)\n                    origin = False\n                else:\n                    auxStr += \"{:.10f} {:d} \\n\".format(f, n)\n\n            auxStr = \"{:d}\\n\".format(number_segment) + auxStr\n            outStr += auxStr\n\n            return outStr\n\n        # Grab face coordinates\n        fx = mesh.nodes_x\n        fz = -mesh.nodes_y[::-1]\n\n        # Create the string\n        outStr = comment_lines\n        outStr = writeF(fx, outStr=outStr)\n        outStr += \"\\n\"\n        outStr = writeF(fz, outStr=outStr)\n\n        # Write file\n        f = open(file_name, \"w\")\n        f.write(outStr)\n        f.close()\n\n    def write_UBC(mesh, file_name, models=None, directory=None, comment_lines=\"\"):\n        \"\"\"Write 2D or 3D tensor mesh (and models) to UBC-GIF formatted file(s).\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path for the output mesh file or just its name if directory is specified\n        models : dict of [str, (n_cells) numpy.ndarray], optional\n            The dictionary key is a string representing the model's name. Each model\n            is an (n_cells) array.\n        directory : str, optional\n            output directory\n        comment_lines : str, optional\n            comment lines preceded are preceeded with '!'\n        \"\"\"\n        if directory is None:\n            directory = \"\"\n        fname = os.path.join(directory, file_name)\n        if mesh.dim == 3:\n            mesh._writeUBC_3DMesh(fname, comment_lines=comment_lines)\n        elif mesh.dim == 2:\n            mesh._writeUBC_2DMesh(fname, comment_lines=comment_lines)\n        else:\n            raise Exception(\"mesh must be a Tensor Mesh 2D or 3D\")\n\n        if models is None:\n            return\n        if not isinstance(models, dict):\n            raise TypeError(\"models must be a dict\")\n        for key in models:\n            if not isinstance(key, str):\n                raise TypeError(\n                    \"The dict key must be a string representing the file name\"\n                )\n            mesh.write_model_UBC(key, models[key], directory=directory)\n\n    # DEPRECATED\n    @classmethod\n    def readUBC(TensorMesh, file_name, directory=\"\"):\n        \"\"\"Read 2D or 3D tensor mesh from UBC-GIF formatted file.\n\n        *readUBC* has been deprecated and replaced by *read_UBC*\n        See Also\n        --------\n        read_UBC\n        \"\"\"\n        raise NotImplementedError(\n            \"TensorMesh.readUBC has been removed and this be removed in\"\n            \"discretize 1.0.0. please use TensorMesh.read_UBC\",\n        )\n\n    readModelUBC = deprecate_method(\n        \"read_model_UBC\", \"readModelUBC\", removal_version=\"1.0.0\", error=True\n    )\n    writeUBC = deprecate_method(\n        \"write_UBC\", \"writeUBC\", removal_version=\"1.0.0\", error=True\n    )\n    writeModelUBC = deprecate_method(\n        \"write_model_UBC\", \"writeModelUBC\", removal_version=\"1.0.0\", error=True\n    )\n\n\nclass TreeMeshIO(object):\n    \"\"\"Class for managing the input/output of tree meshes and models.\n\n    The ``TreeMeshIO`` class contains a set of class methods specifically\n    for the :class:`~discretize.TreeMesh` class. These include:\n\n        - Read/write tree meshes to file\n        - Read/write models defined on tree meshes\n\n    \"\"\"\n\n    @classmethod\n    def read_UBC(TreeMesh, file_name, directory=None):\n        \"\"\"Read 3D tree mesh (OcTree mesh) from UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            full path to the UBC-GIF formatted mesh file or just its name if directory is specified\n        directory : str, optional\n            directory where the UBC-GIF file lives\n\n        Returns\n        -------\n        discretize.TreeMesh\n            The tree mesh\n        \"\"\"\n        if directory is None:\n            directory = \"\"\n        fname = os.path.join(directory, file_name)\n        fileLines = np.genfromtxt(fname, dtype=str, delimiter=\"\\n\", comments=\"!\")\n        nCunderMesh = np.array(fileLines[0].split(\"!\")[0].split(), dtype=int)\n        tswCorn = np.array(fileLines[1].split(\"!\")[0].split(), dtype=float)\n        smallCell = np.array(fileLines[2].split(\"!\")[0].split(), dtype=float)\n        # Read the index array\n        indArr = np.genfromtxt(\n            (line.encode(\"utf8\") for line in fileLines[4::]), dtype=np.int64\n        )\n        nCunderMesh = nCunderMesh[: len(tswCorn)]  # remove information related to core\n\n        hs = [np.ones(nr) * sz for nr, sz in zip(nCunderMesh, smallCell)]\n        origin = tswCorn\n        origin[-1] -= np.sum(hs[-1])\n\n        ls = np.log2(nCunderMesh).astype(int)\n        # if all ls are equal\n        if min(ls) == max(ls):\n            max_level = ls[0]\n        else:\n            max_level = min(ls) + 1\n\n        mesh = TreeMesh(hs, origin=origin, diagonal_balance=False)\n        levels = indArr[:, -1]\n        indArr = indArr[:, :-1]\n\n        indArr -= 1  # shift by 1....\n        indArr = 2 * indArr + levels[:, None]  # get cell center index\n        indArr[:, -1] = 2 * nCunderMesh[-1] - indArr[:, -1]  # switch direction of iz\n        levels = max_level - np.log2(levels)  # calculate level\n\n        mesh.__setstate__((indArr, levels))\n        return mesh\n\n    def read_model_UBC(mesh, file_name, directory=None):\n        \"\"\"Read UBC-GIF formatted file model file for 3D tree mesh (OcTree).\n\n        Parameters\n        ----------\n        file_name : str or list of str\n            full path to the UBC-GIF formatted model file or\n            just its name if directory is specified. It can also be a list of file_names.\n        directory : str\n            directory where the UBC-GIF file(s) lives (optional)\n\n        Returns\n        -------\n        (n_cells) numpy.ndarray or dict of [str, (n_cells) numpy.ndarray]\n            The model defined on the mesh. If **file_name** is a ``dict``, it is a\n            dictionary of models indexed by the file names.\n        \"\"\"\n        if directory is None:\n            directory = \"\"\n        if type(file_name) is list:\n            out = {}\n            for f in file_name:\n                out[f] = mesh.read_model_UBC(f, directory=directory)\n            return out\n\n        modArr = np.loadtxt(file_name)\n\n        ubc_order = mesh._ubc_order\n        # order_ubc will re-order from treemesh ordering to UBC ordering\n        # need the opposite operation\n        un_order = np.empty_like(ubc_order)\n        un_order[ubc_order] = np.arange(len(ubc_order))\n\n        model = modArr[un_order].copy()  # ensure a contiguous array\n        return model\n\n    def write_UBC(mesh, file_name, models=None, directory=None):\n        \"\"\"Write OcTree mesh (and models) to UBC-GIF formatted files.\n\n        Parameters\n        ----------\n        file_name : str\n            full path for the output mesh file or just its name if directory is specified\n        models : dict of [str, (n_cells) numpy.ndarray], optional\n            The dictionary key is a string representing the model's name.\n            Each model is a 1D numpy array of size (n_cells).\n        directory : str, optional\n            output directory (optional)\n        \"\"\"\n        if directory is None:\n            directory = \"\"\n        uniform_hs = np.array([np.allclose(h, h[0]) for h in mesh.h])\n        if np.any(~uniform_hs):\n            raise Exception(\"UBC form does not support variable cell widths\")\n        nCunderMesh = np.array([h.size for h in mesh.h], dtype=np.int64)\n\n        tswCorn = mesh.origin.copy()\n        tswCorn[-1] += np.sum(mesh.h[-1])\n\n        smallCell = np.array([h[0] for h in mesh.h])\n        nrCells = mesh.nC\n\n        indArr, levels = mesh._ubc_indArr\n        ubc_order = mesh._ubc_order\n\n        indArr = indArr[ubc_order]\n        levels = levels[ubc_order]\n\n        # Write the UBC octree mesh file\n        head = \" \".join([f\"{int(n)}\" for n in nCunderMesh]) + \" \\n\"\n        head += \" \".join([f\"{v:.4f}\" for v in tswCorn]) + \" \\n\"\n        head += \" \".join([f\"{v:.3f}\" for v in smallCell]) + \" \\n\"\n        head += f\"{int(nrCells)}\"\n        np.savetxt(file_name, np.c_[indArr, levels], fmt=\"%i\", header=head, comments=\"\")\n\n        # Print the models\n        if models is None:\n            return\n        if not isinstance(models, dict):\n            raise TypeError(\"models must be a dict\")\n        for key in models:\n            mesh.write_model_UBC(key, models[key], directory=directory)\n\n    def write_model_UBC(mesh, file_name, model, directory=None):\n        \"\"\"Write 3D tree model (OcTree) to UBC-GIF formatted file.\n\n        Parameters\n        ----------\n        file_name : str\n            full path for the output mesh file or just its name if directory is specified\n        model : (n_cells) numpy.ndarray\n            model values defined for each cell\n        directory : str\n            output directory (optional)\n        \"\"\"\n        if directory is None:\n            directory = \"\"\n        if type(file_name) is list:\n            for f, m in zip(file_name, model):\n                mesh.write_model_UBC(f, m)\n        else:\n            ubc_order = mesh._ubc_order\n            fname = os.path.join(directory, file_name)\n            m = model[ubc_order]\n            np.savetxt(fname, m)\n\n    # DEPRECATED\n    @classmethod\n    def readUBC(TreeMesh, file_name, directory=\"\"):\n        \"\"\"Read 3D Tree mesh from UBC-GIF formatted file.\n\n        *readUBC* has been deprecated and replaced by *read_UBC*\n\n        See Also\n        --------\n        read_UBC\n        \"\"\"\n        raise NotImplementedError(\n            \"TensorMesh.readUBC has been removed and this be removed in\"\n            \"discretize 1.0.0. please use TensorMesh.read_UBC\",\n        )\n\n    readModelUBC = deprecate_method(\n        \"read_model_UBC\", \"readModelUBC\", removal_version=\"1.0.0\", error=True\n    )\n    writeUBC = deprecate_method(\n        \"write_UBC\", \"writeUBC\", removal_version=\"1.0.0\", error=True\n    )\n    writeModelUBC = deprecate_method(\n        \"write_model_UBC\", \"writeModelUBC\", removal_version=\"1.0.0\", error=True\n    )\n\n\nclass SimplexMeshIO(InterfaceSimplexReadVTK):\n    \"\"\"Empty class for future text based IO of a SimplexMesh.\"\"\"\n\n    pass\n"
  },
  {
    "path": "discretize/mixins/meson.build",
    "content": "\npython_sources = [\n  '__init__.py',\n  'mesh_io.py',\n  'mpl_mod.py',\n  'omf_mod.py',\n  'vtk_mod.py',\n]\n\npy.install_sources(\n  python_sources,\n  subdir: 'discretize/mixins'\n)\n"
  },
  {
    "path": "discretize/mixins/mpl_mod.py",
    "content": "\"\"\"Module for ``matplotlib`` interaction with ``discretize``.\"\"\"\n\nimport numpy as np\nimport warnings\nfrom discretize.utils import mkvc, ndgrid\nfrom discretize.utils.code_utils import deprecate_method\n\nimport discretize\n\n\ndef load_matplotlib():\n    \"\"\"Lazy load principal matplotlib routines.\n\n    This is not beautiful. But if matplotlib is installed, but never used, it\n    reduces load time significantly.\n    \"\"\"\n    import matplotlib\n    import matplotlib.pyplot as plt\n\n    return matplotlib, plt\n\n\nclass InterfaceMPL(object):\n    \"\"\"Class for plotting ``discretize`` meshes with matplotlib.\n\n    This interface adds three plotting methods to all ``discretize`` meshes.\n    :py:attr:`~InterfaceMPL.plot_grid` will plot gridded points for 2D and 3D meshes.\n    :py:attr:`~InterfaceMPL.plot_image` is used for plotting models, scalars and vectors\n    defined on a given mesh. And :py:attr:`~InterfaceMPL.plot_slice` is used for plotting\n    models, scalars and vectors on a 2D slice through a 3D mesh.\n    \"\"\"\n\n    def plot_grid(\n        self,\n        ax=None,\n        nodes=False,\n        faces=False,\n        centers=False,\n        edges=False,\n        lines=True,\n        show_it=False,\n        **kwargs,\n    ):\n        \"\"\"Plot the grid for nodal, cell-centered and staggered grids.\n\n        For 2D and 3D meshes, this method plots the mesh grid. Additionally,\n        the user can choose to denote edge, face, node and cell center locations.\n        This function is built upon the ``matplotlib.pyplot.plot`` function\n        and will accept associated keyword arguments.\n\n        Parameters\n        ----------\n        ax : matplotlib.axes.Axes or None, optional\n            The axes to draw on. *None* produces a new axes.\n        nodes, faces, centers, edges, lines : bool, optional\n            Whether to plot the corresponding item\n        show_it : bool, optional\n            whether to call plt.show()\n        color : Color or str, optional\n            If lines=True, defines the color of the grid lines.\n        linewidth : float, optional\n            If lines=True, defines the thickness of the grid lines.\n\n        Returns\n        -------\n        matplotlib.axes.Axes\n            Axes handle for the plot\n\n        Other Parameters\n        ----------------\n        edges_x, edges_y, edges_z, faces_x, faces_y, faces_z : bool, optional\n            When plotting a ``TreeMesh``, these are also options to plot the\n            individual component items.\n        cell_line : bool, optional\n            When plotting a ``TreeMesh``, you can also plot a line through the\n            cell centers in order.\n        slice : {'both', 'theta', 'z'}\n            When plotting a ``CylindricalMesh``, which dimension to slice over.\n\n        Notes\n        -----\n        Excess arguments are passed on to `plot`\n\n        Examples\n        --------\n        Plotting a 2D TensorMesh grid\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> import numpy as np\n        >>> h1 = np.linspace(.1, .5, 3)\n        >>> h2 = np.linspace(.1, .5, 5)\n        >>> mesh = discretize.TensorMesh([h1, h2])\n        >>> mesh.plot_grid(nodes=True, faces=True, centers=True, lines=True)\n        >>> plt.show()\n\n        Plotting a 3D TensorMesh grid\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> import numpy as np\n        >>> h1 = np.linspace(.1, .5, 3)\n        >>> h2 = np.linspace(.1, .5, 5)\n        >>> h3 = np.linspace(.1, .5, 3)\n        >>> mesh = discretize.TensorMesh([h1, h2, h3])\n        >>> mesh.plot_grid(nodes=True, faces=True, centers=True, lines=True)\n        >>> plt.show()\n\n        Plotting a 2D CurvilinearMesh\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> X, Y = discretize.utils.example_curvilinear_grid([10, 10], 'rotate')\n        >>> M = discretize.CurvilinearMesh([X, Y])\n        >>> M.plot_grid()\n        >>> plt.show()\n\n        Plotting a 3D CurvilinearMesh\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> X, Y, Z = discretize.utils.example_curvilinear_grid([5, 5, 5], 'rotate')\n        >>> M = discretize.CurvilinearMesh([X, Y, Z])\n        >>> M.plot_grid()\n        >>> plt.show()\n\n        Plotting a 2D TreeMesh\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> M = discretize.TreeMesh([32, 32])\n        >>> M.insert_cells([[0.25, 0.25]], [4])\n        >>> M.plot_grid()\n        >>> plt.show()\n\n        Plotting a 3D TreeMesh\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> M = discretize.TreeMesh([32, 32, 32])\n        >>> M.insert_cells([[0.3, 0.75, 0.22]], [4])\n        >>> M.plot_grid()\n        >>> plt.show()\n        \"\"\"\n        matplotlib, plt = load_matplotlib()\n        from matplotlib import rc_params  # lazy loaded\n\n        mesh_type = self._meshType.lower()\n        plotters = {\n            \"tree\": self.__plot_grid_tree,\n            \"tensor\": self.__plot_grid_tensor,\n            \"curv\": self.__plot_grid_curv,\n            \"cyl\": self.__plot_grid_cyl,\n            \"simplex\": self.__plot_grid_simp,\n        }\n        try:\n            plotter = plotters[mesh_type]\n        except KeyError:\n            raise NotImplementedError(\n                \"Mesh type `{}` does not have a plot_grid implementation.\".format(\n                    type(self).__name__\n                )\n            )\n\n        if \"showIt\" in kwargs:\n            show_it = kwargs.pop(\"showIt\")\n            warnings.warn(\n                \"showIt has been deprecated, please use show_it\",\n                FutureWarning,\n                stacklevel=2,\n            )\n\n        if ax is not None:\n            ax_test = ax\n            if not isinstance(ax, (list, tuple, np.ndarray)):\n                ax_test = (ax,)\n            for a in ax_test:\n                if not isinstance(a, matplotlib.axes.Axes):\n                    raise TypeError(\"ax must be an matplotlib.axes.Axes\")\n        elif mesh_type != \"cyl\":\n            axOpts = {\"projection\": \"3d\"} if self.dim == 3 else {}\n            plt.figure()\n            ax = plt.subplot(111, **axOpts)\n\n        rcParams = rc_params()\n        if lines:\n            kwargs[\"color\"] = kwargs.get(\"color\", rcParams[\"lines.color\"])\n            kwargs[\"linewidth\"] = kwargs.get(\"linewidth\", rcParams[\"lines.linewidth\"])\n\n        out = plotter(\n            ax=ax,\n            nodes=nodes,\n            faces=faces,\n            centers=centers,\n            edges=edges,\n            lines=lines,\n            **kwargs,\n        )\n        if show_it:\n            plt.show()\n        return out\n\n    def plot_image(\n        self,\n        v,\n        v_type=\"CC\",\n        grid=False,\n        view=\"real\",\n        ax=None,\n        clim=None,\n        show_it=False,\n        pcolor_opts=None,\n        stream_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        sample_grid=None,\n        stream_thickness=None,\n        stream_threshold=None,\n        **kwargs,\n    ):\n        \"\"\"Plot quantities defined on a given mesh.\n\n        This method is primarily used to plot models, scalar quantities and vector\n        quantities defined on 2D meshes. For 3D :class:`discretize.TensorMesh` however,\n        this method will plot the quantity for every slice of the 3D mesh.\n\n        Parameters\n        ----------\n        v : numpy.ndarray\n            Gridded values being plotted. The length of the array depends on the quantity being\n            plotted; e.g. if the quantity is a scalar value defined on mesh nodes, the\n            length must be equal to the number of mesh nodes.\n        v_type : {'CC','CCV', 'N', 'F', 'Fx', 'Fy', 'Fz', 'E', 'Ex', 'Ey', 'Ez'}\n            Defines the input parameter *v*.\n        view : {'real', 'imag', 'abs', 'vec'}\n            For complex scalar quantities, options are included to image the real, imaginary or\n            absolute value. For vector quantities, *view* must be set to 'vec'.\n        ax : matplotlib.axes.Axes, optional\n            The axes to draw on. *None* produces a new Axes.\n        clim : tuple of float, optional\n            length 2 tuple of (vmin, vmax) for the color limits\n        range_x, range_y : tuple of float, optional\n            length 2 tuple of (min, max) for the bounds of the plot axes.\n        pcolor_opts : dict, optional\n            Arguments passed on to ``pcolormesh``\n        grid : bool, optional\n            Whether to plot the edges of the mesh cells.\n        grid_opts : dict, optional\n            If ``grid`` is true, arguments passed on to ``plot`` for grid\n        sample_grid : tuple of numpy.ndarray, optional\n            If ``view`` == 'vec', mesh cell widths (hx, hy) to interpolate onto for vector plotting\n        stream_opts : dict, optional\n            If ``view`` == 'vec', arguments passed on to ``streamplot``\n        stream_thickness : float, optional\n            If ``view`` == 'vec', linewidth for ``streamplot``\n        stream_threshold : float, optional\n            If ``view`` == 'vec', only plots vectors with magnitude above this threshold\n        show_it : bool, optional\n            Whether to call plt.show()\n        numbering : bool, optional\n            For 3D :class:`~discretize.TensorMesh` only, show the numbering of the slices\n        annotation_color : Color or str, optional\n            For 3D :class:`~discretize.TensorMesh` only, color of the annotation\n\n        Examples\n        --------\n        2D ``TensorMesh`` plotting\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> import numpy as np\n        >>> M = discretize.TensorMesh([20, 20])\n        >>> v = np.sin(M.gridCC[:, 0]*2*np.pi)*np.sin(M.gridCC[:, 1]*2*np.pi)\n        >>> M.plot_image(v)\n        >>> plt.show()\n\n        3D ``TensorMesh`` plotting\n\n        >>> import discretize\n        >>> import numpy as np\n        >>> M = discretize.TensorMesh([20, 20, 20])\n        >>> v = np.sin(M.gridCC[:, 0]*2*np.pi)*np.sin(M.gridCC[:, 1]*2*np.pi)*np.sin(M.gridCC[:, 2]*2*np.pi)\n        >>> M.plot_image(v, annotation_color='k')\n        >>> plt.show()\n        \"\"\"\n        matplotlib, plt = load_matplotlib()\n\n        mesh_type = self._meshType.lower()\n        plotters = {\n            \"tree\": self.__plot_image_tree,\n            \"tensor\": self.__plot_image_tensor,\n            \"curv\": self.__plot_image_curv,\n            \"cyl\": self.__plot_image_cyl,\n            \"simplex\": self.__plot_image_simp,\n        }\n        try:\n            plotter = plotters[mesh_type]\n        except KeyError:\n            raise NotImplementedError(\n                \"Mesh type `{}` does not have a plot_image implementation.\".format(\n                    type(self).__name__\n                )\n            )\n\n        if \"pcolorOpts\" in kwargs:\n            pcolor_opts = kwargs.pop(\"pcolorOpts\")\n            warnings.warn(\n                \"pcolorOpts has been deprecated, please use pcolor_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"streamOpts\" in kwargs:\n            stream_opts = kwargs.pop(\"streamOpts\")\n            warnings.warn(\n                \"streamOpts has been deprecated, please use stream_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"gridOpts\" in kwargs:\n            grid_opts = kwargs.pop(\"gridOpts\")\n            warnings.warn(\n                \"gridOpts has been deprecated, please use grid_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"showIt\" in kwargs:\n            show_it = kwargs.pop(\"showIt\")\n            warnings.warn(\n                \"showIt has been deprecated, please use show_it\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"vType\" in kwargs:\n            v_type = kwargs.pop(\"vType\")\n            warnings.warn(\n                \"vType has been deprecated, please use v_type\",\n                FutureWarning,\n                stacklevel=2,\n            )\n\n        # Some Error checking and common defaults\n        if pcolor_opts is None:\n            pcolor_opts = {}\n        if stream_opts is None:\n            stream_opts = {\"color\": \"k\"}\n        if grid_opts is None:\n            if grid:\n                grid_opts = {\"color\": \"k\"}\n            else:\n                grid_opts = {}\n        v_typeOptsCC = [\"N\", \"CC\", \"Fx\", \"Fy\", \"Ex\", \"Ey\"]\n        v_typeOptsV = [\"CCv\", \"F\", \"E\"]\n        v_typeOpts = v_typeOptsCC + v_typeOptsV\n        if view == \"vec\":\n            if v_type not in v_typeOptsV:\n                raise ValueError(\n                    \"v_type must be in ['{0!s}'] when view='vec'\".format(\n                        \"', '\".join(v_typeOptsV)\n                    )\n                )\n        if v_type not in v_typeOpts:\n            raise ValueError(\n                \"v_type must be in ['{0!s}']\".format(\"', '\".join(v_typeOpts))\n            )\n\n        viewOpts = [\"real\", \"imag\", \"abs\", \"vec\"]\n        if view not in viewOpts:\n            raise ValueError(\"view must be in ['{0!s}']\".format(\"', '\".join(viewOpts)))\n\n        if v.dtype == complex and view == \"vec\":\n            raise NotImplementedError(\"Can not plot a complex vector.\")\n\n        if ax is None:\n            plt.figure()\n            ax = plt.subplot(111)\n        else:\n            if not isinstance(ax, matplotlib.axes.Axes):\n                raise TypeError(\"ax must be an Axes!\")\n        if clim is not None:\n            pcolor_opts[\"vmin\"] = clim[0]\n            pcolor_opts[\"vmax\"] = clim[1]\n\n        out = plotter(\n            v,\n            v_type=v_type,\n            view=view,\n            ax=ax,\n            range_x=range_x,\n            range_y=range_y,\n            pcolor_opts=pcolor_opts,\n            grid=grid,\n            grid_opts=grid_opts,\n            sample_grid=sample_grid,\n            stream_opts=stream_opts,\n            stream_threshold=stream_threshold,\n            stream_thickness=stream_thickness,\n            **kwargs,\n        )\n        if show_it:\n            plt.show()\n        return out\n\n    def plot_slice(\n        self,\n        v,\n        v_type=\"CC\",\n        normal=\"Z\",\n        ind=None,\n        slice_loc=None,\n        grid=False,\n        view=\"real\",\n        ax=None,\n        clim=None,\n        show_it=False,\n        pcolor_opts=None,\n        stream_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        sample_grid=None,\n        stream_threshold=None,\n        stream_thickness=None,\n        **kwargs,\n    ):\n        \"\"\"Plot a slice of fields on the given 3D mesh.\n\n        Parameters\n        ----------\n        v : numpy.ndarray\n            values to plot\n        v_type : {'CC','CCV', 'N', 'F', 'Fx', 'Fy', 'Fz', 'E', 'Ex', 'Ey', 'Ez'}, or tuple of these options\n            Where the values of v are defined.\n        normal : {'Z', 'X', 'Y'}\n            Normal direction of slicing plane.\n        ind : None, optional\n            index along dimension of slice. Defaults to the center index.\n        slice_loc : None, optional\n            Value along dimension of slice. Defaults to the center of the mesh.\n        view : {'real', 'imag', 'abs', 'vec'}\n            How to view the array.\n        ax : matplotlib.axes.Axes, optional\n            The axes to draw on. None produces a new Axes. Must be None if ``v_type`` is a tuple.\n        clim : tuple of float, optional\n            length 2 tuple of (vmin, vmax) for the color limits\n        range_x, range_y : tuple of float, optional\n            length 2 tuple of (min, max) for the bounds of the plot axes.\n        pcolor_opts : dict, optional\n            Arguments passed on to ``pcolormesh``\n        grid : bool, optional\n            Whether to plot the edges of the mesh cells.\n        grid_opts : dict, optional\n            If ``grid`` is true, arguments passed on to ``plot`` for the edges\n        sample_grid : tuple of numpy.ndarray, optional\n            If ``view`` == 'vec', mesh cell widths (hx, hy) to interpolate onto for vector plotting\n        stream_opts : dict, optional\n            If ``view`` == 'vec', arguments passed on to ``streamplot``\n        stream_thickness : float, optional\n            If ``view`` == 'vec', linewidth for ``streamplot``\n        stream_threshold : float, optional\n            If ``view`` == 'vec', only plots vectors with magnitude above this threshold\n        show_it : bool, optional\n            Whether to call plt.show()\n\n        Examples\n        --------\n        Plot a slice of a 3D `TensorMesh` solution to a Laplace's equaiton.\n\n        First build the mesh:\n\n        >>> from matplotlib import pyplot as plt\n        >>> import discretize\n        >>> from scipy.sparse.linalg import spsolve\n        >>> hx = [(5, 2, -1.3), (2, 4), (5, 2, 1.3)]\n        >>> hy = [(2, 2, -1.3), (2, 6), (2, 2, 1.3)]\n        >>> hz = [(2, 2, -1.3), (2, 6), (2, 2, 1.3)]\n        >>> M = discretize.TensorMesh([hx, hy, hz])\n\n        then build the necessary parts of the PDE:\n\n        >>> q = np.zeros(M.vnC)\n        >>> q[[4, 4], [4, 4], [2, 6]]=[-1, 1]\n        >>> q = discretize.utils.mkvc(q)\n        >>> A = M.face_divergence * M.cell_gradient\n        >>> b = spsolve(A, q)\n\n        and finaly, plot the vector values of the result, which are defined on faces\n\n        >>> M.plot_slice(M.cell_gradient*b, 'F', view='vec', grid=True, pcolor_opts={'alpha':0.8})\n        >>> plt.show()\n\n        We can use the `slice_loc kwarg to tell `plot_slice` where to slice the mesh.\n        Let's create a mesh with a random model and plot slice of it. The `slice_loc`\n        kwarg automatically determines the indices for slicing the mesh along a plane with\n        the given normal.\n\n        >>> M = discretize.TensorMesh([32, 32, 32])\n        >>> v = discretize.utils.random_model(M.vnC, random_seed=789).reshape(-1, order='F')\n        >>> x_slice, y_slice, z_slice = 0.75, 0.25, 0.9\n        >>> plt.figure(figsize=(7.5, 3))\n        >>> ax = plt.subplot(131)\n        >>> M.plot_slice(v, normal='X', slice_loc=x_slice, ax=ax)\n        >>> ax = plt.subplot(132)\n        >>> M.plot_slice(v, normal='Y', slice_loc=y_slice, ax=ax)\n        >>> ax = plt.subplot(133)\n        >>> M.plot_slice(v, normal='Z', slice_loc=z_slice, ax=ax)\n        >>> plt.tight_layout()\n        >>> plt.show()\n\n        This also works for `TreeMesh`. We create a mesh here that is refined within three\n        boxes, along with a base level of refinement.\n\n        >>> TM = discretize.TreeMesh([32, 32, 32])\n        >>> TM.refine(3, finalize=False)\n        >>> BSW = [[0.25, 0.25, 0.25], [0.15, 0.15, 0.15], [0.1, 0.1, 0.1]]\n        >>> TNE = [[0.75, 0.75, 0.75], [0.85, 0.85, 0.85], [0.9, 0.9, 0.9]]\n        >>> levels = [6, 5, 4]\n        >>> TM.refine_box(BSW, TNE, levels)\n        >>> v_TM = discretize.utils.volume_average(M, TM, v)\n        >>> plt.figure(figsize=(7.5, 3))\n        >>> ax = plt.subplot(131)\n        >>> TM.plot_slice(v_TM, normal='X', slice_loc=x_slice, ax=ax)\n        >>> ax = plt.subplot(132)\n        >>> TM.plot_slice(v_TM, normal='Y', slice_loc=y_slice, ax=ax)\n        >>> ax = plt.subplot(133)\n        >>> TM.plot_slice(v_TM, normal='Z', slice_loc=z_slice, ax=ax)\n        >>> plt.tight_layout()\n        >>> plt.show()\n\n        \"\"\"\n        matplotlib, plt = load_matplotlib()\n\n        mesh_type = self._meshType.lower()\n        plotters = {\n            \"tree\": self.__plot_slice_tree,\n            \"tensor\": self.__plot_slice_tensor,\n            # 'curv': self.__plot_slice_curv,\n            # 'cyl': self.__plot_slice_cyl,\n        }\n        try:\n            plotter = plotters[mesh_type]\n        except KeyError:\n            raise NotImplementedError(\n                \"Mesh type `{}` does not have a plot_slice implementation.\".format(\n                    type(self).__name__\n                )\n            )\n\n        normal = normal.upper()\n        if \"pcolorOpts\" in kwargs:\n            pcolor_opts = kwargs[\"pcolorOpts\"]\n            warnings.warn(\n                \"pcolorOpts has been deprecated, please use pcolor_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"streamOpts\" in kwargs:\n            stream_opts = kwargs[\"streamOpts\"]\n            warnings.warn(\n                \"streamOpts has been deprecated, please use stream_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"gridOpts\" in kwargs:\n            grid_opts = kwargs[\"gridOpts\"]\n            warnings.warn(\n                \"gridOpts has been deprecated, please use grid_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"showIt\" in kwargs:\n            show_it = kwargs[\"showIt\"]\n            warnings.warn(\n                \"showIt has been deprecated, please use show_it\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if \"vType\" in kwargs:\n            v_type = kwargs[\"vType\"]\n            warnings.warn(\n                \"vType has been deprecated, please use v_type\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        if pcolor_opts is None:\n            pcolor_opts = {}\n        if stream_opts is None:\n            stream_opts = {\"color\": \"k\"}\n        if grid_opts is None:\n            if grid:\n                grid_opts = {\"color\": \"k\"}\n            else:\n                grid_opts = {}\n        if type(v_type) in [list, tuple]:\n            if ax is not None:\n                raise TypeError(\"cannot specify an axis to plot on with this function.\")\n            fig, axs = plt.subplots(1, len(v_type))\n            out = []\n            for v_typeI, ax in zip(v_type, axs):\n                out += [\n                    self.plot_slice(\n                        v,\n                        v_type=v_typeI,\n                        normal=normal,\n                        ind=ind,\n                        slice_loc=slice_loc,\n                        grid=grid,\n                        view=view,\n                        ax=ax,\n                        clim=clim,\n                        show_it=False,\n                        pcolor_opts=pcolor_opts,\n                        stream_opts=stream_opts,\n                        grid_opts=grid_opts,\n                        stream_threshold=stream_threshold,\n                        stream_thickness=stream_thickness,\n                    )\n                ]\n            return out\n\n        viewOpts = [\"real\", \"imag\", \"abs\", \"vec\"]\n        normalOpts = [\"X\", \"Y\", \"Z\"]\n        v_typeOpts = [\n            \"CC\",\n            \"CCv\",\n            \"N\",\n            \"F\",\n            \"E\",\n            \"Fx\",\n            \"Fy\",\n            \"Fz\",\n            \"E\",\n            \"Ex\",\n            \"Ey\",\n            \"Ez\",\n        ]\n\n        # Some user error checking\n        if v_type not in v_typeOpts:\n            raise ValueError(\n                \"v_type must be in ['{0!s}']\".format(\"', '\".join(v_typeOpts))\n            )\n        if not self.dim == 3:\n            raise TypeError(\"Must be a 3D mesh. Use plot_image.\")\n        if view not in viewOpts:\n            raise ValueError(\"view must be in ['{0!s}']\".format(\"', '\".join(viewOpts)))\n        if normal not in normalOpts:\n            raise ValueError(\n                \"normal must be in ['{0!s}']\".format(\"', '\".join(normalOpts))\n            )\n        if not isinstance(grid, bool):\n            raise TypeError(\"grid must be a boolean\")\n\n        if v.dtype == complex and view == \"vec\":\n            raise NotImplementedError(\"Can not plot a complex vector.\")\n\n        if self.dim == 2:\n            raise NotImplementedError(\"Must be a 3D mesh. Use plot_image.\")\n\n        # slice_loc errors\n        if (ind is not None) and (slice_loc is not None):\n            raise Warning(\"Both ind and slice_loc are defined. Behavior undefined.\")\n        # slice_loc implement\n        if slice_loc is not None:\n            if normal == \"X\":\n                ind = int(np.argmin(np.abs(self.cell_centers_x - slice_loc)))\n            if normal == \"Y\":\n                ind = int(np.argmin(np.abs(self.cell_centers_y - slice_loc)))\n            if normal == \"Z\":\n                ind = int(np.argmin(np.abs(self.cell_centers_z - slice_loc)))\n\n        if ax is None:\n            plt.figure()\n            ax = plt.subplot(111)\n        else:\n            if not isinstance(ax, matplotlib.axes.Axes):\n                raise TypeError(\"ax must be an matplotlib.axes.Axes\")\n\n        if clim is not None:\n            pcolor_opts[\"vmin\"] = clim[0]\n            pcolor_opts[\"vmax\"] = clim[1]\n\n        out = plotter(\n            v,\n            v_type=v_type,\n            normal=normal,\n            ind=ind,\n            grid=grid,\n            view=view,\n            ax=ax,\n            pcolor_opts=pcolor_opts,\n            stream_opts=stream_opts,\n            grid_opts=grid_opts,\n            range_x=range_x,\n            range_y=range_y,\n            sample_grid=sample_grid,\n            stream_threshold=stream_threshold,\n            stream_thickness=stream_thickness,\n            **kwargs,\n        )\n        if show_it:\n            plt.show()\n        return out\n\n    def plot_3d_slicer(\n        self,\n        v,\n        xslice=None,\n        yslice=None,\n        zslice=None,\n        v_type=\"CC\",\n        view=\"real\",\n        axis=\"xy\",\n        transparent=None,\n        clim=None,\n        xlim=None,\n        ylim=None,\n        zlim=None,\n        aspect=\"auto\",\n        grid=(2, 2, 1),\n        pcolor_opts=None,\n        fig=None,\n        **kwargs,\n    ):\n        \"\"\"Plot slices of a 3D volume, interactively (scroll wheel).\n\n        If called from a notebook, make sure to set\n\n            %matplotlib notebook\n\n        See the class `discretize.View.Slicer` for more information.\n\n        It returns nothing. However, if you need the different figure handles\n        you can get it via\n\n          `fig = plt.gcf()`\n\n        and subsequently its children via\n\n          `fig.get_children()`\n\n        and recursively deeper, e.g.,\n\n          `fig.get_children()[0].get_children()`.\n\n        One can also provide an existing figure instance, which can be useful\n        for interactive widgets in Notebooks. The provided figure is cleared\n        first.\n\n        \"\"\"\n        _, plt = load_matplotlib()\n\n        mesh_type = self._meshType.lower()\n        if mesh_type != \"tensor\":\n            raise NotImplementedError(\n                \"plot_3d_slicer has only been implemented for a TensorMesh\"\n            )\n        # Initiate figure\n        if fig is None:\n            fig = plt.figure()\n        else:\n            fig.clf()\n\n        if \"pcolorOpts\" in kwargs:\n            pcolor_opts = kwargs[\"pcolorOpts\"]\n            warnings.warn(\n                \"pcolorOpts has been deprecated, please use pcolor_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n\n        # Populate figure\n        tracker = Slicer(\n            self,\n            v,\n            xslice,\n            yslice,\n            zslice,\n            v_type,\n            view,\n            axis,\n            transparent,\n            clim,\n            xlim,\n            ylim,\n            zlim,\n            aspect,\n            grid,\n            pcolor_opts,\n        )\n\n        # Connect figure to scrolling\n        fig.canvas.mpl_connect(\"scroll_event\", tracker.onscroll)\n\n    # TensorMesh plotting\n    def __plot_grid_tensor(\n        self,\n        ax=None,\n        nodes=False,\n        faces=False,\n        centers=False,\n        edges=False,\n        lines=True,\n        color=\"C0\",\n        linewidth=1.0,\n        **kwargs,\n    ):\n        if self.dim == 1:\n            if nodes:\n                ax.plot(\n                    self.gridN, np.ones(self.nN), color=\"C0\", marker=\"s\", linestyle=\"\"\n                )\n            if centers:\n                ax.plot(\n                    self.gridCC, np.ones(self.nC), color=\"C1\", marker=\"o\", linestyle=\"\"\n                )\n            if lines:\n                ax.plot(self.gridN, np.ones(self.nN), color=\"C0\", linestyle=\"-\")\n            ax.set_xlabel(\"x1\")\n        elif self.dim == 2:\n            if nodes:\n                ax.plot(\n                    self.gridN[:, 0],\n                    self.gridN[:, 1],\n                    color=\"C0\",\n                    marker=\"s\",\n                    linestyle=\"\",\n                )\n            if centers:\n                ax.plot(\n                    self.gridCC[:, 0],\n                    self.gridCC[:, 1],\n                    color=\"C1\",\n                    marker=\"o\",\n                    linestyle=\"\",\n                )\n            if faces:\n                ax.plot(\n                    self.gridFx[:, 0],\n                    self.gridFx[:, 1],\n                    color=\"C2\",\n                    marker=\">\",\n                    linestyle=\"\",\n                )\n                ax.plot(\n                    self.gridFy[:, 0],\n                    self.gridFy[:, 1],\n                    color=\"C2\",\n                    marker=\"^\",\n                    linestyle=\"\",\n                )\n            if edges:\n                ax.plot(\n                    self.gridEx[:, 0],\n                    self.gridEx[:, 1],\n                    color=\"C3\",\n                    marker=\">\",\n                    linestyle=\"\",\n                )\n                ax.plot(\n                    self.gridEy[:, 0],\n                    self.gridEy[:, 1],\n                    color=\"C3\",\n                    marker=\"^\",\n                    linestyle=\"\",\n                )\n\n            # Plot the grid lines\n            if lines:\n                NN = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n                nCx, nCy = self.shape_cells\n                X1 = np.c_[\n                    mkvc(NN[0][0, :]), mkvc(NN[0][nCx, :]), mkvc(NN[0][0, :]) * np.nan\n                ].flatten()\n                Y1 = np.c_[\n                    mkvc(NN[1][0, :]), mkvc(NN[1][nCx, :]), mkvc(NN[1][0, :]) * np.nan\n                ].flatten()\n                X2 = np.c_[\n                    mkvc(NN[0][:, 0]), mkvc(NN[0][:, nCy]), mkvc(NN[0][:, 0]) * np.nan\n                ].flatten()\n                Y2 = np.c_[\n                    mkvc(NN[1][:, 0]), mkvc(NN[1][:, nCy]), mkvc(NN[1][:, 0]) * np.nan\n                ].flatten()\n                X = np.r_[X1, X2]\n                Y = np.r_[Y1, Y2]\n                ax.plot(X, Y, color=color, linestyle=\"-\", lw=linewidth)\n\n            ax.set_xlabel(\"x1\")\n            ax.set_ylabel(\"x2\")\n        elif self.dim == 3:\n            if nodes:\n                ax.plot(\n                    self.gridN[:, 0],\n                    self.gridN[:, 1],\n                    color=\"C0\",\n                    marker=\"s\",\n                    linestyle=\"\",\n                    zs=self.gridN[:, 2],\n                )\n            if centers:\n                ax.plot(\n                    self.gridCC[:, 0],\n                    self.gridCC[:, 1],\n                    color=\"C1\",\n                    marker=\"o\",\n                    linestyle=\"\",\n                    zs=self.gridCC[:, 2],\n                )\n            if faces:\n                ax.plot(\n                    self.gridFx[:, 0],\n                    self.gridFx[:, 1],\n                    color=\"C2\",\n                    marker=\">\",\n                    linestyle=\"\",\n                    zs=self.gridFx[:, 2],\n                )\n                ax.plot(\n                    self.gridFy[:, 0],\n                    self.gridFy[:, 1],\n                    color=\"C2\",\n                    marker=\"<\",\n                    linestyle=\"\",\n                    zs=self.gridFy[:, 2],\n                )\n                ax.plot(\n                    self.gridFz[:, 0],\n                    self.gridFz[:, 1],\n                    color=\"C2\",\n                    marker=\"^\",\n                    linestyle=\"\",\n                    zs=self.gridFz[:, 2],\n                )\n            if edges:\n                ax.plot(\n                    self.gridEx[:, 0],\n                    self.gridEx[:, 1],\n                    color=\"C3\",\n                    marker=\">\",\n                    linestyle=\"\",\n                    zs=self.gridEx[:, 2],\n                )\n                ax.plot(\n                    self.gridEy[:, 0],\n                    self.gridEy[:, 1],\n                    color=\"C3\",\n                    marker=\"<\",\n                    linestyle=\"\",\n                    zs=self.gridEy[:, 2],\n                )\n                ax.plot(\n                    self.gridEz[:, 0],\n                    self.gridEz[:, 1],\n                    color=\"C3\",\n                    marker=\"^\",\n                    linestyle=\"\",\n                    zs=self.gridEz[:, 2],\n                )\n\n            # Plot the grid lines\n            if lines:\n                nCx, nCy, nCz = self.shape_cells\n                NN = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n                X1 = np.c_[\n                    mkvc(NN[0][0, :, :]),\n                    mkvc(NN[0][nCx, :, :]),\n                    mkvc(NN[0][0, :, :]) * np.nan,\n                ].flatten()\n                Y1 = np.c_[\n                    mkvc(NN[1][0, :, :]),\n                    mkvc(NN[1][nCx, :, :]),\n                    mkvc(NN[1][0, :, :]) * np.nan,\n                ].flatten()\n                Z1 = np.c_[\n                    mkvc(NN[2][0, :, :]),\n                    mkvc(NN[2][nCx, :, :]),\n                    mkvc(NN[2][0, :, :]) * np.nan,\n                ].flatten()\n                X2 = np.c_[\n                    mkvc(NN[0][:, 0, :]),\n                    mkvc(NN[0][:, nCy, :]),\n                    mkvc(NN[0][:, 0, :]) * np.nan,\n                ].flatten()\n                Y2 = np.c_[\n                    mkvc(NN[1][:, 0, :]),\n                    mkvc(NN[1][:, nCy, :]),\n                    mkvc(NN[1][:, 0, :]) * np.nan,\n                ].flatten()\n                Z2 = np.c_[\n                    mkvc(NN[2][:, 0, :]),\n                    mkvc(NN[2][:, nCy, :]),\n                    mkvc(NN[2][:, 0, :]) * np.nan,\n                ].flatten()\n                X3 = np.c_[\n                    mkvc(NN[0][:, :, 0]),\n                    mkvc(NN[0][:, :, nCz]),\n                    mkvc(NN[0][:, :, 0]) * np.nan,\n                ].flatten()\n                Y3 = np.c_[\n                    mkvc(NN[1][:, :, 0]),\n                    mkvc(NN[1][:, :, nCz]),\n                    mkvc(NN[1][:, :, 0]) * np.nan,\n                ].flatten()\n                Z3 = np.c_[\n                    mkvc(NN[2][:, :, 0]),\n                    mkvc(NN[2][:, :, nCz]),\n                    mkvc(NN[2][:, :, 0]) * np.nan,\n                ].flatten()\n                X = np.r_[X1, X2, X3]\n                Y = np.r_[Y1, Y2, Y3]\n                Z = np.r_[Z1, Z2, Z3]\n                ax.plot(X, Y, color=color, linestyle=\"-\", lw=linewidth, zs=Z)\n            ax.set_xlabel(\"x1\")\n            ax.set_ylabel(\"x2\")\n            ax.set_zlabel(\"x3\")\n\n        ax.grid(True)\n\n        return ax\n\n    def __plot_image_tensor(\n        self,\n        v,\n        v_type=\"CC\",\n        grid=False,\n        view=\"real\",\n        ax=None,\n        pcolor_opts=None,\n        stream_opts=None,\n        grid_opts=None,\n        numbering=True,\n        annotation_color=\"w\",\n        range_x=None,\n        range_y=None,\n        sample_grid=None,\n        stream_threshold=None,\n        **kwargs,\n    ):\n        if \"annotationColor\" in kwargs:\n            annotation_color = kwargs.pop(\"annotationColor\")\n            warnings.warn(\n                \"annotationColor has been deprecated, please use annotation_color\",\n                FutureWarning,\n                stacklevel=2,\n            )\n\n        if self.dim == 1:\n            if v_type == \"CC\":\n                ph = ax.plot(\n                    self.cell_centers_x, v, linestyle=\"-\", color=\"C1\", marker=\"o\"\n                )\n            elif v_type == \"N\":\n                ph = ax.plot(self.nodes_x, v, linestyle=\"-\", color=\"C0\", marker=\"s\")\n            ax.set_xlabel(\"x\")\n            ax.axis(\"tight\")\n        elif self.dim == 2:\n            return self.__plot_image_tensor2D(\n                v,\n                v_type=v_type,\n                grid=grid,\n                view=view,\n                ax=ax,\n                pcolor_opts=pcolor_opts,\n                stream_opts=stream_opts,\n                grid_opts=grid_opts,\n                range_x=range_x,\n                range_y=range_y,\n                sample_grid=sample_grid,\n                stream_threshold=stream_threshold,\n            )\n        elif self.dim == 3:\n            # get copy of image and average to cell-centers is necessary\n            if v_type == \"CC\":\n                vc = v.reshape(self.vnC, order=\"F\")\n            elif v_type == \"N\":\n                vc = (self.aveN2CC * v).reshape(self.vnC, order=\"F\")\n            elif v_type in [\"Fx\", \"Fy\", \"Fz\", \"Ex\", \"Ey\", \"Ez\"]:\n                aveOp = \"ave\" + v_type[0] + \"2CCV\"\n                # n = getattr(self, 'vn'+v_type[0])\n                # if 'x' in v_type: v = np.r_[v, np.zeros(n[1]), np.zeros(n[2])]\n                # if 'y' in v_type: v = np.r_[np.zeros(n[0]), v, np.zeros(n[2])]\n                # if 'z' in v_type: v = np.r_[np.zeros(n[0]), np.zeros(n[1]), v]\n                v = getattr(self, aveOp) * v  # average to cell centers\n                ind_xyz = {\"x\": 0, \"y\": 1, \"z\": 2}[v_type[1]]\n                vc = self.reshape(v.reshape((self.nC, -1), order=\"F\"), \"CC\", \"CC\", \"M\")[\n                    ind_xyz\n                ]\n\n            nCx, nCy, nCz = self.shape_cells\n            # determine number oE slices in x and y dimension\n            nX = int(np.ceil(np.sqrt(nCz)))\n            nY = int(np.ceil(nCz / nX))\n\n            #  allocate space for montage\n            C = np.zeros((nX * nCx, nY * nCy))\n\n            for iy in range(int(nY)):\n                for ix in range(int(nX)):\n                    iz = ix + iy * nX\n                    if iz < nCz:\n                        C[ix * nCx : (ix + 1) * nCx, iy * nCy : (iy + 1) * nCy] = vc[\n                            :, :, iz\n                        ]\n                    else:\n                        C[ix * nCx : (ix + 1) * nCx, iy * nCy : (iy + 1) * nCy] = np.nan\n\n            C = np.ma.masked_where(np.isnan(C), C)\n            xx = np.r_[0, np.cumsum(np.kron(np.ones((nX, 1)), self.h[0]).ravel())]\n            yy = np.r_[0, np.cumsum(np.kron(np.ones((nY, 1)), self.h[1]).ravel())]\n            # Plot the mesh\n\n            ph = ax.pcolormesh(xx, yy, C.T, **pcolor_opts)\n            # Plot the lines\n            gx = np.arange(nX + 1) * (self.nodes_x[-1] - self.origin[0])\n            gy = np.arange(nY + 1) * (self.nodes_y[-1] - self.origin[1])\n            # Repeat and seperate with NaN\n            gxX = np.c_[gx, gx, gx + np.nan].ravel()\n            gxY = np.kron(\n                np.ones((nX + 1, 1)), np.array([0, sum(self.h[1]) * nY, np.nan])\n            ).ravel()\n            gyX = np.kron(\n                np.ones((nY + 1, 1)), np.array([0, sum(self.h[0]) * nX, np.nan])\n            ).ravel()\n            gyY = np.c_[gy, gy, gy + np.nan].ravel()\n            ax.plot(gxX, gxY, annotation_color + \"-\", linewidth=2)\n            ax.plot(gyX, gyY, annotation_color + \"-\", linewidth=2)\n            ax.axis(\"tight\")\n\n            if numbering:\n                pad = np.sum(self.h[0]) * 0.04\n                for iy in range(int(nY)):\n                    for ix in range(int(nX)):\n                        iz = ix + iy * nX\n                        if iz < nCz:\n                            ax.text(\n                                (ix + 1) * (self.nodes_x[-1] - self.origin[0]) - pad,\n                                (iy) * (self.nodes_y[-1] - self.origin[1]) + pad,\n                                \"#{0:.0f}\".format(iz),\n                                color=annotation_color,\n                                verticalalignment=\"bottom\",\n                                horizontalalignment=\"right\",\n                                size=\"x-large\",\n                            )\n\n        ax.set_title(v_type)\n        return (ph,)\n\n    def __plot_image_tensor2D(\n        self,\n        v,\n        v_type=\"CC\",\n        grid=False,\n        view=\"real\",\n        ax=None,\n        pcolor_opts=None,\n        stream_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        sample_grid=None,\n        stream_threshold=None,\n        stream_thickness=None,\n    ):\n        # Common function for plotting an image of a TensorMesh\n        matplotlib, plt = load_matplotlib()\n\n        if ax is None:\n            plt.figure()\n            ax = plt.subplot(111)\n        else:\n            if not isinstance(ax, matplotlib.axes.Axes):\n                raise AssertionError(\"ax must be an matplotlib.axes.Axes\")\n\n        # Reshape to a cell centered variable\n        if v_type == \"CC\":\n            pass\n        elif v_type == \"CCv\":\n            if view != \"vec\":\n                raise AssertionError(\"Other types for CCv not supported\")\n        elif v_type in [\"F\", \"E\", \"N\"]:\n            aveOp = \"ave\" + v_type + (\"2CCV\" if view == \"vec\" else \"2CC\")\n            v = getattr(self, aveOp) * v  # average to cell centers (might be a vector)\n        elif v_type in [\"Fx\", \"Fy\", \"Ex\", \"Ey\"]:\n            aveOp = \"ave\" + v_type[0] + \"2CCV\"\n            v = getattr(self, aveOp) * v  # average to cell centers (might be a vector)\n            xORy = {\"x\": 0, \"y\": 1}[v_type[1]]\n            v = v.reshape((self.nC, -1), order=\"F\")[:, xORy]\n\n        out = ()\n        if view in [\"real\", \"imag\", \"abs\"]:\n            v = self.reshape(v, \"CC\", \"CC\", \"M\")\n            v = getattr(np, view)(v)  # e.g. np.real(v)\n            v = np.ma.masked_where(np.isnan(v), v)\n            out += (\n                ax.pcolormesh(\n                    self.nodes_x,\n                    self.nodes_y,\n                    v.T,\n                    **{**pcolor_opts, **grid_opts},\n                ),\n            )\n        elif view in [\"vec\"]:\n            # Matplotlib seems to not support irregular\n            # spaced vectors at the moment. So we will\n            # Interpolate down to a regular mesh at the\n            # smallest mesh size in this 2D slice.\n            if sample_grid is not None:\n                hxmin = sample_grid[0]\n                hymin = sample_grid[1]\n            else:\n                hxmin = self.h[0].min()\n                hymin = self.h[1].min()\n\n            if range_x is not None:\n                dx = range_x[1] - range_x[0]\n                nxi = int(dx / hxmin)\n                hx = np.ones(nxi) * dx / nxi\n                origin_x = range_x[0]\n            else:\n                nxi = int(self.h[0].sum() / hxmin)\n                hx = np.ones(nxi) * self.h[0].sum() / nxi\n                origin_x = self.origin[0]\n\n            if range_y is not None:\n                dy = range_y[1] - range_y[0]\n                nyi = int(dy / hymin)\n                hy = np.ones(nyi) * dy / nyi\n                origin_y = range_y[0]\n            else:\n                nyi = int(self.h[1].sum() / hymin)\n                hy = np.ones(nyi) * self.h[1].sum() / nyi\n                origin_y = self.origin[1]\n\n            U, V = self.reshape(v.reshape((self.nC, -1), order=\"F\"), \"CC\", \"CC\", \"M\")\n\n            tMi = self.__class__(h=[hx, hy], origin=np.r_[origin_x, origin_y])\n            P = self.get_interpolation_matrix(tMi.gridCC, \"CC\", zeros_outside=True)\n\n            Ui = tMi.reshape(P * mkvc(U), \"CC\", \"CC\", \"M\")\n            Vi = tMi.reshape(P * mkvc(V), \"CC\", \"CC\", \"M\")\n            # End Interpolation\n\n            x = self.nodes_x\n            y = self.nodes_y\n\n            if range_x is not None:\n                x = tMi.nodes_x\n\n            if range_y is not None:\n                y = tMi.nodes_y\n\n            if range_x is not None or range_y is not None:  # use interpolated values\n                U = Ui\n                V = Vi\n\n            if stream_threshold is not None:\n                mask_me = np.sqrt(Ui**2 + Vi**2) <= stream_threshold\n                Ui = np.ma.masked_where(mask_me, Ui)\n                Vi = np.ma.masked_where(mask_me, Vi)\n\n            if stream_thickness is not None:\n                scaleFact = np.copy(stream_thickness)\n\n                # Calculate vector amplitude\n                vecAmp = np.sqrt(U**2 + V**2).T\n\n                # Form bounds to knockout the top and bottom 10%\n                vecAmp_sort = np.sort(vecAmp.ravel())\n                nVecAmp = vecAmp.size\n                tenPercInd = int(np.ceil(0.1 * nVecAmp))\n                lowerBound = vecAmp_sort[tenPercInd]\n                upperBound = vecAmp_sort[-tenPercInd]\n\n                lowInds = np.where(vecAmp < lowerBound)\n                vecAmp[lowInds] = lowerBound\n\n                highInds = np.where(vecAmp > upperBound)\n                vecAmp[highInds] = upperBound\n\n                # Normalize amplitudes 0-1\n                norm_thickness = vecAmp / vecAmp.max()\n\n                # Scale by user defined thickness factor\n                stream_thickness = scaleFact * norm_thickness\n\n                # Add linewidth to stream_opts\n                stream_opts.update({\"linewidth\": stream_thickness})\n\n            out += (\n                ax.pcolormesh(\n                    x,\n                    y,\n                    np.sqrt(U**2 + V**2).T,\n                    **{**pcolor_opts, **grid_opts},\n                ),\n            )\n            out += (\n                ax.streamplot(\n                    tMi.cell_centers_x,\n                    tMi.cell_centers_y,\n                    Ui.T,\n                    Vi.T,\n                    **stream_opts,\n                ),\n            )\n\n        ax.set_xlabel(\"x\")\n        ax.set_ylabel(\"y\")\n\n        if range_x is not None:\n            ax.set_xlim(*range_x)\n        else:\n            ax.set_xlim(*self.nodes_x[[0, -1]])\n\n        if range_y is not None:\n            ax.set_ylim(*range_y)\n        else:\n            ax.set_ylim(*self.nodes_y[[0, -1]])\n        return out\n\n    def __plot_slice_tensor(\n        self,\n        v,\n        v_type=\"CC\",\n        normal=\"z\",\n        ind=None,\n        grid=False,\n        view=\"real\",\n        ax=None,\n        pcolor_opts=None,\n        stream_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        sample_grid=None,\n        stream_threshold=None,\n        stream_thickness=None,\n        **kwargs,\n    ):\n        dim_ind = {\"X\": 0, \"Y\": 1, \"Z\": 2}[normal]\n        szSliceDim = self.shape_cells[dim_ind]  #: Size of the sliced dimension\n        if ind is None:\n            ind = szSliceDim // 2\n        if not isinstance(ind, (np.integer, int)):\n            raise TypeError(\"ind must be an integer\")\n\n        def getIndSlice(v):\n            if normal == \"X\":\n                v = v[ind, :, :]\n            elif normal == \"Y\":\n                v = v[:, ind, :]\n            elif normal == \"Z\":\n                v = v[:, :, ind]\n            return v\n\n        def doSlice(v):\n            if v_type == \"CC\":\n                return getIndSlice(self.reshape(v, \"CC\", \"CC\", \"M\"))\n            elif v_type == \"CCv\":\n                if view != \"vec\":\n                    raise AssertionError(\"Other types for CCv not supported\")\n            else:\n                # Now just deal with 'F' and 'E' (x, y, z, maybe...)\n                aveOp = \"ave\" + v_type + (\"2CCV\" if view == \"vec\" else \"2CC\")\n                Av = getattr(self, aveOp)\n                if v.size == Av.shape[1]:\n                    v = Av * v\n                else:\n                    v = self.reshape(v, v_type[0], v_type)  # get specific component\n                    v = Av * v\n                # we should now be averaged to cell centers (might be a vector)\n            v = self.reshape(v.reshape((self.nC, -1), order=\"F\"), \"CC\", \"CC\", \"M\")\n            if view == \"vec\":\n                outSlice = []\n                if \"X\" not in normal:\n                    outSlice.append(getIndSlice(v[0]))\n                if \"Y\" not in normal:\n                    outSlice.append(getIndSlice(v[1]))\n                if \"Z\" not in normal:\n                    outSlice.append(getIndSlice(v[2]))\n                return np.r_[mkvc(outSlice[0]), mkvc(outSlice[1])]\n            else:\n                return getIndSlice(self.reshape(v, \"CC\", \"CC\", \"M\"))\n\n        h2d = []\n        x2d = []\n        if \"X\" not in normal:\n            h2d.append(self.h[0])\n            x2d.append(self.origin[0])\n        if \"Y\" not in normal:\n            h2d.append(self.h[1])\n            x2d.append(self.origin[1])\n        if \"Z\" not in normal:\n            h2d.append(self.h[2])\n            x2d.append(self.origin[2])\n        tM = self.__class__(h=h2d, origin=x2d)  #: Temp Mesh\n        v2d = doSlice(v)\n\n        out = tM.__plot_image_tensor2D(\n            v2d,\n            v_type=(\"CCv\" if view == \"vec\" else \"CC\"),\n            grid=grid,\n            view=view,\n            ax=ax,\n            pcolor_opts=pcolor_opts,\n            stream_opts=stream_opts,\n            grid_opts=grid_opts,\n            range_x=range_x,\n            range_y=range_y,\n            sample_grid=sample_grid,\n            stream_threshold=stream_threshold,\n            stream_thickness=stream_thickness,\n        )\n\n        ax.set_xlabel(\"y\" if normal == \"X\" else \"x\")\n        ax.set_ylabel(\"y\" if normal == \"Z\" else \"z\")\n        ax.set_title(\"Slice {0:.0f}\".format(ind))\n        return out\n\n    # CylindricalMesh plotting\n    def __plotCylTensorMesh(self, plotType, *args, **kwargs):\n        matplotlib, plt = load_matplotlib()\n\n        if not self.is_symmetric:\n            raise NotImplementedError(\"We have not yet implemented this type of view.\")\n        if plotType not in [\"plot_image\", \"plot_grid\"]:\n            raise TypeError(\"plotType must be either 'plot_grid' or 'plot_image'.\")\n\n        if len(args) > 0:\n            val = args[0]\n\n        v_type = kwargs.get(\"v_type\", None)\n        mirror = kwargs.pop(\"mirror\", None)\n        mirror_data = kwargs.pop(\"mirror_data\", None)\n\n        if mirror_data is not None and mirror is None:\n            mirror = True\n\n        if v_type is not None:\n            if v_type.upper() != \"CCV\":\n                if v_type.upper() == \"F\":\n                    val = mkvc(self.aveF2CCV * val)\n                    if mirror_data is not None:\n                        mirror_data = mkvc(self.aveF2CCV * mirror_data)\n                    kwargs[\"v_type\"] = \"CCv\"  # now the vector is cell centered\n                if v_type.upper() == \"E\":\n                    val = mkvc(self.aveE2CCV * val)\n                    if mirror_data is not None:\n                        mirror_data = mkvc(self.aveE2CCV * mirror_data)\n                args = (val,) + args[1:]\n\n        if mirror:\n            # create a mirrored mesh\n            hx = np.hstack([np.flipud(self.h[0]), self.h[0]])\n            origin0 = self.origin[0] - self.h[0].sum()\n            M = discretize.TensorMesh([hx, self.h[2]], origin=[origin0, self.origin[2]])\n\n            if mirror_data is None:\n                mirror_data = val\n\n            if len(val) == self.nC:  # only a single value at cell centers\n                val = val.reshape(self.vnC[0], self.vnC[2], order=\"F\")\n                mirror_val = mirror_data.reshape(self.vnC[0], self.vnC[2], order=\"F\")\n                val = mkvc(np.vstack([np.flipud(mirror_val), val]))\n\n            elif len(val) == 2 * self.nC:\n                val_x = val[: self.nC].reshape(self.vnC[0], self.vnC[2], order=\"F\")\n                val_z = val[self.nC :].reshape(self.vnC[0], self.vnC[2], order=\"F\")\n\n                mirror_x = mirror_data[: self.nC].reshape(\n                    self.vnC[0], self.vnC[2], order=\"F\"\n                )\n                mirror_z = mirror_data[self.nC :].reshape(\n                    self.vnC[0], self.vnC[2], order=\"F\"\n                )\n\n                val_x = mkvc(\n                    np.vstack([-1.0 * np.flipud(mirror_x), val_x])\n                )  # by symmetry\n                val_z = mkvc(np.vstack([np.flipud(mirror_z), val_z]))\n\n                val = np.hstack([val_x, val_z])\n\n            args = (val,) + args[1:]\n        else:\n            M = discretize.TensorMesh(\n                [self.h[0], self.h[2]], origin=[self.origin[0], self.origin[2]]\n            )\n\n        ax = kwargs.get(\"ax\", None)\n        if ax is None:\n            plt.figure()\n            ax = plt.subplot(111)\n            kwargs[\"ax\"] = ax\n        else:\n            if not isinstance(ax, matplotlib.axes.Axes):\n                raise AssertionError(\"ax must be an matplotlib.axes.Axes\")\n\n        out = getattr(M, plotType)(*args, **kwargs)\n\n        ax.set_xlabel(\"x\")\n        ax.set_ylabel(\"z\")\n\n        return out\n\n    def __plot_grid_cyl(self, *args, **kwargs):\n        _, plt = load_matplotlib()\n\n        if self.is_symmetric:\n            return self.__plotCylTensorMesh(\"plot_grid\", *args, **kwargs)\n\n        # allow a slice to be provided for the mesh\n        slc = kwargs.pop(\"slice\", None)\n        if isinstance(slc, str):\n            slc = slc.lower()\n        if slc not in [\"theta\", \"z\", \"both\", None]:\n            raise ValueError(\n                \"slice must be either 'theta','z', or 'both' not {}\".format(slc)\n            )\n\n        # if slc is None, provide slices in both the theta and z directions\n        if slc == \"theta\":\n            return self.__plot_gridThetaSlice(*args, **kwargs)\n        elif slc == \"z\":\n            return self.__plot_gridZSlice(*args, **kwargs)\n        else:\n            ax = kwargs.pop(\"ax\", None)\n            if ax is not None:\n                if not isinstance(ax, list) or len(ax) != 2:\n                    warnings.warn(\n                        \"two axes handles must be provided to plot both theta \"\n                        \"and z slices through the mesh. Over-writing the axes.\",\n                        stacklevel=2,\n                    )\n                    ax = None\n                else:\n                    # find the one with a polar projection and pass it to the\n                    # theta slice, other one to the z-slice\n                    polarax = [\n                        a\n                        for a in ax\n                        if a.__class__.__name__ in [\"PolarAxesSubplot\", \"PolarAxes\"]\n                    ]\n                    if len(polarax) != 1:\n                        warnings.warn(\n                            \"\"\"\n                            No polar axes provided. Over-writing the axes. If you prefer to create your\n                            own, please use\n\n                                `ax = plt.subplot(121, projection='polar')`\n\n                            for reference, see: http://matplotlib.org/examples/pylab_examples/polar_demo.html\n                                                https://github.com/matplotlib/matplotlib/issues/312\n                            \"\"\",\n                            stacklevel=2,\n                        )\n                        ax = None\n\n                    else:\n                        polarax = polarax[0]\n                        cartax = [a for a in ax if a != polarax][0]\n\n            # ax may have been None to start with or set to None\n            if ax is None:\n                plt.figure(figsize=(12, 5))\n                polarax = plt.subplot(121, projection=\"polar\")\n                cartax = plt.subplot(122)\n\n            # update kwargs with respective axes handles\n            kwargspolar = kwargs.copy()\n            kwargspolar[\"ax\"] = polarax\n\n            kwargscart = kwargs.copy()\n            kwargscart[\"ax\"] = cartax\n\n            ax = []\n            ax.append(self.__plot_gridZSlice(*args, **kwargspolar))\n            ax.append(self.__plot_gridThetaSlice(*args, **kwargscart))\n            plt.tight_layout()\n\n        return ax\n\n    def __plot_gridThetaSlice(self, *args, **kwargs):\n        # make a cyl symmetric mesh\n        h2d = [self.h[0], 1, self.h[2]]\n        mesh2D = self.__class__(h=h2d, origin=self.origin)\n        return mesh2D.plot_grid(*args, **kwargs)\n\n    def __plot_gridZSlice(self, *args, **kwargs):\n        _, plt = load_matplotlib()\n        # https://github.com/matplotlib/matplotlib/issues/312\n        ax = kwargs.get(\"ax\", None)\n        if ax is not None:\n            print(ax.__class__.__name__)\n            if ax.__class__.__name__ not in [\"PolarAxesSubplot\", \"PolarAxes\"]:\n                warnings.warn(\n                    \"\"\"\n                    Creating new axes with Polar projection. If you prefer to create your own, please use\n\n                        `ax = plt.subplot(121, projection='polar')`\n\n                    for reference, see: http://matplotlib.org/examples/pylab_examples/polar_demo.html\n                                        https://github.com/matplotlib/matplotlib/issues/312\n                    \"\"\",\n                    stacklevel=2,\n                )\n                ax = plt.subplot(111, projection=\"polar\")\n        else:\n            ax = plt.subplot(111, projection=\"polar\")\n\n        # radial lines\n        NN = ndgrid(self.nodes_x, self.nodes_y, np.r_[0])[:, :2]\n        NN = NN.reshape((self.vnN[0], self.vnN[1], 2), order=\"F\")\n        NN = [NN[:, :, 0], NN[:, :, 1]]\n        X1 = np.c_[\n            mkvc(NN[0][0, :]),\n            mkvc(NN[0][self.shape_cells[0], :]),\n            mkvc(NN[0][0, :]) * np.nan,\n        ].flatten()\n        Y1 = np.c_[\n            mkvc(NN[1][0, :]),\n            mkvc(NN[1][self.shape_cells[0], :]),\n            mkvc(NN[1][0, :]) * np.nan,\n        ].flatten()\n\n        color = kwargs.get(\"color\", \"C0\")\n        linewidth = kwargs.get(\"linewidth\", 1.0)\n        ax.plot(Y1, X1, linestyle=\"-\", color=color, lw=linewidth)\n\n        # circles\n        n = 100\n        if self.is_wrapped:\n            th = np.linspace(0, 2 * np.pi, n)\n        else:\n            th = np.linspace(self.nodes_y[0], self.nodes_y[-1], n)\n        for r in self.nodes_x:\n            ax.plot(\n                th,\n                r * np.ones(n),\n                linestyle=\"-\",\n                color=color,\n                lw=linewidth,\n            )\n\n        return ax\n\n    def __plot_image_cyl(self, *args, **kwargs):\n        return self.__plotCylTensorMesh(\"plot_image\", *args, **kwargs)\n\n    # CurvilinearMesh plotting:\n    def __plot_grid_curv(\n        self,\n        ax=None,\n        nodes=False,\n        faces=False,\n        centers=False,\n        edges=False,\n        lines=True,\n        color=\"C0\",\n        linewidth=1.0,\n        **kwargs,\n    ):\n        NN = self.reshape(self.gridN, \"N\", \"N\", \"M\")\n        if self.dim == 2:\n            if lines:\n                X1 = np.c_[\n                    mkvc(NN[0][:-1, :]),\n                    mkvc(NN[0][1:, :]),\n                    mkvc(NN[0][:-1, :]) * np.nan,\n                ].flatten()\n                Y1 = np.c_[\n                    mkvc(NN[1][:-1, :]),\n                    mkvc(NN[1][1:, :]),\n                    mkvc(NN[1][:-1, :]) * np.nan,\n                ].flatten()\n\n                X2 = np.c_[\n                    mkvc(NN[0][:, :-1]),\n                    mkvc(NN[0][:, 1:]),\n                    mkvc(NN[0][:, :-1]) * np.nan,\n                ].flatten()\n                Y2 = np.c_[\n                    mkvc(NN[1][:, :-1]),\n                    mkvc(NN[1][:, 1:]),\n                    mkvc(NN[1][:, :-1]) * np.nan,\n                ].flatten()\n\n                X = np.r_[X1, X2]\n                Y = np.r_[Y1, Y2]\n\n                ax.plot(X, Y, color=color, linewidth=linewidth, linestyle=\"-\", **kwargs)\n        elif self.dim == 3:\n            X1 = np.c_[\n                mkvc(NN[0][:-1, :, :]),\n                mkvc(NN[0][1:, :, :]),\n                mkvc(NN[0][:-1, :, :]) * np.nan,\n            ].flatten()\n            Y1 = np.c_[\n                mkvc(NN[1][:-1, :, :]),\n                mkvc(NN[1][1:, :, :]),\n                mkvc(NN[1][:-1, :, :]) * np.nan,\n            ].flatten()\n            Z1 = np.c_[\n                mkvc(NN[2][:-1, :, :]),\n                mkvc(NN[2][1:, :, :]),\n                mkvc(NN[2][:-1, :, :]) * np.nan,\n            ].flatten()\n\n            X2 = np.c_[\n                mkvc(NN[0][:, :-1, :]),\n                mkvc(NN[0][:, 1:, :]),\n                mkvc(NN[0][:, :-1, :]) * np.nan,\n            ].flatten()\n            Y2 = np.c_[\n                mkvc(NN[1][:, :-1, :]),\n                mkvc(NN[1][:, 1:, :]),\n                mkvc(NN[1][:, :-1, :]) * np.nan,\n            ].flatten()\n            Z2 = np.c_[\n                mkvc(NN[2][:, :-1, :]),\n                mkvc(NN[2][:, 1:, :]),\n                mkvc(NN[2][:, :-1, :]) * np.nan,\n            ].flatten()\n\n            X3 = np.c_[\n                mkvc(NN[0][:, :, :-1]),\n                mkvc(NN[0][:, :, 1:]),\n                mkvc(NN[0][:, :, :-1]) * np.nan,\n            ].flatten()\n            Y3 = np.c_[\n                mkvc(NN[1][:, :, :-1]),\n                mkvc(NN[1][:, :, 1:]),\n                mkvc(NN[1][:, :, :-1]) * np.nan,\n            ].flatten()\n            Z3 = np.c_[\n                mkvc(NN[2][:, :, :-1]),\n                mkvc(NN[2][:, :, 1:]),\n                mkvc(NN[2][:, :, :-1]) * np.nan,\n            ].flatten()\n\n            X = np.r_[X1, X2, X3]\n            Y = np.r_[Y1, Y2, Y3]\n            Z = np.r_[Z1, Z2, Z3]\n\n            ax.plot(X, Y, Z, color=color, linewidth=linewidth, linestyle=\"-\", **kwargs)\n            ax.set_zlabel(\"x3\")\n\n        if nodes:\n            ax.plot(*self.gridN.T, color=color, linestyle=\"\", marker=\"s\")\n        if centers:\n            ax.plot(*self.gridCC.T, color=\"C1\", linestyle=\"\", marker=\"o\")\n        if faces:\n            ax.plot(*self.gridFx.T, color=\"C2\", marker=\">\", linestyle=\"\")\n            ax.plot(*self.gridFy.T, color=\"C2\", marker=\"<\", linestyle=\"\")\n            if self.dim == 3:\n                ax.plot(*self.gridFz.T, color=\"C2\", marker=\"^\", linestyle=\"\")\n        if edges:\n            ax.plot(*self.gridEx.T, color=\"C3\", marker=\">\", linestyle=\"\")\n            ax.plot(*self.gridEy.T, color=\"C3\", marker=\"<\", linestyle=\"\")\n            if self.dim == 3:\n                ax.plot(*self.gridEz.T, color=\"C3\", marker=\"^\", linestyle=\"\")\n\n        ax.grid(True)\n        ax.set_xlabel(\"x1\")\n        ax.set_ylabel(\"x2\")\n\n        return ax\n\n    def __plot_image_curv(\n        self,\n        v,\n        v_type=\"CC\",\n        grid=False,\n        view=\"real\",\n        ax=None,\n        pcolor_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        **kwargs,\n    ):\n        if self.dim == 3:\n            raise NotImplementedError(\"This is not yet done!\")\n        if view == \"vec\":\n            raise NotImplementedError(\n                \"Vector ploting is not supported on CurvilinearMesh (yet)\"\n            )\n        if view in [\"real\", \"imag\", \"abs\"]:\n            v = getattr(np, view)(v)  # e.g. np.real(v)\n        if v_type == \"CC\":\n            I = v\n        elif v_type == \"N\":\n            I = self.aveN2CC * v\n        elif v_type in [\"Fx\", \"Fy\", \"Ex\", \"Ey\"]:\n            aveOp = \"ave\" + v_type[0] + \"2CCV\"\n            ind_xy = {\"x\": 0, \"y\": 1}[v_type[1]]\n            I = (getattr(self, aveOp) * v).reshape(2, self.nC)[\n                ind_xy\n            ]  # average to cell centers\n        I = np.ma.masked_where(np.isnan(I), I)\n        X, Y = (x.T for x in self.node_list)\n        out = ax.pcolormesh(\n            X,\n            Y,\n            I.reshape(self.vnC[::-1]),\n            antialiased=True,\n            **pcolor_opts,\n            **grid_opts,\n        )\n\n        ax.set_xlabel(\"x\")\n        ax.set_ylabel(\"y\")\n        return (out,)\n\n    def __plot_grid_tree(\n        self,\n        ax=None,\n        nodes=False,\n        faces=False,\n        centers=False,\n        edges=False,\n        lines=True,\n        cell_line=False,\n        faces_x=False,\n        faces_y=False,\n        faces_z=False,\n        edges_x=False,\n        edges_y=False,\n        edges_z=False,\n        **kwargs,\n    ):\n        from matplotlib.collections import LineCollection  # Lazy loaded\n        from mpl_toolkits.mplot3d.art3d import Line3DCollection  # Lazy loaded\n\n        if faces:\n            faces_x = faces_y = True\n            if self.dim == 3:\n                faces_z = True\n        if edges:\n            edges_x = edges_y = True\n            if self.dim == 3:\n                edges_z = True\n        if lines or nodes:\n            grid_n_full = np.r_[self.nodes, self.hanging_nodes]\n        if nodes:\n            ax.plot(*grid_n_full.T, color=\"C0\", marker=\"s\", linestyle=\"\")\n            # Hanging Nodes\n            ax.plot(\n                *self.gridhN.T,\n                color=\"C0\",\n                marker=\"s\",\n                linestyle=\"\",\n                markersize=10,\n                markerfacecolor=\"none\",\n                markeredgecolor=\"C0\",\n            )\n        if centers:\n            ax.plot(*self.cell_centers.T, color=\"C1\", marker=\"o\", linestyle=\"\")\n        if cell_line:\n            ax.plot(*self.cell_centers.T, color=\"C1\", linestyle=\":\")\n            ax.plot(\n                self.cell_centers[[0, -1], 0],\n                self.cell_centers[[0, -1], 1],\n                color=\"C1\",\n                marker=\"o\",\n                linestyle=\"\",\n            )\n\n        y_mark = \"<\" if self.dim == 3 else \"^\"\n\n        if faces_x:\n            ax.plot(\n                *np.r_[self.faces_x, self.hanging_faces_x].T,\n                color=\"C2\",\n                marker=\">\",\n                linestyle=\"\",\n            )\n            # Hanging Faces x\n            ax.plot(\n                *self.hanging_faces_x.T,\n                color=\"C2\",\n                marker=\"s\",\n                linestyle=\"\",\n                markersize=10,\n                markerfacecolor=\"none\",\n                markeredgecolor=\"C2\",\n            )\n        if faces_y:\n            ax.plot(\n                *np.r_[self.faces_y, self.hanging_faces_y].T,\n                color=\"C2\",\n                marker=y_mark,\n                linestyle=\"\",\n            )\n            # Hanging Faces y\n            ax.plot(\n                *self.hanging_faces_y.T,\n                color=\"C2\",\n                marker=\"s\",\n                linestyle=\"\",\n                markersize=10,\n                markerfacecolor=\"none\",\n                markeredgecolor=\"C2\",\n            )\n        if faces_z:\n            ax.plot(\n                *np.r_[self.faces_z, self.hanging_faces_z].T,\n                color=\"C2\",\n                marker=\"^\",\n                linestyle=\"\",\n            )\n            # Hangin Faces z\n            ax.plot(\n                *self.hanging_faces_z.T,\n                color=\"C2\",\n                marker=\"s\",\n                linestyle=\"\",\n                markersize=10,\n                markerfacecolor=\"none\",\n                markeredgecolor=\"C2\",\n            )\n        if edges_x:\n            ax.plot(\n                *np.r_[self.edges_x, self.hanging_edges_x].T,\n                color=\"C3\",\n                marker=\">\",\n                linestyle=\"\",\n            )\n            # Hanging Edges x\n            ax.plot(\n                *self.hanging_edges_x.T,\n                color=\"C3\",\n                marker=\"s\",\n                linestyle=\"\",\n                markersize=10,\n                markerfacecolor=\"none\",\n                markeredgecolor=\"C3\",\n            )\n        if edges_y:\n            ax.plot(\n                *np.r_[self.edges_y, self.hanging_edges_y].T,\n                color=\"C3\",\n                marker=y_mark,\n                linestyle=\"\",\n            )\n            # Hanging Edges y\n            ax.plot(\n                *self.hanging_edges_y.T,\n                color=\"C3\",\n                marker=\"s\",\n                linestyle=\"\",\n                markersize=10,\n                markerfacecolor=\"none\",\n                markeredgecolor=\"C3\",\n            )\n        if edges_z:\n            ax.plot(\n                *np.r_[self.edges_z, self.hanging_edges_z].T,\n                color=\"C3\",\n                marker=\"^\",\n                linestyle=\"\",\n            )\n            # Hanging Edges z\n            ax.plot(\n                *self.hanging_edges_z.T,\n                color=\"C3\",\n                marker=\"s\",\n                linestyle=\"\",\n                markersize=10,\n                markerfacecolor=\"none\",\n                markeredgecolor=\"C3\",\n            )\n\n        if lines:\n            edge_nodes = self.edge_nodes\n            lines = np.r_[grid_n_full[edge_nodes[0]], grid_n_full[edge_nodes[1]]]\n            if self.dim == 2:\n                line_segments = LineCollection(lines, **kwargs)\n            else:\n                lines = np.r_[lines, grid_n_full[edge_nodes[2]]]\n                line_segments = Line3DCollection(lines, **kwargs)\n            ax.add_collection(line_segments)\n            ax.autoscale()\n\n        ax.set_xlabel(\"x1\")\n        ax.set_ylabel(\"x2\")\n        if self.dim == 3:\n            ax.set_zlabel(\"x3\")\n\n        ax.grid(True)\n\n        return ax\n\n    def __plot_image_tree(\n        self,\n        v,\n        v_type=\"CC\",\n        grid=False,\n        view=\"real\",\n        ax=None,\n        pcolor_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        quiver_opts=None,\n        **kwargs,\n    ):\n        from matplotlib.collections import PolyCollection  # lazy loaded\n\n        if self.dim == 3:\n            raise NotImplementedError(\n                \"plot_image is not implemented for 3D TreeMesh, please use plot_slice\"\n            )\n        # reshape to cell_centered thing\n        if v_type == \"CC\":\n            pass\n        if v_type == \"CCv\":\n            if view != \"vec\":\n                raise ValueError(\"Other types for CCv not supported\")\n        elif v_type in [\"F\", \"E\", \"N\"]:\n            aveOp = aveOp = \"ave\" + v_type + (\"2CCV\" if view == \"vec\" else \"2CC\")\n            v = getattr(self, aveOp) * v\n            if view == \"vec\":\n                v = v.reshape((self.n_cells, 2), order=\"F\")\n        elif v_type in [\"Fx\", \"Fy\", \"Ex\", \"Ey\"]:\n            aveOp = \"ave\" + v_type[0] + \"2CCV\"\n            v = getattr(self, aveOp) * v\n            ind_xy = {\"x\": 0, \"y\": 1}[v_type[1]]\n            v = v.reshape(2, self.n_cells)[ind_xy]\n\n        if view in [\"real\", \"imag\", \"abs\"]:\n            I = getattr(np, view)(v)  # e.g. np.real(v)\n        elif view == \"vec\":\n            I = np.linalg.norm(v, axis=1)\n\n        isnot_nans = ~np.isnan(I)\n\n        # pcolormesh call signature\n        # def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,\n        #            vmax=None, shading='flat', antialiased=False, **kwargs):\n\n        # make a shallow copy so we can pop items off for the pass to PolyCollection\n        pcolor_opts = pcolor_opts.copy()\n\n        alpha = pcolor_opts.pop(\"alpha\", None)\n        norm = pcolor_opts.pop(\"norm\", None)\n        cmap = pcolor_opts.pop(\"cmap\", None)\n        vmin = pcolor_opts.pop(\"vmin\", None)\n        vmax = pcolor_opts.pop(\"vmax\", None)\n        pcolor_opts.pop(\"shading\", \"flat\")  # polycollection does not support shading\n        antialiased = pcolor_opts.pop(\"antialiased\", False)\n\n        node_grid = np.r_[self.nodes, self.hanging_nodes]\n        cell_nodes = self.cell_nodes[:, (0, 1, 3, 2)]\n        cell_nodes = cell_nodes[isnot_nans]\n        cell_verts = node_grid[cell_nodes]\n\n        # Below taken from pcolormesh source code with QuadMesh exchanged to PolyCollection\n        collection = PolyCollection(\n            cell_verts, antialiased=antialiased, **{**pcolor_opts, **grid_opts}\n        )\n        collection.set_alpha(alpha)\n        collection.set_array(I[isnot_nans])\n        collection.set_cmap(cmap)\n        collection.set_norm(norm)\n        try:\n            collection._scale_norm(norm, vmin, vmax)\n        except AttributeError:\n            collection.set_clim(vmin, vmax)\n            collection.autoscale_None()\n\n        ax.grid(False)\n\n        ax.add_collection(collection, autolim=False)\n\n        if range_x is not None:\n            minx, maxx = range_x\n        else:\n            minx, maxx = self.nodes_x[[0, -1]]\n\n        if range_y is not None:\n            miny, maxy = range_y\n        else:\n            miny, maxy = self.nodes_y[[0, -1]]\n\n        collection.sticky_edges.x[:] = [minx, maxx]\n        collection.sticky_edges.y[:] = [miny, maxy]\n        corners = (minx, miny), (maxx, maxy)\n        ax.update_datalim(corners)\n        ax._request_autoscale_view()\n\n        out = (collection,)\n\n        if view == \"vec\":\n            if quiver_opts is None:\n                quiver_opts = {}\n            # make a copy so we can set some defaults without modifying the original\n            quiver_opts = quiver_opts.copy()\n            quiver_opts.setdefault(\"pivot\", \"mid\")\n\n            v = v.reshape(2, self.n_cells)\n            qvr = ax.quiver(\n                self.cell_centers[:, 0],\n                self.cell_centers[:, 1],\n                v[0],\n                v[1],\n                **quiver_opts,\n            )\n            out = (collection, qvr)\n\n        return out\n\n    def __plot_slice_tree(\n        self,\n        v,\n        v_type=\"CC\",\n        normal=\"Z\",\n        ind=None,\n        grid=False,\n        view=\"real\",\n        ax=None,\n        pcolor_opts=None,\n        stream_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        quiver_opts=None,\n        **kwargs,\n    ):\n        normalInd = {\"X\": 0, \"Y\": 1, \"Z\": 2}[normal]\n        antiNormalInd = {\"X\": [1, 2], \"Y\": [0, 2], \"Z\": [0, 1]}[normal]\n\n        h2d = (self.h[antiNormalInd[0]], self.h[antiNormalInd[1]])\n        x2d = (self.origin[antiNormalInd[0]], self.origin[antiNormalInd[1]])\n\n        #: Size of the sliced dimension\n        szSliceDim = len(self.h[normalInd])\n        if ind is None:\n            ind = szSliceDim // 2\n        if not isinstance(ind, (np.integer, int)):\n            raise ValueError(\"ind must be an integer\")\n\n        cc_tensor = [self.cell_centers_x, self.cell_centers_y, self.cell_centers_z]\n        slice_loc = cc_tensor[normalInd][ind]\n\n        slice_origin = self.origin.copy()\n        slice_origin[normalInd] = slice_loc\n        normal = [0, 0, 0]\n        normal[normalInd] = 1\n\n        # create a temporary TreeMesh with the slice through\n        temp_mesh = discretize.TreeMesh(h2d, x2d, diagonal_balance=False)\n        level_diff = self.max_level - temp_mesh.max_level\n\n        # get list of cells which intersect the slicing plane\n        inds = self.get_cells_on_plane(slice_origin, normal)\n        levels = self._cell_levels_by_indexes(inds) - level_diff\n        grid2d = self.cell_centers[inds][:, antiNormalInd]\n\n        temp_mesh.insert_cells(grid2d, levels)\n        tm_gridboost = np.empty((temp_mesh.n_cells, 3))\n        tm_gridboost[:, antiNormalInd] = temp_mesh.cell_centers\n        tm_gridboost[:, normalInd] = slice_loc\n\n        # interpolate values to self.gridCC if not \"CC\" or \"CCv\"\n        if v_type[:2] != \"CC\":\n            aveOp = \"ave\" + v_type + \"2CC\"\n            if view == \"vec\":\n                aveOp += \"V\"\n            Av = getattr(self, aveOp)\n            if v.shape[0] == Av.shape[1]:\n                v = Av * v\n            if view == \"vec\":\n                v = v.reshape((self.n_cells, 3), order=\"F\")\n            elif len(v_type) == 2:\n                # was one of Fx, Fy, Fz, Ex, Ey, Ez\n                # assuming v has all three components in these cases\n                vec_ind = {\"x\": 0, \"y\": 1, \"z\": 2}[v_type[1]]\n                if v_type[0] == \"E\":\n                    i_s = np.cumsum([0, self.nEx, self.nEy, self.nEz])\n                elif v_type[0] == \"F\":\n                    i_s = np.cumsum([0, self.nFx, self.nFy, self.nFz])\n                v = v[i_s[vec_ind] : i_s[vec_ind + 1]]\n                v = Av * v\n        elif v_type == \"CCv\":\n            if view != \"vec\":\n                raise ValueError(\"Other types for CCv not supported\")\n        slice_view = view\n        if view == \"vec\":\n            slice_view = \"real\"\n            vecs = v[:, antiNormalInd]\n            v = np.linalg.norm(v, axis=1)\n\n        # interpolate values from self.gridCC to grid2d\n        ind_3d_to_2d = self.get_containing_cells(tm_gridboost)\n        v2d = v[ind_3d_to_2d]\n\n        out = temp_mesh.plot_image(\n            v2d,\n            v_type=\"CC\",\n            grid=grid,\n            view=slice_view,\n            ax=ax,\n            pcolor_opts=pcolor_opts,\n            grid_opts=grid_opts,\n            range_x=range_x,\n            range_y=range_y,\n        )\n\n        ax.set_xlabel(\"y\" if normal == \"X\" else \"x\")\n        ax.set_ylabel(\"y\" if normal == \"Z\" else \"z\")\n        ax.set_title(\"Slice {0:d}, {1!s} = {2:4.2f}\".format(ind, normal, slice_loc))\n\n        if view == \"vec\":\n            if quiver_opts is None:\n                quiver_opts = {}\n            # make a copy so we can set some defaults without modifying the original\n            quiver_opts = quiver_opts.copy()\n            quiver_opts.setdefault(\"pivot\", \"mid\")\n            vecs = vecs[ind_3d_to_2d]\n            qvr = ax.quiver(\n                temp_mesh.cell_centers[:, 0],\n                temp_mesh.cell_centers[:, 1],\n                vecs[:, 0],\n                vecs[:, 1],\n                **quiver_opts,\n            )\n            out = (\n                out,\n                qvr,\n            )\n\n        return out\n\n    def __plot_grid_simp(\n        self,\n        ax=None,\n        nodes=False,\n        faces=False,\n        centers=False,\n        edges=False,\n        lines=True,\n        show_it=False,\n        color=\"C0\",\n        linewidth=1.0,\n    ):\n        if lines:\n            if self.dim == 2:\n                ax.triplot(\n                    *self.nodes.T, self.simplices, color=color, linewidth=linewidth\n                )\n            elif self.dim == 3:\n                edge_nodes = self._edges\n                n_edges = edge_nodes.shape[0]\n                to_plot = np.full((3 * n_edges, 3), np.nan)\n                to_plot[::3] = self.nodes[edge_nodes[:, 0]]\n                to_plot[1::3] = self.nodes[edge_nodes[:, 1]]\n                ax.plot(*to_plot.T, color=color, linewidth=linewidth)\n        if nodes:\n            ax.plot(*self.nodes.T, color=\"C0\", marker=\"s\", linestyle=\"\")\n        if centers:\n            ax.plot(*self.cell_centers.T, color=\"C1\", marker=\"o\", linestyle=\"\")\n        if faces:\n            ax.plot(*self.faces.T, color=\"C2\", marker=\">\", linestyle=\"\")\n        if edges:\n            ax.plot(*self.edges.T, color=\"C3\", marker=\"^\", linestyle=\"\")\n        ax.set_xlabel(\"x1\")\n        ax.set_ylabel(\"x2\")\n        if self.dim == 3:\n            ax.set_zlabel(\"x3\")\n        return ax\n\n    def __plot_image_simp(\n        self,\n        v,\n        v_type=\"CC\",\n        grid=False,\n        view=\"real\",\n        ax=None,\n        pcolor_opts=None,\n        grid_opts=None,\n        range_x=None,\n        range_y=None,\n        quiver_opts=None,\n        **kwargs,\n    ):\n        if self.dim == 3:\n            raise NotImplementedError(\n                \"plot_image is not implemented for 3D SimplexMesh.\"\n            )\n        v = np.squeeze(v)\n        # reshape to cell_centered thing\n        if v_type == \"CCv\":\n            if view != \"vec\":\n                raise ValueError(\"Other types for CCv not supported\")\n        if \"F\" in v_type:\n            aveOp = \"average_face_to_cell\"\n            if view == \"vec\" or \"x\" in v_type or \"y\" in v_type:\n                aveOp += \"_vector\"\n            v = getattr(self, aveOp) * v\n        elif \"E\" in v_type:\n            aveOp = \"average_edge_to_cell\"\n            if view == \"vec\" or \"x\" in v_type or \"y\" in v_type:\n                aveOp += \"_vector\"\n            v = getattr(self, aveOp) * v\n        if view == \"vec\":\n            v = v.reshape((self.n_cells, 2), order=\"F\")\n        elif \"x\" in v_type:\n            v = v.reshape((self.n_cells, 2), order=\"F\")\n            v = v[:, 0]\n        elif \"y\" in v_type:\n            v = v.reshape((self.n_cells, 2), order=\"F\")\n            v = v[:, 1]\n\n        if view in [\"real\", \"imag\", \"abs\"]:\n            image_data = getattr(np, view)(v)  # e.g. np.real(v)\n        elif view == \"vec\":\n            image_data = np.linalg.norm(v, axis=1)\n        shading = \"gouraud\" if v_type == \"N\" else \"flat\"\n        trip = ax.tripcolor(\n            *self.nodes.T, self._simplices, image_data, shading=shading, **pcolor_opts\n        )\n\n        if range_x is None:\n            range_x = (self.nodes[:, 0].min(), self.nodes[:, 0].max())\n        if range_y is None:\n            range_y = (self.nodes[:, 1].min(), self.nodes[:, 1].max())\n\n        ax.set_xlabel(\"x\")\n        ax.set_ylabel(\"y\")\n        ax.set_xlim(*range_x)\n        ax.set_ylim(*range_y)\n\n        out = (trip,)\n        if view == \"vec\":\n            if quiver_opts is None:\n                quiver_opts = {}\n            # make a copy so we can set some defaults without modifying the original\n            quiver_opts = quiver_opts.copy()\n            quiver_opts.setdefault(\"pivot\", \"mid\")\n\n            qvr = ax.quiver(\n                self.cell_centers[:, 0],\n                self.cell_centers[:, 1],\n                v[:, 0],\n                v[:, 1],\n                **quiver_opts,\n            )\n            out = (trip, qvr)\n\n        return out\n\n    plotGrid = deprecate_method(\n        \"plot_grid\", \"plotGrid\", removal_version=\"1.0.0\", future_warn=True\n    )\n    plotImage = deprecate_method(\n        \"plot_image\", \"plotImage\", removal_version=\"1.0.0\", future_warn=True\n    )\n    plotSlice = deprecate_method(\n        \"plot_slice\", \"plotSlice\", removal_version=\"1.0.0\", future_warn=True\n    )\n\n\nclass Slicer(object):\n    \"\"\"Plot slices of a 3D volume, interactively (scroll wheel).\n\n    If called from a notebook, make sure to set\n\n        %matplotlib notebook\n\n\n    Parameters\n    ----------\n    v : (n_cells) numpy.ndarray\n        Data array\n    xslice, yslice, zslice : float, optional\n        Initial slice locations (in meter);\n        defaults to the middle of the volume.\n    v_type: {'CC', 'Fx', 'Fy', 'Fz', 'Ex', 'Ey', 'Ez'}\n        Type of visualization.\n    view : {'real', 'imag', 'abs'}\n        Which component to show.\n    axis : {'xy', 'yx'}\n        'xy': horizontal axis is x, vertical axis is y. Reversed otherwise.\n    transparent : 'slider' or list of float or pairs of float, optional\n        Values to be removed. E.g. air, water.\n        If single value, only exact matches are removed. Pairs are treated as\n        ranges. E.g. [0.3, [1, 4], [-np.infty, -10]] removes all values equal\n        to 0.3, all values between 1 and 4, and all values smaller than -10.\n        If 'slider' is provided it will plot an interactive slider to choose\n        the shown range.\n    clim : None or list of [min, max]\n        For `pcolormesh` (`vmin`, `vmax`). Note: if you use a `norm` (e.g.,\n        `LogNorm`) then `vmin`/`vmax` have to be provided in the norm.\n    xlim, ylim, zlim : None or list of [min, max]\n        Axis limits.\n    aspect : 'auto', 'equal', or num\n        Aspect ratio of subplots. Defaults to 'auto'.\n\n        A list of two values can be provided. The first will be for the\n        XY-plot, the second for the XZ- and YZ-plots, e.g. ['equal', 2] to have\n        the vertical dimension exaggerated by a factor of 2.\n\n        WARNING: For anything else than 'auto', unexpected things might happen\n                 when zooming, and the subplot-arrangement won't look pretty.\n    grid : (3) list of int\n        Number of cells occupied by x, y, and z dimension on plt.subplot2grid.\n    pcolor_opts : dictionary\n        Passed to `pcolormesh`.\n\n    Examples\n    --------\n    The straight forward usage for the Slicer is through, e.g., a\n    `TensorMesh`-mesh, by accessing its `mesh.plot_3d_slicer`.\n\n    If you, however, call this class directly, you have first to initiate a\n    figure, and afterwards connect it:\n\n    >>> fig = plt.figure()\n\n    Then you have to get the tracker from the Slicer\n\n    >>> tracker = discretize.View.Slicer(mesh, Lpout)\n\n    Finally you have to connect the tracker to the figure\n\n    >>> fig.canvas.mpl_connect('scroll_event', tracker.onscroll)\n    >>> plt.show()\n\n    \"\"\"\n\n    def __init__(\n        self,\n        mesh,\n        v,\n        xslice=None,\n        yslice=None,\n        zslice=None,\n        v_type=\"CC\",\n        view=\"real\",\n        axis=\"xy\",\n        transparent=None,\n        clim=None,\n        xlim=None,\n        ylim=None,\n        zlim=None,\n        aspect=\"auto\",\n        grid=(2, 2, 1),\n        pcolor_opts=None,\n        **kwargs,\n    ):\n        \"\"\"Initialize interactive figure.\"\"\"\n        _, plt = load_matplotlib()\n        from matplotlib.widgets import Slider  # Lazy loaded\n        from matplotlib.colors import Normalize\n\n        # 0. Some checks, not very extensive\n        if \"pcolorOpts\" in kwargs:\n            pcolor_opts = kwargs[\"pcolorOpts\"]\n            warnings.warn(\n                \"pcolorOpts has been deprecated, please use pcolor_opts\",\n                FutureWarning,\n                stacklevel=2,\n            )\n\n        # Add pcolor_opts to self\n        self.pc_props = pcolor_opts if pcolor_opts is not None else {}\n\n        # (a) Mesh dimensionality\n        if mesh.dim != 3:\n            err = \"Must be a 3D mesh. Use plot_image instead.\"\n            err += \" Mesh provided has {} dimension(s).\".format(mesh.dim)\n            raise ValueError(err)\n\n        # (b) v_type  # Not yet working for ['CCv']\n        v_typeOpts = [\"CC\", \"Fx\", \"Fy\", \"Fz\", \"Ex\", \"Ey\", \"Ez\"]\n        if v_type not in v_typeOpts:\n            err = \"v_type must be in ['{0!s}'].\".format(\"', '\".join(v_typeOpts))\n            err += \" v_type provided: '{0!s}'.\".format(v_type)\n            raise ValueError(err)\n\n        if v_type != \"CC\":\n            aveOp = \"ave\" + v_type + \"2CC\"\n            Av = getattr(mesh, aveOp)\n            if v.size == Av.shape[1]:\n                v = Av * v\n            else:\n                v = mesh.reshape(v, v_type[0], v_type)  # get specific component\n                v = Av * v\n\n        # (c) vOpts  # Not yet working for 'vec'\n\n        # Backwards compatibility\n        if view in [\"xy\", \"yx\"]:\n            axis = view\n            view = \"real\"\n\n        viewOpts = [\"real\", \"imag\", \"abs\"]\n        if view in viewOpts:\n            v = getattr(np, view)(v)  # e.g. np.real(v)\n        else:\n            err = \"view must be in ['{0!s}'].\".format(\"', '\".join(viewOpts))\n            err += \" view provided: '{0!s}'.\".format(view)\n            raise ValueError(err)\n\n        # 1. Store relevant data\n\n        # Store data in self as (nx, ny, nz)\n        self.v = mesh.reshape(v.reshape((mesh.nC, -1), order=\"F\"), \"CC\", \"CC\", \"M\")\n        self.v = np.ma.masked_array(self.v, np.isnan(self.v))\n\n        # Store relevant information from mesh in self\n        self.x = mesh.nodes_x  # x-node locations\n        self.y = mesh.nodes_y  # y-node locations\n        self.z = mesh.nodes_z  # z-node locations\n        self.xc = mesh.cell_centers_x  # x-cell center locations\n        self.yc = mesh.cell_centers_y  # y-cell center locations\n        self.zc = mesh.cell_centers_z  # z-cell center locations\n\n        # Axis: Default ('xy'): horizontal axis is x, vertical axis is y.\n        # Reversed otherwise.\n        self.yx = axis == \"yx\"\n\n        # Store initial slice indices; if not provided, takes the middle.\n        if xslice is not None:\n            self.xind = np.argmin(np.abs(self.xc - xslice))\n        else:\n            self.xind = self.xc.size // 2\n        if yslice is not None:\n            self.yind = np.argmin(np.abs(self.yc - yslice))\n        else:\n            self.yind = self.yc.size // 2\n        if zslice is not None:\n            self.zind = np.argmin(np.abs(self.zc - zslice))\n        else:\n            self.zind = self.zc.size // 2\n\n        # Aspect ratio\n        if isinstance(aspect, (list, tuple)):\n            aspect1 = aspect[0]\n            aspect2 = aspect[1]\n        else:\n            aspect1 = aspect\n            aspect2 = aspect\n        if aspect2 in [\"auto\", \"equal\"]:\n            aspect3 = aspect2\n        else:\n            aspect3 = 1.0 / aspect2\n\n        # Ensure a consistent color normalization for the three plots.\n        if (norm := self.pc_props.get(\"norm\", None)) is None:\n            # Create a default normalizer\n            norm = Normalize()\n            if clim is not None:\n                norm.vmin, norm.vmax = clim\n            self.pc_props[\"norm\"] = norm\n        else:\n            if clim is not None:\n                raise ValueError(\n                    \"Passing a Normalize instance simultaneously with clim is not supported. \"\n                    \"Please pass vmin/vmax directly to the norm when creating it.\"\n                )\n\n        # Auto scales None values for norm.vmin and norm.vmax.\n        norm.autoscale_None(self.v[~self.v.mask].reshape(-1, order=\"A\"))\n\n        # 2. Start populating figure\n\n        # Get plot2grid dimension\n        figgrid = (grid[0] + grid[2], grid[1] + grid[2])\n\n        # Create subplots\n        self.fig = plt.gcf()\n        self.fig.subplots_adjust(wspace=0.075, hspace=0.1)\n        # To capture mouse scroll in notebooks (otherwise it is hard to slice through volume)\n        self.fig.canvas.capture_scroll = True\n\n        # X-Y\n        self.ax1 = plt.subplot2grid(\n            figgrid, (0, 0), colspan=grid[1], rowspan=grid[0], aspect=aspect1\n        )\n        if self.yx:\n            self.ax1.set_ylabel(\"x\")\n            if ylim is not None:\n                self.ax1.set_xlim([ylim[0], ylim[1]])\n            if xlim is not None:\n                self.ax1.set_ylim([xlim[0], xlim[1]])\n        else:\n            self.ax1.set_ylabel(\"y\")\n            if xlim is not None:\n                self.ax1.set_xlim([xlim[0], xlim[1]])\n            if ylim is not None:\n                self.ax1.set_ylim([ylim[0], ylim[1]])\n        self.ax1.xaxis.set_ticks_position(\"top\")\n        plt.setp(self.ax1.get_xticklabels(), visible=False)\n\n        # X-Z\n        self.ax2 = plt.subplot2grid(\n            figgrid,\n            (grid[0], 0),\n            colspan=grid[1],\n            rowspan=grid[2],\n            sharex=self.ax1,\n            aspect=aspect2,\n        )\n        self.ax2.yaxis.set_ticks_position(\"both\")\n        if self.yx:\n            self.ax2.set_xlabel(\"y\")\n            if ylim is not None:\n                self.ax2.set_xlim([ylim[0], ylim[1]])\n        else:\n            self.ax2.set_xlabel(\"x\")\n            if xlim is not None:\n                self.ax2.set_xlim([xlim[0], xlim[1]])\n        self.ax2.set_ylabel(\"z\")\n        if zlim is not None:\n            self.ax2.set_ylim([zlim[0], zlim[1]])\n\n        # Z-Y\n        self.ax3 = plt.subplot2grid(\n            figgrid,\n            (0, grid[1]),\n            colspan=grid[2],\n            rowspan=grid[0],\n            sharey=self.ax1,\n            aspect=aspect3,\n        )\n        self.ax3.yaxis.set_ticks_position(\"right\")\n        self.ax3.xaxis.set_ticks_position(\"both\")\n        self.ax3.invert_xaxis()\n        plt.setp(self.ax3.get_yticklabels(), visible=False)\n        if self.yx:\n            if xlim is not None:\n                self.ax3.set_ylim([xlim[0], xlim[1]])\n        else:\n            if ylim is not None:\n                self.ax3.set_ylim([ylim[0], ylim[1]])\n        if zlim is not None:\n            self.ax3.set_xlim([zlim[1], zlim[0]])\n\n        # Cross-line properties\n        # We have two lines, a thick white one, and in the middle a thin black\n        # one, to assure that the lines can be seen on dark and on bright\n        # spots.\n        self.clpropsw = {\"c\": \"w\", \"lw\": 2, \"zorder\": 10}\n        self.clpropsk = {\"c\": \"k\", \"lw\": 1, \"zorder\": 11}\n\n        # Initial draw\n        self.update_xy()\n        self.update_xz()\n        self.update_zy()\n\n        # Create colorbar\n        plt.colorbar(self.zy_pc, pad=0.15)\n\n        # Remove transparent value\n        if isinstance(transparent, str) and transparent.lower() == \"slider\":\n            clim = (norm.vmin, norm.vmax)\n            # Sliders\n            self.ax_smin = plt.axes([0.7, 0.11, 0.15, 0.03])\n            self.ax_smax = plt.axes([0.7, 0.15, 0.15, 0.03])\n\n            # Limits slightly below/above actual limits, clips otherwise\n            self.smin = Slider(self.ax_smin, \"Min\", *clim, valinit=clim[0])\n            self.smax = Slider(self.ax_smax, \"Max\", *clim, valinit=clim[1])\n\n            def update(val):\n                self.v.mask = False  # Re-set\n                self.v = np.ma.masked_outside(self.v.data, self.smin.val, self.smax.val)\n                # Update plots\n                self.update_xy()\n                self.update_xz()\n                self.update_zy()\n\n            self.smax.on_changed(update)\n            self.smin.on_changed(update)\n\n        elif transparent is not None:\n            # Loop over values\n            for value in transparent:\n                # If value is a list/tuple, we treat is as a range\n                if isinstance(value, (list, tuple)):\n                    self.v = np.ma.masked_inside(self.v, value[0], value[1])\n                else:  # Exact value\n                    self.v = np.ma.masked_equal(self.v, value)\n\n            # Update plots\n            self.update_xy()\n            self.update_xz()\n            self.update_zy()\n\n        # 3. Keep depth in X-Z and Z-Y in sync\n\n        def do_adjust():\n            \"\"\"Return True if z-axis in X-Z and Z-Y are different.\"\"\"\n            one = np.array(self.ax2.get_ylim())\n            two = np.array(self.ax3.get_xlim())[::-1]\n            return sum(abs(one - two)) > 0.001  # Difference at least 1 m.\n\n        def on_ylims_changed(ax):\n            \"\"\"Adjust Z-Y if X-Z changed.\"\"\"\n            if do_adjust():\n                self.ax3.set_xlim([self.ax2.get_ylim()[1], self.ax2.get_ylim()[0]])\n\n        def on_xlims_changed(ax):\n            \"\"\"Adjust X-Z if Z-Y changed.\"\"\"\n            if do_adjust():\n                self.ax2.set_ylim([self.ax3.get_xlim()[1], self.ax3.get_xlim()[0]])\n\n        self.ax3.callbacks.connect(\"xlim_changed\", on_xlims_changed)\n        self.ax2.callbacks.connect(\"ylim_changed\", on_ylims_changed)\n\n    def onscroll(self, event):\n        \"\"\"Update index and data when scrolling.\"\"\"\n        _, plt = load_matplotlib()\n\n        # Get scroll direction\n        if event.button == \"up\":\n            pm = 1\n        else:\n            pm = -1\n\n        # Update slice index depending on subplot over which mouse is\n        if event.inaxes == self.ax1:  # X-Y\n            self.zind = (self.zind + pm) % self.zc.size\n            self.update_xy()\n        elif event.inaxes == self.ax2:  # X-Z\n            if self.yx:\n                self.xind = (self.xind + pm) % self.xc.size\n            else:\n                self.yind = (self.yind + pm) % self.yc.size\n            self.update_xz()\n        elif event.inaxes == self.ax3:  # Z-Y\n            if self.yx:\n                self.yind = (self.yind + pm) % self.yc.size\n            else:\n                self.xind = (self.xind + pm) % self.xc.size\n            self.update_zy()\n\n        plt.draw()\n\n    def update_xy(self):\n        \"\"\"Update plot for change in Z-index.\"\"\"\n        # Clean up\n        self._clear_elements([\"xy_pc\", \"xz_ahw\", \"xz_ahk\", \"zy_avw\", \"zy_avk\"])\n\n        # Draw X-Y slice\n        if self.yx:\n            zdat = np.rot90(self.v[:, :, self.zind].transpose())\n            hor = self.y\n            ver = self.x\n        else:\n            zdat = self.v[:, :, self.zind].transpose()\n            hor = self.x\n            ver = self.y\n        self.xy_pc = self.ax1.pcolormesh(hor, ver, zdat, **self.pc_props)\n\n        # Draw Z-slice intersection in X-Z plot\n        self.xz_ahw = self.ax2.axhline(self.zc[self.zind], **self.clpropsw)\n        self.xz_ahk = self.ax2.axhline(self.zc[self.zind], **self.clpropsk)\n\n        # Draw Z-slice intersection in Z-Y plot\n        self.zy_avw = self.ax3.axvline(self.zc[self.zind], **self.clpropsw)\n        self.zy_avk = self.ax3.axvline(self.zc[self.zind], **self.clpropsk)\n\n    def update_xz(self):\n        \"\"\"Update plot for change in Y-index.\"\"\"\n        # Clean up\n        self._clear_elements([\"xz_pc\", \"zy_ahk\", \"zy_ahw\", \"xy_ahk\", \"xy_ahw\"])\n\n        # Draw X-Z slice\n        if self.yx:\n            ydat = self.v[-self.xind, :, :].transpose()\n            hor = self.y\n            ver = self.z\n            ind = self.xc[self.xind]\n        else:\n            ydat = self.v[:, self.yind, :].transpose()\n            hor = self.x\n            ver = self.z\n            ind = self.yc[self.yind]\n        self.xz_pc = self.ax2.pcolormesh(hor, ver, ydat, **self.pc_props)\n\n        # Draw X-slice intersection in X-Y plot\n        self.xy_ahw = self.ax1.axhline(ind, **self.clpropsw)\n        self.xy_ahk = self.ax1.axhline(ind, **self.clpropsk)\n\n        # Draw X-slice intersection in Z-Y plot\n        self.zy_ahw = self.ax3.axhline(ind, **self.clpropsw)\n        self.zy_ahk = self.ax3.axhline(ind, **self.clpropsk)\n\n    def update_zy(self):\n        \"\"\"Update plot for change in X-index.\"\"\"\n        # Clean up\n        self._clear_elements([\"zy_pc\", \"xz_avw\", \"xz_avk\", \"xy_avw\", \"xy_avk\"])\n\n        # Draw Z-Y slice\n        if self.yx:\n            xdat = np.flipud(self.v[:, self.yind, :])\n            hor = self.z\n            ver = self.x\n            ind = self.yc[self.yind]\n        else:\n            xdat = self.v[self.xind, :, :]\n            hor = self.z\n            ver = self.y\n            ind = self.xc[self.xind]\n        self.zy_pc = self.ax3.pcolormesh(hor, ver, xdat, **self.pc_props)\n\n        # Draw Y-slice intersection in X-Y plot\n        self.xy_avw = self.ax1.axvline(ind, **self.clpropsw)\n        self.xy_avk = self.ax1.axvline(ind, **self.clpropsk)\n\n        # Draw Y-slice intersection in X-Z plot\n        self.xz_avw = self.ax2.axvline(ind, **self.clpropsw)\n        self.xz_avk = self.ax2.axvline(ind, **self.clpropsk)\n\n    def _clear_elements(self, names):\n        \"\"\"Remove elements from list <names> from plot if they exists.\"\"\"\n        for element in names:\n            if hasattr(self, element):\n                getattr(self, element).remove()\n"
  },
  {
    "path": "discretize/mixins/omf_mod.py",
    "content": "\"\"\"Module for ``omf`` interaction with ``discretize``.\"\"\"\n\nimport numpy as np\nimport discretize\n\n\ndef omf():\n    \"\"\"Lazy loading omf.\"\"\"\n    import omf\n\n    return omf\n\n\ndef _ravel_data_array(arr, nx, ny, nz):\n    \"\"\"Ravel an array from discretize ordering to omf ordering.\n\n    Converts a 1D numpy array from ``discretize`` ordering (x, y, z)\n    to a flattened 1D numpy array with ``OMF`` ordering (z, y, x)\n\n    In ``discretize``, three-dimensional data are frequently organized within a\n    1D numpy array whose elements are ordered along the x-axis, then the y-axis,\n    then the z-axis. **_ravel_data_array** converts the input array\n    (discretize format) to a 1D numpy array ordered according to the open\n    mining format; which is ordered along\n    the z-axis, then the y-axis, then the x-axis.\n\n    Parameters\n    ----------\n    arr : numpy.ndarray\n        A 1D vector or nD array ordered along the x, then y, then z axes\n    nx : int\n        Number of cells along the x-axis\n    ny : int\n        Number of cells along the y-axis\n    nz : int\n        Number of cells along the z-axis\n\n    Returns\n    -------\n    numpy.ndarray (n_cells)\n        A flattened 1D array ordered according to the open mining format\n\n    Examples\n    --------\n    To demonstrate the reordering, we design a small 3D tensor mesh.\n    We print a numpy array with the xyz locations of cell the centers using the\n    original ordering (discretize). We then re-order the cell locations according to OMF.\n\n    >>> from discretize import TensorMesh\n    >>> import numpy as np\n\n    >>> hx = np.ones(4)\n    >>> hy = 2*np.ones(3)\n    >>> hz = 3*np.ones(2)\n    >>> mesh = TensorMesh([hx, hy, hz])\n\n    >>> dim = mesh.shape_cells[::-1] # OMF orderting\n    >>> xc = np.reshape(mesh.cell_centers[:, 0], dim, order=\"C\").ravel(order=\"F\")\n    >>> yc = np.reshape(mesh.cell_centers[:, 1], dim, order=\"C\").ravel(order=\"F\")\n    >>> zc = np.reshape(mesh.cell_centers[:, 2], dim, order=\"C\").ravel(order=\"F\")\n\n    >>> mesh.cell_centers\n    array([[0.5, 1. , 1.5],\n           [1.5, 1. , 1.5],\n           [2.5, 1. , 1.5],\n           [3.5, 1. , 1.5],\n           [0.5, 3. , 1.5],\n           [1.5, 3. , 1.5],\n           [2.5, 3. , 1.5],\n           [3.5, 3. , 1.5],\n           [0.5, 5. , 1.5],\n           [1.5, 5. , 1.5],\n           [2.5, 5. , 1.5],\n           [3.5, 5. , 1.5],\n           [0.5, 1. , 4.5],\n           [1.5, 1. , 4.5],\n           [2.5, 1. , 4.5],\n           [3.5, 1. , 4.5],\n           [0.5, 3. , 4.5],\n           [1.5, 3. , 4.5],\n           [2.5, 3. , 4.5],\n           [3.5, 3. , 4.5],\n           [0.5, 5. , 4.5],\n           [1.5, 5. , 4.5],\n           [2.5, 5. , 4.5],\n           [3.5, 5. , 4.5]])\n\n    >>> np.c_[xc, yc, zc]\n    array([[0.5, 1. , 1.5],\n           [0.5, 1. , 4.5],\n           [0.5, 3. , 1.5],\n           [0.5, 3. , 4.5],\n           [0.5, 5. , 1.5],\n           [0.5, 5. , 4.5],\n           [1.5, 1. , 1.5],\n           [1.5, 1. , 4.5],\n           [1.5, 3. , 1.5],\n           [1.5, 3. , 4.5],\n           [1.5, 5. , 1.5],\n           [1.5, 5. , 4.5],\n           [2.5, 1. , 1.5],\n           [2.5, 1. , 4.5],\n           [2.5, 3. , 1.5],\n           [2.5, 3. , 4.5],\n           [2.5, 5. , 1.5],\n           [2.5, 5. , 4.5],\n           [3.5, 1. , 1.5],\n           [3.5, 1. , 4.5],\n           [3.5, 3. , 1.5],\n           [3.5, 3. , 4.5],\n           [3.5, 5. , 1.5],\n           [3.5, 5. , 4.5]])\n    \"\"\"\n    dim = (nz, ny, nx)\n    return np.reshape(arr, dim, order=\"C\").ravel(order=\"F\")\n\n\ndef _unravel_data_array(arr, nx, ny, nz):\n    \"\"\"Unravel an array from omf ordering to discretize ordering.\n\n    Converts a 1D numpy array from ``OMF`` ordering (z, y, x)\n    to a flattened 1D numpy array with ``discretize`` ordering (x, y, z)\n\n    In ``OMF``, three-dimensional data are organized within a\n    1D numpy array whose elements are ordered along the z-axis, then the y-axis,\n    then the x-axis. **_unravel_data_array** converts the input array\n    (OMF format) to a 1D numpy array ordered according to ``discretize``;\n    which is ordered along the x-axis, then the y-axis, then the y-axis.\n\n    Parameters\n    ----------\n    arr : numpy.ndarray\n        A 1D vector or nD array ordered along the z, then y, then x axes\n    nx : int\n        Number of cells along the x-axis\n    ny : int\n        Number of cells along the y-axis\n    nz : int\n        Number of cells along the z-axis\n\n    Returns\n    -------\n    (n_cells) numpy.ndarray\n        A flattened 1D array ordered according to the discretize format\n\n    \"\"\"\n    dim = (nz, ny, nx)\n    return np.reshape(arr, dim, order=\"F\").ravel(order=\"C\")\n\n\nclass InterfaceOMF(object):\n    \"\"\"Convert between ``omf`` and ``discretize`` objects.\n\n    The ``InterfaceOMF`` class was designed for easy conversion between\n    ``discretize`` objects and `open mining format <https://www.seequent.com/the-open-mining-format/>`__ (OMF) objects.\n    Examples include: meshes, models and data arrays.\n    \"\"\"\n\n    def _tensor_mesh_to_omf(mesh, models=None):\n        \"\"\"Convert a TensorMesh to an omf object.\n\n        Constructs an :class:`omf.VolumeElement` object of this tensor mesh and\n        the given models as cell data of that grid.\n\n        Parameters\n        ----------\n        mesh : discretize.TensorMesh\n            The tensor mesh to convert to a :class:`omf.VolumeElement`\n        models : dict(numpy.ndarray)\n            Name('s) and array('s). Match number of cells\n        \"\"\"\n        if models is None:\n            models = {}\n        # Make the geometry\n        geometry = omf().VolumeGridGeometry()\n        # Set tensors\n        tensors = mesh.h\n        if len(tensors) < 1:\n            raise RuntimeError(\n                \"Your mesh is empty... fill it out before converting to OMF\"\n            )\n        elif len(tensors) == 1:\n            geometry.tensor_u = tensors[0]\n            geometry.tensor_v = np.array(\n                [\n                    0.0,\n                ]\n            )\n            geometry.tensor_w = np.array(\n                [\n                    0.0,\n                ]\n            )\n        elif len(tensors) == 2:\n            geometry.tensor_u = tensors[0]\n            geometry.tensor_v = tensors[1]\n            geometry.tensor_w = np.array(\n                [\n                    0.0,\n                ]\n            )\n        elif len(tensors) == 3:\n            geometry.tensor_u = tensors[0]\n            geometry.tensor_v = tensors[1]\n            geometry.tensor_w = tensors[2]\n        else:\n            raise RuntimeError(\"This mesh is too high-dimensional for OMF\")\n        # Set rotation axes\n        orientation = mesh.orientation\n        geometry.axis_u = orientation[0]\n        geometry.axis_v = orientation[1]\n        geometry.axis_w = orientation[2]\n        # Set the origin\n        geometry.origin = mesh.origin\n        # Make sure the geometry is built correctly\n        geometry.validate()\n        # Make the volume element (the OMF object)\n        omfmesh = omf().VolumeElement(\n            geometry=geometry,\n        )\n        # Add model data arrays onto the cells of the mesh\n        omfmesh.data = []\n        for name, arr in models.items():\n            data = omf().ScalarData(\n                name=name,\n                array=_ravel_data_array(arr, *mesh.shape_cells),\n                location=\"cells\",\n            )\n            omfmesh.data.append(data)\n        # Validate to make sure a proper OMF object is returned to the user\n        omfmesh.validate()\n        return omfmesh\n\n    def _tree_mesh_to_omf(mesh, models=None):\n        raise NotImplementedError(\"Not possible until OMF v2 is released.\")\n\n    def _curvilinear_mesh_to_omf(mesh, models=None):\n        raise NotImplementedError(\"Not currently possible.\")\n\n    def _cyl_mesh_to_omf(mesh, models=None):\n        raise NotImplementedError(\"Not currently possible.\")\n\n    def to_omf(mesh, models=None):\n        \"\"\"Convert to an ``omf`` data object.\n\n        Convert this mesh object to its proper ``omf`` data object with\n        the given model dictionary as the cell data of that dataset.\n\n        Parameters\n        ----------\n        models : dict of [str, (n_cells) numpy.ndarray], optional\n            Name('s) and array('s).\n\n        Returns\n        -------\n        omf.volume.VolumeElement\n        \"\"\"\n        # TODO: mesh.validate()\n        converters = {\n            # TODO: 'tree' : InterfaceOMF._tree_mesh_to_omf,\n            \"tensor\": InterfaceOMF._tensor_mesh_to_omf,\n            # TODO: 'curv' : InterfaceOMF._curvilinear_mesh_to_omf,\n            # TODO: 'CylindricalMesh' : InterfaceOMF._cyl_mesh_to_omf,\n        }\n        key = mesh._meshType.lower()\n        try:\n            convert = converters[key]\n        except KeyError:\n            raise RuntimeError(\n                \"Mesh type `{}` is not currently supported for OMF conversion.\".format(\n                    key\n                )\n            )\n        # Convert the data object\n        return convert(mesh, models=models)\n\n    @staticmethod\n    def _omf_volume_to_tensor(element):\n        \"\"\"Convert an :class:`omf.VolumeElement` to :class:`discretize.TensorMesh`.\"\"\"\n        geometry = element.geometry\n        h = [geometry.tensor_u, geometry.tensor_v, geometry.tensor_w]\n        orientation = np.array(\n            [\n                geometry.axis_u,\n                geometry.axis_v,\n                geometry.axis_w,\n            ]\n        )\n        mesh = discretize.TensorMesh(h, origin=geometry.origin, orientation=orientation)\n\n        data_dict = {}\n        for data in element.data:\n            # NOTE: this is agnostic about data location - i.e. nodes vs cells\n            data_dict[data.name] = _unravel_data_array(\n                np.array(data.array), *mesh.shape_cells\n            )\n\n        # Return TensorMesh and data dictionary\n        return mesh, data_dict\n\n    @staticmethod\n    def from_omf(element):\n        \"\"\"Convert an ``omf`` object to a ``discretize`` mesh.\n\n        Convert an OMF element to it's proper ``discretize`` type.\n        Automatically determines the output type. Returns both the mesh and a\n        dictionary of model arrays.\n\n        Parameters\n        ----------\n        element : omf.volume.VolumeElement\n            The open mining format volume element object\n\n        Returns\n        -------\n        mesh : discretize.TensorMesh\n            The returned mesh type will be appropriately based on the input `element`.\n        models : dict of [str, (n_cells) numpy.ndarray]\n            The models contained in `element`\n\n        Notes\n        -----\n        Currently only :class:discretize.TensorMesh is supported.\n        \"\"\"\n        element.validate()\n        converters = {\n            omf().VolumeElement.__name__: InterfaceOMF._omf_volume_to_tensor,\n        }\n        key = element.__class__.__name__\n        try:\n            convert = converters[key]\n        except KeyError:\n            raise RuntimeError(\n                \"OMF type `{}` is not currently supported for conversion.\".format(key)\n            )\n        # Convert the data object\n        return convert(element)\n"
  },
  {
    "path": "discretize/mixins/vtk_mod.py",
    "content": "\"\"\"Module for ``vtk`` interaction with ``discretize``.\n\nThis module provides a way for ``discretize`` meshes to be\nconverted to VTK data objects (and back when possible) if the\n`VTK Python package`_ is available.\nThe :class:`discretize.mixins.vtk_mod.InterfaceVTK` class becomes inherrited\nby all mesh objects and allows users to directly convert any given mesh by\ncalling that mesh's ``to_vtk()`` method\n(note that this method will not be available if VTK is not available).\n\n.. _`VTK Python package`: https://pypi.org/project/vtk/\n\nThis functionality was originally developed so that discretize could be\ninteroperable with PVGeo_, providing a direct interface for discretize meshes\nwithin ParaView and other VTK powered platforms. This interoperablity allows\nusers to visualize their finite volume meshes and model data from discretize\nalong side all their other georeferenced datasets in a common rendering\nenvironment.\n\n.. _PVGeo: http://pvgeo.org\n.. _pyvista: http://docs.pyvista.org\n\nAnother notable VTK powered software platforms is ``pyvista`` (see pyvista_ docs)\nwhich provides a direct interface to the VTK software library through accesible\nPython data structures and NumPy arrays::\n\n    pip install pyvista\n\nBy default, the ``to_vtk()`` method will return a ``pyvista`` data object so that\nusers can immediately start visualizing their data in 3D.\n\nSee :ref:`pyvista_demo_ref` for an example of the types of integrated\nvisualizations that are possible leveraging the link between discretize, pyvista_,\nand PVGeo_:\n\n.. image:: ../images/pyvista_laguna_del_maule.png\n   :target: http://pvgeo.org\n   :alt: PVGeo Example Visualization\n\n.. admonition:: Laguna del Maule Bouguer Gravity\n   :class: note\n\n    This data scene is was produced from the `Laguna del Maule Bouguer Gravity`_\n    example provided by Craig Miller (see Maule volcanic field, Chile. Refer to\n    Miller et al 2016 EPSL for full details.)\n\n    The rendering below shows several data sets and a model integrated together:\n\n    * `Point Data`: the Bouguer gravity anomalies\n    * Topography Surface\n    * `Inverted Model`: The model has been both sliced and thresholded for low values\n\n.. _`Laguna del Maule Bouguer Gravity`: http://docs.simpeg.xyz/content/examples/20-published/plot_laguna_del_maule_inversion.html\n\n\"\"\"\n\nimport os\nimport numpy as np\nfrom discretize.utils import cyl2cart\n\nimport warnings\n\n\ndef load_vtk(extra=None):\n    \"\"\"Lazy load principal VTK routines.\n\n    This is not beautiful. But if VTK is installed, but never used, it reduces\n    load time significantly.\n    \"\"\"\n    import vtk as _vtk\n    import vtk.util.numpy_support as _nps\n\n    if extra:\n        if isinstance(extra, str):\n            return _vtk, _nps, getattr(_vtk, extra)\n        else:\n            return _vtk, _nps, [getattr(_vtk, e) for e in extra]\n    else:\n        return _vtk, _nps\n\n\ndef assign_cell_data(vtkDS, models=None):\n    \"\"\"Assign the model(s) to the VTK dataset as ``CellData``.\n\n    Parameters\n    ----------\n    vtkDS : pyvista.Common\n        Any given VTK data object that has cell data\n    models : dict of str:numpy.ndarray\n        Name('s) and array('s). Match number of cells\n    \"\"\"\n    _, _nps = load_vtk()\n\n    nc = vtkDS.GetNumberOfCells()\n    if models is not None:\n        for name, mod in models.items():\n            # Convert numpy array\n            if mod.shape[0] != nc:\n                raise RuntimeError(\n                    'Number of model cells ({}) (first axis of model array) for \"{}\" does not match number of mesh cells ({}).'.format(\n                        mod.shape[0], name, nc\n                    )\n                )\n            vtkDoubleArr = _nps.numpy_to_vtk(mod, deep=1)\n            vtkDoubleArr.SetName(name)\n            vtkDS.GetCellData().AddArray(vtkDoubleArr)\n    return vtkDS\n\n\nclass InterfaceVTK(object):\n    \"\"\"VTK interface for ``discretize`` meshes.\n\n    Class enabling straight forward conversion between ``discretize``\n    meshes and their corresponding `VTK <https://vtk.org/doc/nightly/html/index.html>`__ or\n    `PyVista <https://docs.pyvista.org/>`__ data objects. Since ``InterfaceVTK``\n    is inherritted by the :class:`~discretize.base.BaseMesh` class, this\n    functionality can be called directly from any ``discretize`` mesh!\n    Currently this functionality is implemented for :class:`~discretize.CurvilinearMesh`,\n    :class:`~discretize.TreeMesh` and :class:`discretize.TensorMesh` classes; not\n    implemented for :class:`~discretize.CylindricalMesh`.\n\n    It should be noted that if your mesh is defined on a reference frame that is **not** the\n    traditional <X,Y,Z> system with vectors of :math:`(1,0,0)`, :math:`(0,1,0)`,\n    and :math:`(0,0,1)`, then the mesh in VTK will be rotated so that it is plotted\n    on the traditional reference frame; see examples below.\n\n    Examples\n    --------\n    The following are examples which use the VTK interface to convert\n    discretize meshes to VTK data objects and write to VTK formatted files.\n    In the first example example, a tensor mesh whose axes lie on the\n    traditional reference frame is converted to a\n    :class:`pyvista.RectilinearGrid` object.\n\n    >>> import discretize\n    >>> import numpy as np\n    >>> h1 = np.linspace(.1, .5, 3)\n    >>> h2 = np.linspace(.1, .5, 5)\n    >>> h3 = np.linspace(.1, .8, 3)\n    >>> mesh = discretize.TensorMesh([h1, h2, h3])\n\n    Get a VTK data object\n\n    >>> dataset = mesh.to_vtk()\n\n    Save this mesh to a VTK file\n\n    >>> mesh.write_vtk('sample_mesh')\n\n    Here, the reference frame of the mesh is rotated. In this case, conversion\n    to VTK produces a :class:`pyvista.StructuredGrid` object.\n\n    >>> axis_u = (1,-1,0)\n    >>> axis_v = (-1,-1,0)\n    >>> axis_w = (0,0,1)\n    >>> mesh.orientation = np.array([\n    ...    axis_u,\n    ...    axis_v,\n    ...    axis_w\n    ... ])\n\n    Yield the rotated vtkStructuredGrid\n\n    >>> dataset_r = mesh.to_vtk()\n\n    or write it out to a VTK format\n\n    >>> mesh.write_vtk('sample_rotated')\n\n    The two above code snippets produced a :class:`pyvista.RectilinearGrid` and a\n    :class:`pyvista.StructuredGrid` respecitvely. To demonstarte the difference, we\n    have plotted the two datasets next to each other where the first mesh is in\n    green and its data axes are parrallel to the traditional cartesian reference\n    frame. The second, rotated mesh is shown in red and its data axes are\n    rotated from the traditional Cartesian reference frame as specified by the\n    *orientation* property.\n\n    >>> import pyvista\n    >>> pyvista.set_plot_theme('document')\n\n    >>> p = pyvista.BackgroundPlotter()\n    >>> p.add_mesh(dataset, color='green', show_edges=True)\n    >>> p.add_mesh(dataset_r, color='maroon', show_edges=True)\n    >>> p.show_grid()\n    >>> p.screenshot('vtk-rotated-example.png')\n\n    .. image:: ../../images/vtk-rotated-example.png\n\n    \"\"\"\n\n    def __tree_mesh_to_vtk(mesh, models=None):\n        \"\"\"Convert the TreeMesh to a vtk object.\n\n        Constructs a :class:`pyvista.UnstructuredGrid` object of this tree mesh and\n        the given models as ``cell_arrays`` of that ``pyvista`` dataset.\n\n        Parameters\n        ----------\n        mesh : discretize.TreeMesh\n            The tree mesh to convert to a :class:`pyvista.UnstructuredGrid`\n        models : dict(numpy.ndarray)\n            Name('s) and array('s). Match number of cells\n\n        \"\"\"\n        _vtk, _nps = load_vtk()\n\n        # Make the data parts for the vtu object\n        # Points\n        nodes = mesh.total_nodes\n\n        # Adjust if result was 2D (voxels are pixels in 2D):\n        VTK_CELL_TYPE = _vtk.VTK_VOXEL if mesh.dim == 3 else _vtk.VTK_PIXEL\n\n        # Rotate the points to the cartesian system\n        nodes = np.dot(nodes, mesh.rotation_matrix)\n        if mesh.dim == 2:\n            nodes = np.pad(nodes, ((0, 0), (0, 1)))\n\n        # Grab the points\n        vtkPts = _vtk.vtkPoints()\n        vtkPts.SetData(_nps.numpy_to_vtk(nodes, deep=True))\n\n        # Cells\n        cellConn = mesh.cell_nodes\n        cellsMat = np.concatenate(\n            (np.ones((cellConn.shape[0], 1), dtype=int) * cellConn.shape[1], cellConn),\n            axis=1,\n        ).ravel()\n        cellsArr = _vtk.vtkCellArray()\n        cellsArr.SetNumberOfCells(cellConn.shape[0])\n        cellsArr.SetCells(\n            cellConn.shape[0],\n            _nps.numpy_to_vtk(cellsMat, deep=True, array_type=_vtk.VTK_ID_TYPE),\n        )\n        # Make the object\n        output = _vtk.vtkUnstructuredGrid()\n        output.SetPoints(vtkPts)\n        output.SetCells(VTK_CELL_TYPE, cellsArr)\n        # Add the level of refinement as a cell array\n        cell_levels = mesh._cell_levels_by_indexes()\n        refineLevelArr = _nps.numpy_to_vtk(cell_levels, deep=1)\n        refineLevelArr.SetName(\"octreeLevel\")\n        output.GetCellData().AddArray(refineLevelArr)\n        ubc_order = mesh._ubc_order\n        # order_ubc will re-order from treemesh ordering to UBC ordering\n        # need the opposite operation\n        un_order = np.empty_like(ubc_order)\n        un_order[ubc_order] = np.arange(len(ubc_order))\n        order = _nps.numpy_to_vtk(un_order)\n        order.SetName(\"index_cell_corner\")\n        output.GetCellData().AddArray(order)\n        # Assign the model('s) to the object\n        return assign_cell_data(output, models=models)\n\n    def __simplex_mesh_to_vtk(mesh, models=None):\n        \"\"\"Convert the SimplexMesh to a vtk object.\n\n        Constructs a :class:`pyvista.UnstructuredGrid` object of this simplex mesh and\n        the given models as ``cell_arrays`` of that ``pyvista`` dataset.\n\n        Parameters\n        ----------\n        mesh : discretize.SimplexMesh\n            The simplex mesh to convert to a :class:`pyvista.UnstructuredGrid`\n\n        models : dict(numpy.ndarray)\n            Name('s) and array('s). Match number of cells\n        \"\"\"\n        _vtk, _nps = load_vtk()\n\n        # Make the data parts for the vtu object\n        # Points\n        pts = mesh.nodes\n        if mesh.dim == 2:\n            cell_type = _vtk.VTK_TRIANGLE\n            pts = np.c_[pts, np.zeros(mesh.n_nodes)]\n        elif mesh.dim == 3:\n            cell_type = _vtk.VTK_TETRA\n        vtk_pts = _vtk.vtkPoints()\n        vtk_pts.SetData(_nps.numpy_to_vtk(pts, deep=True))\n\n        cell_con_array = np.c_[np.full(mesh.n_cells, mesh.dim + 1), mesh.simplices]\n        cells = _vtk.vtkCellArray()\n        cells.SetNumberOfCells(mesh.n_cells)\n        cells.SetCells(\n            mesh.n_cells,\n            _nps.numpy_to_vtk(\n                cell_con_array.reshape(-1), deep=True, array_type=_vtk.VTK_ID_TYPE\n            ),\n        )\n\n        output = _vtk.vtkUnstructuredGrid()\n        output.SetPoints(vtk_pts)\n        output.SetCells(cell_type, cells)\n        # Assign the model('s) to the object\n        return assign_cell_data(output, models=models)\n\n    @staticmethod\n    def __create_structured_grid(ptsMat, dims, models=None):\n        \"\"\"Build a structured grid (an internal helper).\"\"\"\n        _vtk, _nps = load_vtk()\n\n        # Adjust if result was 2D:\n        if ptsMat.shape[1] == 2:\n            # Figure out which dim is null\n            nullDim = dims.index(None)\n            ptsMat = np.insert(ptsMat, nullDim, np.zeros(ptsMat.shape[0]), axis=1)\n        if ptsMat.shape[1] != 3:\n            raise RuntimeError(\"Points of the mesh are improperly defined.\")\n        # Convert the points\n        vtkPts = _vtk.vtkPoints()\n        vtkPts.SetData(_nps.numpy_to_vtk(ptsMat, deep=True))\n        # Uncover hidden dimension\n        dims = tuple(0 if dim is None else dim + 1 for dim in dims)\n        output = _vtk.vtkStructuredGrid()\n        output.SetDimensions(dims[0], dims[1], dims[2])  # note this subtracts 1\n        output.SetPoints(vtkPts)\n        # Assign the model('s) to the object\n        return assign_cell_data(output, models=models)\n\n    def __get_rotated_nodes(mesh):\n        \"\"\"Rotate mesh nodes (a helper routine).\"\"\"\n        nodes = mesh.gridN\n        if mesh.dim == 1:\n            nodes = np.c_[mesh.gridN, np.zeros((mesh.nN, 2))]\n        elif mesh.dim == 2:\n            nodes = np.c_[mesh.gridN, np.zeros((mesh.nN, 1))]\n        # Now garuntee nodes are correct\n        if nodes.shape != (mesh.nN, 3):\n            raise RuntimeError(\"Nodes of the grid are improperly defined.\")\n        # Rotate the points based on the axis orientations\n        return np.dot(nodes, mesh.rotation_matrix)\n\n    def __tensor_mesh_to_vtk(mesh, models=None):\n        \"\"\"Convert the TensorMesh to a vtk object.\n\n        Constructs a :class:`pyvista.RectilinearGrid`\n        (or a :class:`pyvista.StructuredGrid`) object of this tensor mesh and the\n        given models as ``cell_arrays`` of that grid.\n        If the mesh is defined on a normal cartesian system then a rectilinear\n        grid is generated. Otherwise, a structured grid is generated.\n\n        Parameters\n        ----------\n        mesh : discretize.TensorMesh\n            The tensor mesh to convert to a :class:`pyvista.RectilinearGrid`\n\n        models : dict(numpy.ndarray)\n            Name('s) and array('s). Match number of cells\n        \"\"\"\n        _vtk, _nps = load_vtk()\n\n        # Deal with dimensionalities\n        if mesh.dim >= 1:\n            vX = mesh.nodes_x\n            xD = len(vX)\n            yD, zD = 1, 1\n            vY, vZ = np.array([0, 0])\n        if mesh.dim >= 2:\n            vY = mesh.nodes_y\n            yD = len(vY)\n        if mesh.dim == 3:\n            vZ = mesh.nodes_z\n            zD = len(vZ)\n        # If axis orientations are standard then use a vtkRectilinearGrid\n        if not mesh.reference_is_rotated:\n            # Use rectilinear VTK grid.\n            # Assign the spatial information.\n            output = _vtk.vtkRectilinearGrid()\n            output.SetDimensions(xD, yD, zD)\n            output.SetXCoordinates(_nps.numpy_to_vtk(vX, deep=1))\n            output.SetYCoordinates(_nps.numpy_to_vtk(vY, deep=1))\n            output.SetZCoordinates(_nps.numpy_to_vtk(vZ, deep=1))\n            return assign_cell_data(output, models=models)\n        # Use a structured grid where points are rotated to the cartesian system\n        ptsMat = InterfaceVTK.__get_rotated_nodes(mesh)\n        # Assign the model('s) to the object\n        return InterfaceVTK.__create_structured_grid(\n            ptsMat, mesh.shape_cells, models=models\n        )\n\n    def __curvilinear_mesh_to_vtk(mesh, models=None):\n        \"\"\"Convert the CurvilinearMesh to a vtk object.\n\n        Constructs a :class:`pyvista.StructuredGrid` of this mesh and the given\n        models as ``cell_arrays`` of that object.\n\n        Parameters\n        ----------\n        mesh : discretize.CurvilinearMesh\n            The curvilinear mesh to convert to a :class:`pyvista.StructuredGrid`\n        models : dict(numpy.ndarray)\n            Name('s) and array('s). Match number of cells\n        \"\"\"\n        ptsMat = InterfaceVTK.__get_rotated_nodes(mesh)\n        return InterfaceVTK.__create_structured_grid(\n            ptsMat, mesh.shape_cells, models=models\n        )\n\n    def __cyl_mesh_to_vtk(mesh, models=None):\n        \"\"\"Convert the CylindricalMesh to a vtk object.\n\n        Constructs an vtkUnstructuredGrid made of rational Bezier hexahedrons and wedges.\n        Wedges happen on the very internal layer about r=0, and hexes occur elsewhere.\n        \"\"\"\n        if np.any(mesh.h[1] >= np.pi):\n            raise NotImplementedError(\n                \"Exporting cylindrical meshes to vtk with angles larger than 180 degrees\"\n                \" is not yet supported.\"\n            )\n        # # Points\n        deflate_nodes = mesh._n_total_nodes != mesh.n_nodes\n        if deflate_nodes:\n            inds = np.empty(mesh._n_total_nodes, dtype=int)\n            is_hanging_nodes = mesh._ishanging_nodes\n            inds[~is_hanging_nodes] = np.arange(mesh.n_nodes)\n            inds[is_hanging_nodes] = list(mesh._hanging_nodes.values())\n\n        # calculate control points\n        dphis_half = mesh.h[1] / 2\n        if mesh.is_wrapped:\n            phi_controls = mesh.nodes_y + dphis_half\n        else:\n            phi_controls = mesh.nodes_y[:-1] + dphis_half\n        if mesh.nodes_x[0] == 0.0:\n            Rs, Phis, Zs = np.meshgrid(\n                mesh.nodes_x[1:], phi_controls, mesh.nodes_z, indexing=\"ij\"\n            )\n        else:\n            Rs, Phis, Zs = np.meshgrid(\n                mesh.nodes_x, phi_controls, mesh.nodes_z, indexing=\"ij\"\n            )\n        Rs /= np.cos(dphis_half)[None, :, None]\n\n        control_nodes = np.c_[\n            Rs.reshape(-1, order=\"F\"),\n            Phis.reshape(-1, order=\"F\"),\n            Zs.reshape(-1, order=\"F\"),\n        ]\n\n        cells = np.arange(mesh.n_cells).reshape(mesh.shape_cells, order=\"F\")\n        if mesh.includes_zero:\n            wedge_cells = cells[0].reshape(-1, order=\"F\")\n            hex_cells = (cells[1:]).reshape(-1, order=\"F\")\n        else:\n            hex_cells = cells.reshape(-1, order=\"F\")\n\n        # Hex Cells...\n        # calculate indices\n        ir, it, iz = np.unravel_index(hex_cells, shape=mesh.shape_cells, order=\"F\")\n\n        irs = np.stack([ir, ir, ir + 1, ir + 1, ir, ir, ir + 1, ir + 1], axis=-1)\n        its = np.stack([it + 1, it, it, it + 1, it + 1, it, it, it + 1], axis=-1)\n        izs = np.stack([iz, iz, iz, iz, iz + 1, iz + 1, iz + 1, iz + 1], axis=-1)\n        i_hex_nodes = np.ravel_multi_index(\n            (irs, its, izs), mesh._shape_total_nodes, order=\"F\"\n        )\n        if deflate_nodes:\n            i_hex_nodes = inds[i_hex_nodes]\n\n        if mesh.includes_zero:\n            irs = np.stack([ir - 1, ir, ir - 1, ir], axis=-1)\n        else:\n            irs = np.stack([ir, ir + 1, ir, ir + 1], axis=-1)\n        its = np.stack([it, it, it, it], axis=-1)\n        izs = np.stack([iz, iz, iz + 1, iz + 1], axis=-1)\n        i_hex_control_nodes = np.ravel_multi_index((irs, its, izs), Rs.shape, order=\"F\")\n\n        if mesh.includes_zero:\n            # Wedge Cells nodes\n            # put control points along radial edge for halfway points on the edges...\n            Phis, Zs = np.meshgrid(mesh.nodes_y, mesh.nodes_z, indexing=\"ij\")\n            Rhalfs = np.full_like(Phis, mesh.nodes_x[1] * 0.5)\n            wedge_control_nodes = np.c_[\n                Rhalfs.reshape(-1, order=\"F\"),\n                Phis.reshape(-1, order=\"F\"),\n                Zs.reshape(-1, order=\"F\"),\n            ]\n\n            # indices for wedge nodes: for cell 0\n            ir, it, iz = np.unravel_index(\n                wedge_cells, shape=mesh.shape_cells, order=\"F\"\n            )\n            irs = np.stack([ir, ir + 1, ir + 1, ir, ir + 1, ir + 1], axis=-1)\n            its = np.stack([it, it, it + 1, it, it, it + 1], axis=-1)\n            izs = np.stack([iz, iz, iz, iz + 1, iz + 1, iz + 1], axis=-1)\n            i_wedge_nodes = np.ravel_multi_index(\n                (irs, its, izs), mesh._shape_total_nodes, order=\"F\"\n            )\n            if deflate_nodes:\n                i_wedge_nodes = inds[i_wedge_nodes]\n\n            its = np.stack([it, it + 1, it, it + 1], axis=-1)\n            izs = np.stack([iz, iz, iz + 1, iz + 1], axis=-1)\n            # wrap mode for the duplicated wedge control nodes\n            i_wscn = np.ravel_multi_index(\n                (its, izs), Rhalfs.shape, order=\"F\", mode=\"wrap\"\n            ) + len(control_nodes)\n\n            irs = np.stack([ir, ir], axis=-1)\n            its = np.stack([it, it], axis=-1)\n            izs = np.stack([iz, iz + 1], axis=-1)\n            i_wccn = np.ravel_multi_index((irs, its, izs), Rs.shape, order=\"F\")\n            i_wedge_control_nodes = np.c_[\n                i_wscn[:, 0],\n                i_wccn[:, 0],\n                i_wscn[:, 1],\n                i_wscn[:, 2],\n                i_wccn[:, 1],\n                i_wscn[:, 3],\n            ]\n\n        _vtk, _nps = load_vtk()\n        ####\n        # assemble cells\n        cell_types = np.empty(mesh.n_cells, dtype=np.uint8)\n        cell_types[hex_cells] = _vtk.VTK_BEZIER_HEXAHEDRON\n\n        cell_con = np.empty((mesh.n_cells, 13), dtype=int)\n        cell_con[:, 0] = 12\n        cell_con[hex_cells, 1:9] = i_hex_nodes\n        cell_con[hex_cells, 9:] = i_hex_control_nodes + mesh.n_nodes\n        nodes_cyl = np.r_[mesh.nodes, control_nodes]\n\n        # calculate weights for control points\n        if mesh.includes_zero:\n            control_weights = (\n                np.sin(np.pi / 2 - dphis_half)[None, :, None]\n                * np.ones((mesh.shape_nodes[0] - 1, mesh.shape_nodes[2]))[:, None, :]\n            )\n        else:\n            control_weights = (\n                np.sin(np.pi / 2 - dphis_half)[None, :, None]\n                * np.ones((mesh.shape_nodes[0], mesh.shape_nodes[2]))[:, None, :]\n            )\n\n        rational_weights = np.r_[\n            np.ones(mesh.n_nodes), control_weights.reshape(-1, order=\"F\")\n        ]\n\n        higher_order_degrees = np.empty((mesh.n_cells, 3))\n        higher_order_degrees[hex_cells, :] = [2, 1, 1]\n\n        if mesh.includes_zero:\n            cell_types[wedge_cells] = _vtk.VTK_BEZIER_WEDGE\n            cell_con[wedge_cells, 1:7] = i_wedge_nodes\n            cell_con[wedge_cells, 7:] = i_wedge_control_nodes + mesh.n_nodes\n            nodes_cyl = np.r_[nodes_cyl, wedge_control_nodes]\n            rational_weights = np.r_[\n                rational_weights, np.ones(len(wedge_control_nodes))\n            ]\n            higher_order_degrees[wedge_cells] = [2, 2, 1]\n\n        nodes = cyl2cart(nodes_cyl)\n\n        vtk_pts = _vtk.vtkPoints()\n        vtk_pts.SetData(_nps.numpy_to_vtk(nodes, deep=True))\n\n        cells = _vtk.vtkCellArray()\n        cells.SetNumberOfCells(mesh.n_cells)\n        cells.SetCells(\n            mesh.n_cells,\n            _nps.numpy_to_vtk(\n                cell_con.reshape(-1), deep=True, array_type=_vtk.VTK_ID_TYPE\n            ),\n        )\n        cell_types = _nps.numpy_to_vtk(cell_types, deep=True)\n\n        output = _vtk.vtkUnstructuredGrid()\n        output.SetPoints(vtk_pts)\n        output.SetCells(cell_types, cells)\n\n        vtk_rational_weights = _nps.numpy_to_vtk(rational_weights)\n        vtk_higher_order_degrees = _nps.numpy_to_vtk(higher_order_degrees)\n\n        output.GetPointData().SetRationalWeights(vtk_rational_weights)\n        output.GetCellData().SetHigherOrderDegrees(vtk_higher_order_degrees)\n        # Assign the model('s) to the object\n        return assign_cell_data(output, models=models)\n\n    def to_vtk(mesh, models=None):\n        \"\"\"Convert mesh (and models) to corresponding VTK or PyVista data object.\n\n        This method converts a ``discretize`` mesh (and associated models) to its\n        corresponding `VTK <https://vtk.org/doc/nightly/html/index.html>`__ or\n        `PyVista <https://docs.pyvista.org/>`__ data object.\n\n        Parameters\n        ----------\n        models : dict of [str, (n_cells) numpy.ndarray], optional\n            Models are supplied as a dictionary where the keys are the model\n            names. Each model is a 1D :class:`numpy.ndarray` of size (n_cells).\n\n        Returns\n        -------\n        pyvista.UnstructuredGrid, pyvista.RectilinearGrid or pyvista.StructuredGrid\n            The corresponding VTK or PyVista data object for the mesh and its models\n        \"\"\"\n        converters = {\n            \"tree\": InterfaceVTK.__tree_mesh_to_vtk,\n            \"tensor\": InterfaceVTK.__tensor_mesh_to_vtk,\n            \"curv\": InterfaceVTK.__curvilinear_mesh_to_vtk,\n            \"simplex\": InterfaceVTK.__simplex_mesh_to_vtk,\n            \"cyl\": InterfaceVTK.__cyl_mesh_to_vtk,\n        }\n        key = mesh._meshType.lower()\n        try:\n            convert = converters[key]\n        except KeyError:\n            raise RuntimeError(\n                \"Mesh type `{}` is not currently supported for VTK conversion.\".format(\n                    key\n                )\n            )\n        # Convert the data object then attempt a wrapping with `pyvista`\n        cvtd = convert(mesh, models=models)\n        try:\n            import pyvista\n\n            cvtd = pyvista.wrap(cvtd)\n        except ImportError:\n            warnings.warn(\n                \"For easier use of VTK objects, you should install `pyvista` (the VTK interface): pip install pyvista\",\n                stacklevel=1,\n            )\n        return cvtd\n\n    def toVTK(mesh, models=None):\n        \"\"\"Convert mesh (and models) to corresponding VTK or PyVista data object.\n\n        *toVTK* has been deprecated and replaced by *to_vtk*.\n\n        See Also\n        --------\n        to_vtk\n        \"\"\"\n        warnings.warn(\n            \"Deprecation Warning: `toVTK` is deprecated, use `to_vtk` instead\",\n            category=FutureWarning,\n            stacklevel=2,\n        )\n        return InterfaceVTK.to_vtk(mesh, models=models)\n\n    @staticmethod\n    def _save_unstructured_grid(file_name, vtkUnstructGrid, directory=\"\"):\n        \"\"\"Save an unstructured grid to a vtk file.\n\n        Saves a VTK unstructured grid file (vtu) for an already generated\n        :class:`pyvista.UnstructuredGrid` object.\n\n        Parameters\n        ----------\n        file_name : str\n            path to the output vtk file or just its name if directory is specified\n        directory : str\n            directory where the UBC GIF file lives\n        \"\"\"\n        _vtk, _, _vtkUnstWriter = load_vtk(\"vtkXMLUnstructuredGridWriter\")\n\n        if not isinstance(vtkUnstructGrid, _vtk.vtkUnstructuredGrid):\n            raise RuntimeError(\n                \"`_save_unstructured_grid` can only handle `vtkUnstructuredGrid` objects. `%s` is not supported.\"\n                % vtkUnstructGrid.__class__\n            )\n        # Check the extension of the file_name\n        fname = os.path.join(directory, file_name)\n        ext = os.path.splitext(fname)[1]\n        if ext == \"\":\n            fname = fname + \".vtu\"\n        elif ext not in \".vtu\":\n            raise IOError(\"{:s} is an incorrect extension, has to be .vtu\".format(ext))\n        # Make the writer\n        vtuWriteFilter = _vtkUnstWriter()\n        vtuWriteFilter.SetDataModeToBinary()\n        vtuWriteFilter.SetInputDataObject(vtkUnstructGrid)\n        vtuWriteFilter.SetFileName(fname)\n        # Write the file\n        vtuWriteFilter.Write()\n\n    @staticmethod\n    def _save_structured_grid(file_name, vtkStructGrid, directory=\"\"):\n        \"\"\"Save a structured grid to a vtk file.\n\n        Saves a VTK structured grid file (vtk) for an already generated\n        :class:`pyvista.StructuredGrid` object.\n\n        Parameters\n        ----------\n        file_name : str\n            path to the output vtk file or just its name if directory is specified\n        directory : str\n            directory where the UBC GIF file lives\n        \"\"\"\n        _vtk, _, _vtkStrucWriter = load_vtk(\"vtkXMLStructuredGridWriter\")\n\n        if not isinstance(vtkStructGrid, _vtk.vtkStructuredGrid):\n            raise RuntimeError(\n                \"`_save_structured_grid` can only handle `vtkStructuredGrid` objects. `{}` is not supported.\".format(\n                    vtkStructGrid.__class__\n                )\n            )\n        # Check the extension of the file_name\n        fname = os.path.join(directory, file_name)\n        ext = os.path.splitext(fname)[1]\n        if ext == \"\":\n            fname = fname + \".vts\"\n        elif ext not in \".vts\":\n            raise IOError(\"{:s} is an incorrect extension, has to be .vts\".format(ext))\n        # Make the writer\n        writer = _vtkStrucWriter()\n        writer.SetDataModeToBinary()\n        writer.SetInputDataObject(vtkStructGrid)\n        writer.SetFileName(fname)\n        # Write the file\n        writer.Write()\n\n    @staticmethod\n    def _save_rectilinear_grid(file_name, vtkRectGrid, directory=\"\"):\n        \"\"\"Save a rectilinear grid to a vtk file.\n\n        Saves a VTK rectilinear file (vtr) ffor an already generated\n        :class:`pyvista.RectilinearGrid` object.\n\n        Parameters\n        ----------\n        file_name : str\n            path to the output vtk file or just its name if directory is specified\n        directory : str\n            directory where the UBC GIF file lives\n        \"\"\"\n        _vtk, _, _vtkRectWriter = load_vtk(\"vtkXMLRectilinearGridWriter\")\n\n        if not isinstance(vtkRectGrid, _vtk.vtkRectilinearGrid):\n            raise RuntimeError(\n                \"`_save_rectilinear_grid` can only handle `vtkRectilinearGrid` objects. `{}` is not supported.\".format(\n                    vtkRectGrid.__class__\n                )\n            )\n        # Check the extension of the file_name\n        fname = os.path.join(directory, file_name)\n        ext = os.path.splitext(fname)[1]\n        if ext == \"\":\n            fname = fname + \".vtr\"\n        elif ext not in \".vtr\":\n            raise IOError(\"{:s} is an incorrect extension, has to be .vtr\".format(ext))\n        # Write the file.\n        vtrWriteFilter = _vtkRectWriter()\n        vtrWriteFilter.SetDataModeToBinary()\n        vtrWriteFilter.SetFileName(fname)\n        vtrWriteFilter.SetInputDataObject(vtkRectGrid)\n        vtrWriteFilter.Write()\n\n    def write_vtk(mesh, file_name, models=None, directory=\"\"):\n        \"\"\"Convert mesh (and models) to corresponding VTK or PyVista data object then writes to file.\n\n        This method converts a ``discretize`` mesh (and associated models) to its\n        corresponding `VTK <https://vtk.org/doc/nightly/html/index.html>`__ or\n        `PyVista <https://docs.pyvista.org/>`__ data object, then writes to file.\n        The output structure will be one of: ``vtkUnstructuredGrid``,\n        ``vtkRectilinearGrid`` or ``vtkStructuredGrid``.\n\n        Parameters\n        ----------\n        file_name : str or file name\n            Full path for the output file or just its name if directory is specified\n        models : dict of [str, (n_cells) numpy.ndarray], optional\n            Models are supplied as a dictionary where the keys are the model\n            names. Each model is a 1D :class:`numpy.ndarray` of size (n_cells).\n        directory : str, optional\n            output directory\n\n        Returns\n        -------\n        str\n            The output of Python's *write* function\n        \"\"\"\n        vtkObj = InterfaceVTK.to_vtk(mesh, models=models)\n        writers = {\n            \"vtkUnstructuredGrid\": InterfaceVTK._save_unstructured_grid,\n            \"vtkRectilinearGrid\": InterfaceVTK._save_rectilinear_grid,\n            \"vtkStructuredGrid\": InterfaceVTK._save_structured_grid,\n        }\n        key = vtkObj.GetClassName()\n        try:\n            write = writers[key]\n        except KeyError:\n            raise RuntimeError(\"VTK data type `%s` is not currently supported.\" % key)\n        return write(file_name, vtkObj, directory=directory)\n\n    def writeVTK(mesh, file_name, models=None, directory=\"\"):\n        \"\"\"Convert mesh (and models) to corresponding VTK or PyVista data object then writes to file.\n\n        *writeVTK* has been deprecated and replaced by *write_vtk*\n\n        See Also\n        --------\n        write_vtk\n        \"\"\"\n        warnings.warn(\n            \"Deprecation Warning: `writeVTK` is deprecated, use `write_vtk` instead\",\n            category=FutureWarning,\n            stacklevel=2,\n        )\n        return InterfaceVTK.write_vtk(\n            mesh, file_name, models=models, directory=directory\n        )\n\n\nclass InterfaceTensorread_vtk(object):\n    \"\"\"Mixin class for converting vtk to TensorMesh.\n\n    This class provides convenient methods for converting VTK Rectilinear Grid\n    files/objects to :class:`~discretize.TensorMesh` objects.\n    \"\"\"\n\n    @classmethod\n    def vtk_to_tensor_mesh(TensorMesh, vtrGrid):\n        \"\"\"Convert vtk object to a TensorMesh.\n\n        Convert ``vtkRectilinearGrid`` or :class:`~pyvista.RectilinearGrid` object\n        to a :class:`~discretize.TensorMesh` object.\n\n        Parameters\n        ----------\n        vtuGrid : ``vtkRectilinearGrid`` or :class:`~pyvista.RectilinearGrid`\n            A VTK or PyVista rectilinear grid object\n\n        Returns\n        -------\n        discretize.TensorMesh\n            A discretize tensor mesh\n        \"\"\"\n        _, _nps = load_vtk()\n\n        # Sort information\n        hx = np.abs(np.diff(_nps.vtk_to_numpy(vtrGrid.GetXCoordinates())))\n        xR = _nps.vtk_to_numpy(vtrGrid.GetXCoordinates())[0]\n        hy = np.abs(np.diff(_nps.vtk_to_numpy(vtrGrid.GetYCoordinates())))\n        yR = _nps.vtk_to_numpy(vtrGrid.GetYCoordinates())[0]\n        zD = np.diff(_nps.vtk_to_numpy(vtrGrid.GetZCoordinates()))\n        # Check the direction of hz\n        if np.all(zD < 0):\n            hz = np.abs(zD[::-1])\n            zR = _nps.vtk_to_numpy(vtrGrid.GetZCoordinates())[-1]\n        else:\n            hz = np.abs(zD)\n            zR = _nps.vtk_to_numpy(vtrGrid.GetZCoordinates())[0]\n        origin = np.array([xR, yR, zR])\n\n        # Make the object\n        tensMsh = TensorMesh([hx, hy, hz], origin=origin)\n\n        # Grap the models\n        models = {}\n        for i in np.arange(vtrGrid.GetCellData().GetNumberOfArrays()):\n            modelName = vtrGrid.GetCellData().GetArrayName(i)\n            if np.all(zD < 0):\n                modFlip = _nps.vtk_to_numpy(vtrGrid.GetCellData().GetArray(i))\n                tM = tensMsh.reshape(modFlip, \"CC\", \"CC\", \"M\")\n                modArr = tensMsh.reshape(tM[:, :, ::-1], \"CC\", \"CC\", \"V\")\n            else:\n                modArr = _nps.vtk_to_numpy(vtrGrid.GetCellData().GetArray(i))\n            models[modelName] = modArr\n\n        # Return the data\n        return tensMsh, models\n\n    @classmethod\n    def read_vtk(TensorMesh, file_name, directory=\"\"):\n        \"\"\"Read VTK rectilinear file (vtr or xml) and return a discretize tensor mesh (and models).\n\n        This method reads a VTK rectilinear file (vtr or xml format) and returns\n        a tuple containing the :class:`~discretize.TensorMesh` as well as a dictionary\n        containing any models. The keys in the model dictionary correspond to the file\n        names of the models.\n\n        Parameters\n        ----------\n        file_name : str\n            full path to the VTK rectilinear file (vtr or xml) containing the mesh (and\n            models) or just the file name if the directory is specified.\n        directory : str, optional\n            directory where the file lives\n\n        Returns\n        -------\n        mesh : discretize.TensorMesh\n            The tensor mesh object.\n        model_dict : dict of [str, (n_cells) numpy.ndarray]\n            A dictionary containing the models. The keys correspond to the names of the\n            models.\n        \"\"\"\n        _, _, _vtkRectReader = load_vtk(\"vtkXMLRectilinearGridReader\")\n        fname = os.path.join(directory, file_name)\n        # Read the file\n        vtrReader = _vtkRectReader()\n        vtrReader.SetFileName(fname)\n        vtrReader.Update()\n        vtrGrid = vtrReader.GetOutput()\n        return TensorMesh.vtk_to_tensor_mesh(vtrGrid)\n\n    @classmethod\n    def readVTK(TensorMesh, file_name, directory=\"\"):\n        \"\"\"Read VTK rectilinear file (vtr or xml) and return a discretize tensor mesh (and models).\n\n        *readVTK* has been deprecated and replaced by *read_vtk*\n\n        See Also\n        --------\n        read_vtk\n        \"\"\"\n        warnings.warn(\n            \"Deprecation Warning: `readVTK` is deprecated, use `read_vtk` instead\",\n            category=FutureWarning,\n            stacklevel=2,\n        )\n        return InterfaceTensorread_vtk.read_vtk(\n            TensorMesh, file_name, directory=directory\n        )\n\n\nclass InterfaceSimplexReadVTK:\n    \"\"\"Mixin class for converting vtk to SimplexMesh.\n\n    This class provides convenient methods for converting VTK Unstructured Grid\n    files/objects to :class:`~discretize.SimplexMesh` objects.\n    \"\"\"\n\n    @classmethod\n    def vtk_to_simplex_mesh(SimplexMesh, vtuGrid):\n        \"\"\"Convert an unstructured grid of simplices to a SimplexMesh.\n\n        Convert ``vtkUnstructuredGrid`` or :class:`~pyvista.UnstructuredGrid` object\n        to a :class:`~discretize.SimplexMesh` object.\n\n        Parameters\n        ----------\n        vtrGrid : ``vtkUnstructuredGrid`` or :class:`~pyvista.UnstructuredGrid`\n            A VTK or PyVista unstructured grid object\n\n        Returns\n        -------\n        discretize.SimplexMesh\n            A discretize tensor mesh\n        \"\"\"\n        _, _nps = load_vtk()\n\n        # check if all of the cells are the same type\n        cell_types = np.unique(_nps.vtk_to_numpy(vtuGrid.GetCellTypesArray()))\n        if len(cell_types) > 1:\n            raise ValueError(\n                \"Incompatible unstructured grid. All cell's must have the same type.\"\n            )\n        if cell_types[0] not in [5, 10]:\n            raise ValueError(\"Cell types must be either triangular or tetrahedral\")\n        if cell_types[0] == 5:\n            dim = 2\n        else:\n            dim = 3\n        # then should be safe to move forward\n        simplices = _nps.vtk_to_numpy(\n            vtuGrid.GetCells().GetConnectivityArray()\n        ).reshape(-1, dim + 1)\n        points = _nps.vtk_to_numpy(vtuGrid.GetPoints().GetData())\n        if dim == 2:\n            points = points[:, :-1]\n\n        mesh = SimplexMesh(points, simplices)\n\n        # Grap the models\n        models = {}\n        for i in np.arange(vtuGrid.GetCellData().GetNumberOfArrays()):\n            modelName = vtuGrid.GetCellData().GetArrayName(i)\n            modArr = _nps.vtk_to_numpy(vtuGrid.GetCellData().GetArray(i))\n            models[modelName] = modArr\n\n        # Return the data\n        return mesh, models\n\n    @classmethod\n    def read_vtk(SimplexMesh, file_name, directory=\"\"):\n        \"\"\"Read VTK unstructured file (vtu or xml) and return a discretize simplex mesh (and models).\n\n        This method reads a VTK unstructured file (vtu or xml format) and returns\n        the :class:`~discretize.SimplexMesh` as well as a dictionary containing any\n        models. The keys in the model dictionary correspond to the file names of the\n        models.\n\n        Parameters\n        ----------\n        file_name : str\n            full path to the VTK unstructured file (vtr or xml) containing the mesh (and\n            models) or just the file name if the directory is specified.\n        directory : str, optional\n            directory where the file lives\n\n        Returns\n        -------\n        mesh : discretize.SimplexMesh\n            The tensor mesh object.\n        model_dict : dict of [str, (n_cells) numpy.ndarray]\n            A dictionary containing the models. The keys correspond to the names of the\n            models.\n        \"\"\"\n        _, _, _vtkUnstReader = load_vtk(\"vtkXMLUnstructuredGridReader\")\n\n        fname = os.path.join(directory, file_name)\n        # Read the file\n        vtuReader = _vtkUnstReader()\n        vtuReader.SetFileName(fname)\n        vtuReader.Update()\n        vtuGrid = vtuReader.GetOutput()\n        return SimplexMesh.vtk_to_simplex_mesh(vtuGrid)\n"
  },
  {
    "path": "discretize/operators/__init__.py",
    "content": "\"\"\"\n================================================\nDiscrete Operators (:mod:`discretize.operators`)\n================================================\n.. currentmodule:: discretize.operators\n\nThe ``operators`` package contains the classes discretize meshes with regular structure\nuse to construct discrete versions of the differential operators.\n\nOperator Classes\n----------------\n.. autosummary::\n  :toctree: generated/\n\n  DiffOperators\n  InnerProducts\n\"\"\"\n\nfrom discretize.operators.differential_operators import DiffOperators\nfrom discretize.operators.inner_products import InnerProducts\n"
  },
  {
    "path": "discretize/operators/differential_operators.py",
    "content": "\"\"\"Differential operators for meshes.\"\"\"\n\nimport numpy as np\nfrom scipy import sparse as sp\nimport warnings\nfrom discretize.base import BaseMesh\nfrom discretize.utils import (\n    sdiag,\n    speye,\n    kron3,\n    spzeros,\n    ddx,\n    av,\n    av_extrap,\n    make_boundary_bool,\n    cross2d,\n)\nfrom discretize.utils.code_utils import deprecate_method, deprecate_property\n\n\ndef _validate_BC(bc):\n    \"\"\"Check if boundary condition 'bc' is valid.\n\n    Each bc must be either 'dirichlet' or 'neumann'\n    \"\"\"\n    if isinstance(bc, str):\n        bc = [bc, bc]\n    if not isinstance(bc, list):\n        raise TypeError(\"bc must be a single string or list of strings\")\n    if not len(bc) == 2:\n        raise TypeError(\"bc list must have two elements, one for each side\")\n\n    for bc_i in bc:\n        if not isinstance(bc_i, str):\n            raise TypeError(\"each bc must be a string\")\n        if bc_i not in [\"dirichlet\", \"neumann\"]:\n            raise ValueError(\"each bc must be either, 'dirichlet' or 'neumann'\")\n    return bc\n\n\ndef _ddxCellGrad(n, bc):\n    \"\"\"Create 1D derivative operator from cell-centers to nodes.\n\n    This means we go from n to n+1\n\n    For Cell-Centered **Dirichlet**, use a ghost point::\n\n        (u_1 - u_g)/hf = grad\n\n            u_g       u_1      u_2\n             *    |    *   |    *     ...\n                  ^\n                  0\n\n        u_g = - u_1\n        grad = 2*u1/dx\n        negitive on the other side.\n\n    For Cell-Centered **Neumann**, use a ghost point::\n\n        (u_1 - u_g)/hf = 0\n\n            u_g       u_1      u_2\n             *    |    *   |    *     ...\n\n        u_g = u_1\n        grad = 0;  put a zero in.\n    \"\"\"\n    bc = _validate_BC(bc)\n\n    D = sp.spdiags((np.ones((n + 1, 1)) * [-1, 1]).T, [-1, 0], n + 1, n, format=\"csr\")\n    # Set the first side\n    if bc[0] == \"dirichlet\":\n        D[0, 0] = 2\n    elif bc[0] == \"neumann\":\n        D[0, 0] = 0\n    # Set the second side\n    if bc[1] == \"dirichlet\":\n        D[-1, -1] = -2\n    elif bc[1] == \"neumann\":\n        D[-1, -1] = 0\n    return D\n\n\ndef _ddxCellGradBC(n, bc):\n    \"\"\"Create 1D derivative operator from cell-centers to nodes.\n\n    This means we go from n to n+1.\n\n    For Cell-Centered **Dirichlet**, use a ghost point::\n\n        (u_1 - u_g)/hf = grad\n\n         u_g       u_1      u_2\n          *    |    *   |    *     ...\n               ^\n              u_b\n\n    We know the value at the boundary (u_b)::\n\n        (u_g+u_1)/2 = u_b               (the average)\n        u_g = 2*u_b - u_1\n\n        So plug in to gradient:\n\n        (u_1 - (2*u_b - u_1))/hf = grad\n        2*(u_1-u_b)/hf = grad\n\n    Separate, because BC are known (and can move to RHS later)::\n\n        ( 2/hf )*u_1 + ( -2/hf )*u_b = grad\n\n                       (   ^   ) JUST RETURN THIS\n    \"\"\"\n    bc = _validate_BC(bc)\n\n    ij = (np.array([0, n]), np.array([0, 1]))\n    vals = np.zeros(2)\n\n    # Set the first side\n    if bc[0] == \"dirichlet\":\n        vals[0] = -2\n    elif bc[0] == \"neumann\":\n        vals[0] = 0\n    # Set the second side\n    if bc[1] == \"dirichlet\":\n        vals[1] = 2\n    elif bc[1] == \"neumann\":\n        vals[1] = 0\n    D = sp.csr_matrix((vals, ij), shape=(n + 1, 2))\n    return D\n\n\nclass DiffOperators(BaseMesh):\n    \"\"\"Class used for creating differential and averaging operators.\n\n    ``DiffOperators`` is a class for managing the construction of\n    differential and averaging operators at the highest level.\n    The ``DiffOperator`` class is inherited by every ``discretize``\n    mesh class. In practice, differential and averaging operators are\n    not constructed by creating instances of ``DiffOperators``.\n    Instead, the operators are constructed (and sometimes stored)\n    when called as a property of the mesh.\n\n    \"\"\"\n\n    _aliases = {\n        \"aveFx2CC\": \"average_face_x_to_cell\",\n        \"aveFy2CC\": \"average_face_y_to_cell\",\n        \"aveFz2CC\": \"average_face_z_to_cell\",\n        \"aveEx2CC\": \"average_edge_x_to_cell\",\n        \"aveEy2CC\": \"average_edge_y_to_cell\",\n        \"aveEz2CC\": \"average_edge_z_to_cell\",\n    }\n\n    ###########################################################################\n    #                                                                         #\n    #                             Face Divergence                             #\n    #                                                                         #\n    ###########################################################################\n    @property\n    def _face_x_divergence_stencil(self):\n        \"\"\"Stencil for face divergence operator in the x-direction (x-faces to cell centers).\"\"\"\n        if self.dim == 1:\n            Dx = ddx(self.shape_cells[0])\n        elif self.dim == 2:\n            Dx = sp.kron(speye(self.shape_cells[1]), ddx(self.shape_cells[0]))\n        elif self.dim == 3:\n            Dx = kron3(\n                speye(self.shape_cells[2]),\n                speye(self.shape_cells[1]),\n                ddx(self.shape_cells[0]),\n            )\n        return Dx\n\n    @property\n    def _face_y_divergence_stencil(self):\n        \"\"\"Stencil for face divergence operator in the y-direction (y-faces to cell centers).\"\"\"\n        if self.dim == 1:\n            return None\n        elif self.dim == 2:\n            Dy = sp.kron(ddx(self.shape_cells[1]), speye(self.shape_cells[0]))\n        elif self.dim == 3:\n            Dy = kron3(\n                speye(self.shape_cells[2]),\n                ddx(self.shape_cells[1]),\n                speye(self.shape_cells[0]),\n            )\n        return Dy\n\n    @property\n    def _face_z_divergence_stencil(self):\n        \"\"\"Stencil for face divergence operator in the z-direction (z-faces to cell centers).\"\"\"\n        if self.dim == 1 or self.dim == 2:\n            return None\n        elif self.dim == 3:\n            Dz = kron3(\n                ddx(self.shape_cells[2]),\n                speye(self.shape_cells[1]),\n                speye(self.shape_cells[0]),\n            )\n        return Dz\n\n    @property\n    def _face_divergence_stencil(self):\n        \"\"\"Full stencil for face divergence operator (faces to cell centers).\"\"\"\n        if self.dim == 1:\n            D = self._face_x_divergence_stencil\n        elif self.dim == 2:\n            D = sp.hstack(\n                (self._face_x_divergence_stencil, self._face_y_divergence_stencil),\n                format=\"csr\",\n            )\n        elif self.dim == 3:\n            D = sp.hstack(\n                (\n                    self._face_x_divergence_stencil,\n                    self._face_y_divergence_stencil,\n                    self._face_z_divergence_stencil,\n                ),\n                format=\"csr\",\n            )\n        return D\n\n    @property\n    def face_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_face_divergence\", None) is None:\n            # Get the stencil of +1, -1's\n            D = self._face_divergence_stencil\n            # Compute areas of cell faces & volumes\n            S = self.face_areas\n            V = self.cell_volumes\n            self._face_divergence = sdiag(1 / V) * D * sdiag(S)\n        return self._face_divergence\n\n    @property\n    def face_x_divergence(self):\n        r\"\"\"X-derivative operator (x-faces to cell-centres).\n\n        This property constructs a 2nd order x-derivative operator which maps\n        from x-faces to cell centers. The operator is a sparse matrix\n        :math:`\\mathbf{D_x}` that can be applied as a matrix-vector product\n        to a discrete scalar quantity :math:`\\boldsymbol{\\phi}` that lives on\n        x-faces; i.e.::\n\n            dphi_dx = Dx @ phi\n\n        For a discrete vector whose x-component lives on x-faces, this operator\n        can also be used to compute the contribution of the x-component toward\n        the divergence.\n\n        Returns\n        -------\n        (n_cells, n_faces_x) scipy.sparse.csr_matrix\n            The numerical x-derivative operator from x-faces to cell centers\n\n        Examples\n        --------\n        Below, we demonstrate 1) how to apply the face-x divergence operator,\n        and 2) the mapping of the face-x divergence operator and its sparsity.\n        Our example is carried out on a 2D mesh but it can\n        be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        For a discrete scalar quantity :math:`\\boldsymbol{\\phi}` defined on the\n        x-faces, we take the x-derivative by constructing the face-x divergence\n        operator and multiplying as a matrix-vector product.\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete quantity on x-faces\n\n        >>> faces_x = mesh.faces_x\n        >>> phi = np.exp(-(faces_x[:, 0] ** 2) / 8** 2)\n\n        Construct the x-divergence operator and apply to vector\n\n        >>> Dx = mesh.face_x_divergence\n        >>> dphi_dx = Dx @ phi\n\n        Plot the original function and the x-divergence\n\n        >>> fig = plt.figure(figsize=(13, 6))\n        >>> ax1 = fig.add_subplot(121)\n        >>> w = np.r_[phi, np.ones(mesh.nFy)]  # Need vector on all faces for image plot\n        >>> mesh.plot_image(w, ax=ax1, v_type=\"Fx\")\n        >>> ax1.set_title(\"Scalar on x-faces\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(dphi_dx, ax=ax2)\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"X-derivative at cell center\", fontsize=14)\n        >>> plt.show()\n\n        The discrete x-face divergence operator is a sparse matrix that maps\n        from x-faces to cell centers. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements in\n        the original discrete quantity :math:`\\boldsymbol{\\phi}}` and its\n        x-derivative :math:`\\partial \\boldsymbol{\\phi}}/ \\partial x` as well as a\n        spy plot.\n\n        >>> mesh = TensorMesh([[(1, 6)], [(1, 3)]])\n        >>> fig = plt.figure(figsize=(10, 10))\n        >>> ax1 = fig.add_subplot(211)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.plot(\n        ...     mesh.faces_x[:, 0], mesh.faces_x[:, 1], \"g>\", markersize=8\n        ... )\n        >>> for ii, loc in zip(range(mesh.nFx), mesh.faces_x):\n        ...     ax1.text(loc[0]+0.05, loc[1]+0.02, \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.plot(\n        ...     mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8\n        ... )\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0]+0.05, loc[1]+0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.set_title(\"Mapping of Face-X Divergence\", fontsize=14, pad=15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (x-faces)', r'$\\partial \\mathbf{phi}/\\partial x$ (centers)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(212)\n        >>> ax2.spy(mesh.face_x_divergence)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"X-Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        # Compute areas of cell faces & volumes\n        S = self.reshape(self.face_areas, \"F\", \"Fx\", \"V\")\n        V = self.cell_volumes\n        return sdiag(1 / V) * self._face_x_divergence_stencil * sdiag(S)\n\n    @property\n    def face_y_divergence(self):\n        r\"\"\"Y-derivative operator (y-faces to cell-centres).\n\n        This property constructs a 2nd order y-derivative operator which maps\n        from y-faces to cell centers. The operator is a sparse matrix\n        :math:`\\mathbf{D_y}` that can be applied as a matrix-vector product\n        to a discrete scalar quantity :math:`\\boldsymbol{\\phi}` that lives on\n        y-faces; i.e.::\n\n            dphi_dy = Dy @ phi\n\n        For a discrete vector whose y-component lives on y-faces, this operator\n        can also be used to compute the contribution of the y-component toward\n        the divergence.\n\n        Returns\n        -------\n        (n_cells, n_faces_y) scipy.sparse.csr_matrix\n            The numerical y-derivative operator from y-faces to cell centers\n\n        Examples\n        --------\n        Below, we demonstrate 1) how to apply the face-y divergence operator,\n        and 2) the mapping of the face-y divergence operator and its sparsity.\n        Our example is carried out on a 2D mesh but it can\n        be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        For a discrete scalar quantity :math:`\\boldsymbol{\\phi}` defined on the\n        y-faces, we take the y-derivative by constructing the face-y divergence\n        operator and multiplying as a matrix-vector product.\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete quantity on x-faces\n\n        >>> faces_y = mesh.faces_y\n        >>> phi = np.exp(-(faces_y[:, 1] ** 2) / 8** 2)\n\n        Construct the y-divergence operator and apply to vector\n\n        >>> Dy = mesh.face_y_divergence\n        >>> dphi_dy = Dy @ phi\n\n        Plot original function and the y-divergence\n\n        >>> fig = plt.figure(figsize=(13, 6))\n        >>> ax1 = fig.add_subplot(121)\n        >>> w = np.r_[np.ones(mesh.nFx), phi]  # Need vector on all faces for image plot\n        >>> mesh.plot_image(w, ax=ax1, v_type=\"Fy\")\n        >>> ax1.set_title(\"Scalar on y-faces\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(dphi_dy, ax=ax2)\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Y-derivative at cell center\", fontsize=14)\n        >>> plt.show()\n\n        The discrete y-face divergence operator is a sparse matrix that maps\n        from y-faces to cell centers. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements in\n        the original discrete quantity :math:`\\boldsymbol{\\phi}` and its\n        y-derivative :math:`\\partial \\boldsymbol{\\phi}/ \\partial y` as well as a\n        spy plot.\n\n        >>> mesh = TensorMesh([[(1, 6)], [(1, 3)]])\n        >>> fig = plt.figure(figsize=(10, 10))\n        >>> ax1 = fig.add_subplot(211)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.plot(\n        ...     mesh.faces_y[:, 0], mesh.faces_y[:, 1], \"g^\", markersize=8\n        ... )\n        >>> for ii, loc in zip(range(mesh.nFy), mesh.faces_y):\n        ...     ax1.text(loc[0]+0.05, loc[1]+0.02, \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.plot(\n        ...     mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8\n        ... )\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0]+0.05, loc[1]+0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.set_title(\"Mapping of Face-Y Divergence\", fontsize=14, pad=15)\n        >>> ax1.legend(\n        ...     ['Mesh',r'$\\mathbf{\\phi}$ (y-faces)',r'$\\partial_y \\mathbf{\\phi}/\\partial y$ (centers)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(212)\n        >>> ax2.spy(mesh.face_y_divergence)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Y-Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 2:\n            return None\n        # Compute areas of cell faces & volumes\n        S = self.reshape(self.face_areas, \"F\", \"Fy\", \"V\")\n        V = self.cell_volumes\n        return sdiag(1 / V) * self._face_y_divergence_stencil * sdiag(S)\n\n    @property\n    def face_z_divergence(self):\n        r\"\"\"Z-derivative operator (z-faces to cell-centres).\n\n        This property constructs a 2nd order z-derivative operator which maps\n        from z-faces to cell centers. The operator is a sparse matrix\n        :math:`\\mathbf{D_z}` that can be applied as a matrix-vector product\n        to a discrete scalar quantity :math:`\\boldsymbol{\\phi}` that lives on\n        z-faces; i.e.::\n\n            dphi_dz = Dz @ phi\n\n        For a discrete vector whose z-component lives on z-faces, this operator\n        can also be used to compute the contribution of the z-component toward\n        the divergence.\n\n        Returns\n        -------\n        (n_cells, n_faces_z) scipy.sparse.csr_matrix\n            The numerical z-derivative operator from z-faces to cell centers\n\n        Examples\n        --------\n        Below, we demonstrate 2) how to apply the face-z divergence operator,\n        and 2) the mapping of the face-z divergence operator and its sparsity.\n        Our example is carried out on a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        For a discrete scalar quantity :math:`\\boldsymbol{\\phi}` defined on the\n        z-faces, we take the z-derivative by constructing the face-z divergence\n        operator and multiplying as a matrix-vector product.\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h, h], \"CCC\")\n\n        Create a discrete quantity on z-faces\n\n        >>> faces_z = mesh.faces_z\n        >>> phi = np.exp(-(faces_z[:, 2] ** 2) / 8** 2)\n\n        Construct the z-divergence operator and apply to vector\n\n        >>> Dz = mesh.face_z_divergence\n        >>> dphi_dz = Dz @ phi\n\n        Plot the original function and the z-divergence\n\n        >>> fig = plt.figure(figsize=(13, 6))\n        >>> ax1 = fig.add_subplot(121)\n        >>> w = np.r_[np.ones(mesh.nFx+mesh.nFz), phi]  # Need vector on all faces for image plot\n        >>> mesh.plot_slice(w, ax=ax1, v_type=\"Fz\", normal='Y', ind=20)\n        >>> ax1.set_title(\"Scalar on z-faces (y-slice)\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_slice(dphi_dz, ax=ax2, normal='Y', ind=20)\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Z-derivative at cell center (y-slice)\", fontsize=14)\n        >>> plt.show()\n\n        The discrete z-face divergence operator is a sparse matrix that maps\n        from z-faces to cell centers. To demonstrate this, we construct\n        a small 3D mesh. We then show the ordering of the elements in\n        the original discrete quantity :math:`\\boldsymbol{\\phi}` and its\n        z-derivative :math:`\\partial \\boldsymbol{\\phi}/ \\partial z` as well as a\n        spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 2)], [(1, 2)]])\n        >>> fig = plt.figure(figsize=(9, 12))\n        >>> ax1 = fig.add_axes([0, 0.35, 1, 0.6], projection='3d', elev=10, azim=-82)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.plot(\n        ...     mesh.faces_z[:, 0], mesh.faces_z[:, 1], mesh.faces_z[:, 2], \"g^\", markersize=10\n        ... )\n        >>> for ii, loc in zip(range(mesh.nFz), mesh.faces_z):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.05, loc[2], \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.plot(\n        ...    mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], mesh.cell_centers[:, 2],\n        ...    \"ro\", markersize=10\n        ... )\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.05, loc[2], \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.legend(\n        ...     ['Mesh',r'$\\mathbf{\\phi}$ (z-faces)',r'$\\partial \\mathbf{\\phi}/\\partial z$ (centers)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n\n            Manually make axis properties invisible\n\n            >>> ax1.set_xticks([])\n            >>> ax1.set_yticks([])\n            >>> ax1.set_zticks([])\n            >>> ax1.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n            >>> ax1.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n            >>> ax1.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n            >>> ax1.xaxis.line.set_color((1.0, 1.0, 1.0, 0.0))\n            >>> ax1.yaxis.line.set_color((1.0, 1.0, 1.0, 0.0))\n            >>> ax1.zaxis.line.set_color((1.0, 1.0, 1.0, 0.0))\n            >>> ax1.set_xlabel('X', labelpad=-15, fontsize=16)\n            >>> ax1.set_ylabel('Y', labelpad=-20, fontsize=16)\n            >>> ax1.set_zlabel('Z', labelpad=-20, fontsize=16)\n            >>> ax1.set_title(\"Mapping of Face-Z Divergence\", fontsize=16, pad=-15)\n\n            Spy plot the operator,\n\n            >>> ax2 = fig.add_axes([0.05, 0.05, 0.9, 0.3])\n            >>> ax2.spy(mesh.face_z_divergence)\n            >>> ax2.set_title(\"Spy Plot\", fontsize=16, pad=5)\n            >>> ax2.set_ylabel(\"Cell Index\", fontsize=12)\n            >>> ax2.set_xlabel(\"Z-Face Index\", fontsize=12)\n            >>> plt.show()\n        \"\"\"\n        if self.dim < 3:\n            return None\n        # Compute areas of cell faces & volumes\n        S = self.reshape(self.face_areas, \"F\", \"Fz\", \"V\")\n        V = self.cell_volumes\n        return sdiag(1 / V) * self._face_z_divergence_stencil * sdiag(S)\n\n    ###########################################################################\n    #                                                                         #\n    #                          Nodal Diff Operators                           #\n    #                                                                         #\n    ###########################################################################\n\n    @property\n    def _nodal_gradient_x_stencil(self):\n        \"\"\"Stencil for the nodal gradient in the x-direction (nodes to x-edges).\"\"\"\n        if self.dim == 1:\n            Gx = ddx(self.shape_cells[0])\n        elif self.dim == 2:\n            Gx = sp.kron(speye(self.shape_nodes[1]), ddx(self.shape_cells[0]))\n        elif self.dim == 3:\n            Gx = kron3(\n                speye(self.shape_nodes[2]),\n                speye(self.shape_nodes[1]),\n                ddx(self.shape_cells[0]),\n            )\n        return Gx\n\n    @property\n    def _nodal_gradient_y_stencil(self):\n        \"\"\"Stencil for the nodal gradient in the y-direction (nodes to y-edges).\"\"\"\n        if self.dim == 1:\n            return None\n        elif self.dim == 2:\n            Gy = sp.kron(ddx(self.shape_cells[1]), speye(self.shape_nodes[0]))\n        elif self.dim == 3:\n            Gy = kron3(\n                speye(self.shape_nodes[2]),\n                ddx(self.shape_cells[1]),\n                speye(self.shape_nodes[0]),\n            )\n        return Gy\n\n    @property\n    def _nodal_gradient_z_stencil(self):\n        \"\"\"Stencil for the nodal gradient in the z-direction (nodes to z-edges).\"\"\"\n        if self.dim == 1 or self.dim == 2:\n            return None\n        else:\n            Gz = kron3(\n                ddx(self.shape_cells[2]),\n                speye(self.shape_nodes[1]),\n                speye(self.shape_nodes[0]),\n            )\n        return Gz\n\n    @property\n    def _nodal_gradient_stencil(self):\n        \"\"\"Full stencil for the nodal gradient (nodes to edges).\"\"\"\n        # Compute divergence operator on faces\n        if self.dim == 1:\n            G = self._nodal_gradient_x_stencil\n        elif self.dim == 2:\n            G = sp.vstack(\n                (self._nodal_gradient_x_stencil, self._nodal_gradient_y_stencil),\n                format=\"csr\",\n            )\n        elif self.dim == 3:\n            G = sp.vstack(\n                (\n                    self._nodal_gradient_x_stencil,\n                    self._nodal_gradient_y_stencil,\n                    self._nodal_gradient_z_stencil,\n                ),\n                format=\"csr\",\n            )\n        return G\n\n    @property\n    def nodal_gradient(self):  # NOQA D102\n        if getattr(self, \"_nodal_gradient\", None) is None:\n            G = self._nodal_gradient_stencil\n            L = self.edge_lengths\n            self._nodal_gradient = sdiag(1 / L) * G\n        return self._nodal_gradient\n\n    @property\n    def _nodal_laplacian_x_stencil(self):\n        \"\"\"Stencil for the nodal Laplacian in the x-direction (nodes to nodes).\"\"\"\n        warnings.warn(\n            \"Laplacian has not been tested rigorously.\",\n            stacklevel=3,\n        )\n\n        Dx = ddx(self.shape_cells[0])\n        Lx = -Dx.T * Dx\n\n        if self.dim == 2:\n            Lx = sp.kron(speye(self.shape_nodes[1]), Lx)\n        elif self.dim == 3:\n            Lx = kron3(speye(self.shape_nodes[2]), speye(self.shape_nodes[1]), Lx)\n        return Lx\n\n    @property\n    def _nodal_laplacian_y_stencil(self):\n        \"\"\"Stencil for the nodal Laplacian in the y-direction (nodes to nodes).\"\"\"\n        warnings.warn(\n            \"Laplacian has not been tested rigorously.\",\n            stacklevel=3,\n        )\n\n        if self.dim == 1:\n            return None\n\n        Dy = ddx(self.shape_cells[1])\n        Ly = -Dy.T * Dy\n\n        if self.dim == 2:\n            Ly = sp.kron(Ly, speye(self.shape_nodes[0]))\n        elif self.dim == 3:\n            Ly = kron3(speye(self.shape_nodes[2]), Ly, speye(self.shape_nodes[0]))\n        return Ly\n\n    @property\n    def _nodal_laplacian_z_stencil(self):\n        \"\"\"Stencil for the nodal Laplacian in the z-direction (nodes to nodes).\"\"\"\n        warnings.warn(\n            \"Laplacian has not been tested rigorously.\",\n            stacklevel=3,\n        )\n\n        if self.dim == 1 or self.dim == 2:\n            return None\n\n        Dz = ddx(self.shape_cells[2])\n        Lz = -Dz.T * Dz\n        return kron3(Lz, speye(self.shape_nodes[1]), speye(self.shape_nodes[0]))\n\n    @property\n    def _nodal_laplacian_x(self):\n        \"\"\"Construct the nodal Laplacian in the x-direction (nodes to nodes).\"\"\"\n        Hx = sdiag(1.0 / self.h[0])\n        if self.dim == 2:\n            Hx = sp.kron(speye(self.shape_nodes[1]), Hx)\n        elif self.dim == 3:\n            Hx = kron3(speye(self.shape_nodes[2]), speye(self.shape_nodes[1]), Hx)\n        return Hx.T * self._nodal_gradient_x_stencil * Hx\n\n    @property\n    def _nodal_laplacian_y(self):\n        \"\"\"Construct the nodal Laplacian in the y-direction (nodes to nodes).\"\"\"\n        Hy = sdiag(1.0 / self.h[1])\n        if self.dim == 1:\n            return None\n        elif self.dim == 2:\n            Hy = sp.kron(Hy, speye(self.shape_nodes[0]))\n        elif self.dim == 3:\n            Hy = kron3(speye(self.shape_nodes[2]), Hy, speye(self.shape_nodes[0]))\n        return Hy.T * self._nodal_gradient_y_stencil * Hy\n\n    @property\n    def _nodal_laplacian_z(self):\n        \"\"\"Construct the nodal Laplacian in the z-direction (nodes to nodes).\"\"\"\n        if self.dim == 1 or self.dim == 2:\n            return None\n        Hz = sdiag(1.0 / self.h[2])\n        Hz = kron3(Hz, speye(self.shape_nodes[1]), speye(self.shape_nodes[0]))\n        return Hz.T * self._nodal_laplacian_z_stencil * Hz\n\n    @property\n    def nodal_laplacian(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_nodal_laplacian\", None) is None:\n            warnings.warn(\n                \"Laplacian has not been tested rigorously.\",\n                stacklevel=2,\n            )\n            # Compute divergence operator on faces\n            if self.dim == 1:\n                self._nodal_laplacian = self._nodal_laplacian_x\n            elif self.dim == 2:\n                self._nodal_laplacian = (\n                    self._nodal_laplacian_x + self._nodal_laplacian_y\n                )\n            elif self.dim == 3:\n                self._nodal_laplacian = (\n                    self._nodal_laplacian_x\n                    + self._nodal_laplacian_y\n                    + self._nodal_laplacian_z\n                )\n        return self._nodal_laplacian\n\n    def edge_divergence_weak_form_robin(self, alpha=0.0, beta=1.0, gamma=0.0):\n        r\"\"\"Create Robin conditions pieces for weak form of the edge divergence operator (edges to nodes).\n\n        This method returns the pieces required to impose Robin boundary conditions\n        for the discrete weak form divergence operator that maps from edges to nodes.\n        These pieces are needed when constructing the discrete representation\n        of the inner product :math:`\\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle`\n        according to the finite volume method.\n\n        To implement the boundary conditions, we assume\n\n        .. math::\n            \\vec{u} = \\nabla \\phi\n\n        for some scalar function :math:`\\phi`. Boundary conditions are imposed\n        on the scalar function according to the Robin condition:\n\n        .. math::\n            \\alpha \\phi + \\beta \\frac{\\partial \\phi}{\\partial n} = \\gamma\n\n        The user supplies values for :math:`\\alpha`, :math:`\\beta` and :math:`\\gamma`\n        for all boundary nodes or faces. For the values supplied,\n        **edge_divergence_weak_form_robin** returns the matrix :math:`\\mathbf{B}`\n        and vector :math:`\\mathbf{b}` required for the discrete representation\n        of :math:`\\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle`.\n        *See the notes section for a comprehensive description.*\n\n        Parameters\n        ----------\n        alpha : scalar or array_like\n            Defines :math:`\\alpha` for Robin boundary condition. Can be defined as a\n            scalar or array_like. If array_like, the length of the array must be equal\n            to the number of boundary faces or boundary nodes.\n        beta : scalar or array_like\n            Defines :math:`\\beta` for Robin boundary condition. Can be defined as a\n            scalar or array_like. If array_like, must have the same length as *alpha*.\n            Cannot be zero.\n        gamma : scalar or array_like\n            Defines :math:`\\gamma` for Robin boundary condition. If array like, *gamma*\n            can have shape (n_boundary_xxx,). Can also have shape (n_boundary_xxx, n_rhs)\n            if multiple systems have the same *alpha* and *beta* parameters.\n\n        Returns\n        -------\n        B : (n_nodes, n_nodes) scipy.sparse.dia_matrix\n            A sparse matrix dependent on the values of *alpha*, *beta* and *gamma* supplied\n        b : (n_nodes) numpy.ndarray or (n_nodes, n_rhs) numpy.ndarray\n            A vector dependent on the values of *alpha*, *beta* and *gamma* supplied\n\n        Notes\n        -----\n        For the divergence of a vector :math:`\\vec{u}`, the weak form is implemented by taking\n        the inner product with a piecewise-constant test function :math:`\\psi` and integrating\n        over the domain:\n\n        .. math::\n            \\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle \\; = \\int_\\Omega \\psi \\, (\\nabla \\cdot \\vec{u}) \\, dv\n\n        For a discrete representation of the vector :math:`\\vec{u}` that lives on mesh edges,\n        the divergence operator must map from edges to nodes. To implement boundary conditions in this\n        case, we must use the integration by parts to re-express the inner product as:\n\n        .. math::\n            \\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle \\, = - \\int_V \\vec{u} \\cdot \\nabla \\psi \\, dV\n            + \\oint_{\\partial \\Omega} \\psi \\, (\\hat{n} \\cdot \\vec{u}) \\, da\n\n        Assuming :math:`\\vec{u} = \\nabla \\phi`, the above equation becomes:\n\n        .. math::\n            \\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle \\, = - \\int_V \\nabla \\phi \\cdot \\nabla \\psi \\, dV\n            + \\oint_{\\partial \\Omega} \\psi \\, \\frac{\\partial \\phi}{\\partial n} \\, da\n\n        Substituting in the Robin conditions:\n\n        .. math::\n            \\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle \\, = - \\int_V \\nabla \\phi \\cdot \\nabla \\psi \\, dV\n            + \\oint_{\\partial \\Omega} \\psi \\, \\frac{(\\gamma - \\alpha \\Phi)}{\\beta} \\, da\n\n        therefore, `beta` cannot be zero.\n\n        The discrete approximation to the above expression is given by:\n\n        .. math::\n            \\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle \\,\n            \\approx - \\boldsymbol{\\psi^T \\big ( G_n^T M_e G_n - B \\big ) \\phi + \\psi^T b}\n\n        where\n\n        .. math::\n            \\boldsymbol{u} = \\boldsymbol{G_n \\, \\phi}\n\n        :math:`\\mathbf{G_n}` is the :py:attr:`~discretize.operators.DiffOperators.nodal_gradient`\n        and :math:`\\mathbf{M_e}` is the edge inner product matrix\n        (see :py:attr:`~discretize.operators.InnerProducts.get_edge_inner_product`).\n        **edge_divergence_weak_form_robin** returns the matrix :math:`\\mathbf{B}`\n        and vector :math:`\\mathbf{b}` based on the parameters *alpha* , *beta*\n        and *gamma* provided.\n\n        Examples\n        --------\n        Here we construct all of the pieces required for the discrete\n        representation of :math:`\\langle \\psi , \\nabla \\cdot \\vec{u} \\rangle`\n        for specified Robin boundary conditions. We define\n        :math:`\\mathbf{u}` on the edges, and :math:`\\boldsymbol{\\psi}`\n        and :math:`\\boldsymbol{\\psi}` on the nodes.\n        We begin by creating a small 2D tensor mesh:\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import scipy.sparse as sp\n        >>> h = np.ones(32)\n        >>> mesh = TensorMesh([h, h])\n\n        We then define `alpha`, `beta`, and `gamma` parameters for a zero Neumann\n        condition on the boundary faces. This corresponds to setting:\n\n        >>> alpha = 0.0\n        >>> beta = 1.0\n        >>> gamma = 0.0\n\n        Next, we construct all of the necessary pieces required to take\n        the discrete inner product:\n\n        >>> B, b = mesh.edge_divergence_weak_form_robin(alpha, beta, gamma)\n        >>> Me = mesh.get_edge_inner_product()\n        >>> Gn = mesh.nodal_gradient\n\n        In practice, these pieces are usually re-arranged when used to\n        solve PDEs with the finite volume method. Because the boundary\n        conditions are applied to the scalar potential :math:`\\phi`,\n        we create a function which computes the discrete inner product for any\n        :math:`\\boldsymbol{\\psi}` and :math:`\\boldsymbol{\\phi}` where\n        :math:`\\mathbf{u} = \\boldsymbol{G \\, \\phi}`:\n\n        >>> def inner_product(psi, phi):\n        ...     return psi @ (-Gn.T @ Me @ Gn + B) @ phi + psi @ b\n        \"\"\"\n        alpha = np.atleast_1d(alpha)\n        beta = np.atleast_1d(beta)\n        gamma = np.atleast_1d(gamma)\n\n        if np.any(beta == 0.0):\n            raise ValueError(\"beta cannot have a zero value\")\n\n        Pbn = self.project_node_to_boundary_node\n        Pbf = self.project_face_to_boundary_face\n\n        n_boundary_faces = Pbf.shape[0]\n        n_boundary_nodes = Pbn.shape[0]\n\n        if len(alpha) == 1:\n            if len(beta) != 1:\n                alpha = np.full(len(beta), alpha[0])\n            elif len(gamma) != 1:\n                alpha = np.full(len(gamma), alpha[0])\n            else:\n                alpha = np.full(n_boundary_faces, alpha[0])\n        if len(beta) == 1:\n            if len(alpha) != 1:\n                beta = np.full(len(alpha), beta[0])\n        if len(gamma) == 1:\n            if len(alpha) != 1:\n                gamma = np.full(len(alpha), gamma[0])\n\n        if len(alpha) != len(beta) or len(beta) != len(gamma):\n            raise ValueError(\"alpha, beta, and gamma must have the same length\")\n\n        if len(alpha) not in [n_boundary_faces, n_boundary_nodes]:\n            raise ValueError(\n                \"The arrays must be of length n_boundary_faces or n_boundary_nodes\"\n            )\n\n        AveN2F = self.average_node_to_face\n        boundary_areas = Pbf @ self.face_areas\n        AveBN2Bf = Pbf @ AveN2F @ Pbn.T\n\n        # at the boundary, we have that u dot n = (gamma - alpha * phi)/beta\n        if len(alpha) == n_boundary_faces:\n            if gamma.ndim == 2:\n                b = Pbn.T @ (\n                    AveBN2Bf.T @ (gamma / beta[:, None] * boundary_areas[:, None])\n                )\n            else:\n                b = Pbn.T @ (AveBN2Bf.T @ (gamma / beta * boundary_areas))\n            B = sp.diags(Pbn.T @ (AveBN2Bf.T @ (-alpha / beta * boundary_areas)))\n        else:\n            if gamma.ndim == 2:\n                b = Pbn.T @ (\n                    gamma / beta[:, None] * (AveBN2Bf.T @ boundary_areas)[:, None]\n                )\n            else:\n                b = Pbn.T @ (gamma / beta * (AveBN2Bf.T @ boundary_areas))\n            B = sp.diags(Pbn.T @ (-alpha / beta * (AveBN2Bf.T @ boundary_areas)))\n        return B, b\n\n    ###########################################################################\n    #                                                                         #\n    #                                Cell Grad                                #\n    #                                                                         #\n    ###########################################################################\n\n    _cell_gradient_BC_list = \"neumann\"\n\n    def set_cell_gradient_BC(self, BC):\n        \"\"\"Set boundary conditions for derivative operators acting on cell-centered quantities.\n\n        This method is used to set zero Dirichlet and/or zero Neumann boundary\n        conditions for differential operators that act on cell-centered quantities.\n        The user may apply the same boundary conditions to all boundaries, or\n        define the boundary conditions of boundary face (x, y and z) separately.\n        The user may also apply boundary conditions to the lower and upper boundary\n        face separately.\n\n        Cell gradient boundary conditions are enforced when constructing\n        the following properties:\n\n            - :py:attr:`~discretize.operators.DiffOperators.cell_gradient`\n            - :py:attr:`~discretize.operators.DiffOperators.cell_gradient_x`\n            - :py:attr:`~discretize.operators.DiffOperators.cell_gradient_y`\n            - :py:attr:`~discretize.operators.DiffOperators.cell_gradient_z`\n            - :py:attr:`~discretize.operators.DiffOperators.stencil_cell_gradient`\n            - :py:attr:`~discretize.operators.DiffOperators.stencil_cell_gradient_x`\n            - :py:attr:`~discretize.operators.DiffOperators.stencil_cell_gradient_y`\n            - :py:attr:`~discretize.operators.DiffOperators.stencil_cell_gradient_z`\n\n        By default, the mesh assumes a zero Neumann boundary condition on the\n        entire boundary. To define robin boundary conditions, see\n        :py:attr:`~discretize.operators.DiffOperators.cell_gradient_weak_form_robin`.\n\n        Parameters\n        ----------\n        BC : str or list [dim,]\n            Define the boundary conditions using the string 'dirichlet' for zero\n            Dirichlet conditions and 'neumann' for zero Neumann conditions. See\n            *examples* for several implementations.\n\n        Examples\n        --------\n        Here we demonstrate how to apply zero Dirichlet and/or Neumann boundary\n        conditions for cell-centers differential operators.\n\n        >>> from discretize import TensorMesh\n        >>> mesh = TensorMesh([[(1, 20)], [(1, 20)], [(1, 20)]])\n\n        Define zero Neumann conditions for all boundaries\n\n        >>> BC = 'neumann'\n        >>> mesh.set_cell_gradient_BC(BC)\n\n        Define zero Dirichlet on y boundaries and zero Neumann otherwise\n\n        >>> BC = ['neumann', 'dirichlet', 'neumann']\n        >>> mesh.set_cell_gradient_BC(BC)\n\n        Define zero Neumann on the bottom x-boundary and zero Dirichlet otherwise\n\n        >>> BC = [['neumann', 'dirichlet'], 'dirichlet', 'dirichlet']\n        >>> mesh.set_cell_gradient_BC(BC)\n        \"\"\"\n        if isinstance(BC, str):\n            BC = [BC] * self.dim\n        if isinstance(BC, list):\n            if len(BC) != self.dim:\n                raise ValueError(\"BC list must be the size of your mesh\")\n        else:\n            raise TypeError(\"BC must be a str or a list.\")\n\n        for i, bc_i in enumerate(BC):\n            BC[i] = _validate_BC(bc_i)\n\n        # ensure we create a new gradient next time we call it\n        self._cell_gradient = None\n        self._cell_gradient_BC = None\n        self._cell_gradient_BC_list = BC\n        return BC\n\n    @property\n    def stencil_cell_gradient_x(self):\n        r\"\"\"Differencing operator along x-direction (cell centers to x-faces).\n\n        This property constructs a differencing operator along the x-axis\n        that acts on cell centered quantities; i.e. the stencil for the\n        x-component of the cell gradient. The operator computes the\n        differences between the values at adjacent cell centers along the\n        x-direction, and places the result on the x-faces. The operator is a sparse\n        matrix :math:`\\mathbf{G_x}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            diff_phi_x = Gx @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **stencil_cell_gradient_x** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **stencil_cell_gradient_x** is called, the boundary conditions are\n        enforced for the differencing operator.\n\n        Returns\n        -------\n        (n_faces_x, n_cells) scipy.sparse.csr_matrix\n            The stencil for the x-component of the cell gradient\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the\n        x-component cell gradient stencil, construct the operator and apply it\n        to a discrete scalar quantity. The mapping of the operator and\n        its sparsity is also illustrated. Our example is carried out on a 2D\n        mesh but it can be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers. In this case, the scalar represents some block within\n        a homogeneous medium.\n\n        Create a uniform grid\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete scalar at cell centers\n\n        >>> centers = mesh.cell_centers\n        >>> phi = np.zeros(mesh.nC)\n        >>> k = (np.abs(mesh.cell_centers[:, 0]) < 10.) & (np.abs(mesh.cell_centers[:, 1]) < 10.)\n        >>> phi[k] = 1.\n\n        Before constructing the stencil gradient operator, we must define\n        the boundary conditions; zero Neumann for our example. Even though\n        we are only computing the difference along x, we define boundary\n        conditions for all boundary faces. Once the\n        operator is created, it is applied as a matrix-vector product.\n\n        >>> mesh.set_cell_gradient_BC(['neumann', 'neumann'])\n        >>> Gx = mesh.stencil_cell_gradient_x\n        >>> diff_phi_x = Gx @ phi\n\n        Now we plot the original scalar, and the differencing taken along the\n        x axes.\n\n        >>> fig = plt.figure(figsize=(13, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi, ax=ax1)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> v = np.r_[diff_phi_x, np.zeros(mesh.nFy)]  # Define vector for plotting fun\n        >>> mesh.plot_image(v, ax=ax2, v_type=\"Fx\")\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Difference (x-axis)\", fontsize=14)\n        >>> plt.show()\n\n        The x-component cell gradient stencil is a sparse differencing matrix\n        that maps from cell centers to x-faces. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements\n        and a spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 4)]])\n        >>> mesh.set_cell_gradient_BC('neumann')\n\n        >>> fig = plt.figure(figsize=(12, 8))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.set_title(\"Mapping of Stencil\", fontsize=14, pad=15)\n        >>> ax1.plot(mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.plot(mesh.faces_x[:, 0], mesh.faces_x[:, 1], \"g>\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFx), mesh.faces_x):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (centers)', r'$\\mathbf{Gx^* \\phi}$ (x-faces)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(122)\n        >>> ax2.spy(mesh.stencil_cell_gradient_x)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"X-Face Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        BC = [\"neumann\", \"neumann\"]\n        if self.dim == 1:\n            G1 = _ddxCellGrad(self.shape_cells[0], BC)\n        elif self.dim == 2:\n            G1 = sp.kron(\n                speye(self.shape_cells[1]), _ddxCellGrad(self.shape_cells[0], BC)\n            )\n        elif self.dim == 3:\n            G1 = kron3(\n                speye(self.shape_cells[2]),\n                speye(self.shape_cells[1]),\n                _ddxCellGrad(self.shape_cells[0], BC),\n            )\n        return G1\n\n    @property\n    def stencil_cell_gradient_y(self):\n        r\"\"\"Differencing operator along y-direction (cell centers to y-faces).\n\n        This property constructs a differencing operator along the x-axis\n        that acts on cell centered quantities; i.e. the stencil for the\n        y-component of the cell gradient. The operator computes the\n        differences between the values at adjacent cell centers along the\n        y-direction, and places the result on the y-faces. The operator is a sparse\n        matrix :math:`\\mathbf{G_y}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            diff_phi_y = Gy @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **stencil_cell_gradient_y** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **stencil_cell_gradient_y** is called, the boundary conditions are\n        enforced for the differencing operator.\n\n        Returns\n        -------\n        (n_faces_y, n_cells) scipy.sparse.csr_matrix\n            The stencil for the y-component of the cell gradient\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the\n        y-component cell gradient stencil, construct the operator and apply it\n        to a discrete scalar quantity. The mapping of the operator and\n        its sparsity is also illustrated. Our example is carried out on a 2D\n        mesh but it can be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers. In this case, the scalar represents some block within\n        a homogeneous medium.\n\n        Create a uniform grid\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete scalar at cell centers\n\n        >>> centers = mesh.cell_centers\n        >>> phi = np.zeros(mesh.nC)\n        >>> k = (np.abs(mesh.cell_centers[:, 0]) < 10.) & (np.abs(mesh.cell_centers[:, 1]) < 10.)\n        >>> phi[k] = 1.\n\n        Before constructing the operator, we must define\n        the boundary conditions; zero Neumann for our example. Even though\n        we are only computing the difference along y, we define boundary\n        conditions for all boundary faces. Once the\n        operator is created, it is applied as a matrix-vector product.\n\n        >>> mesh.set_cell_gradient_BC(['neumann', 'neumann'])\n        >>> Gy = mesh.stencil_cell_gradient_y\n        >>> diff_phi_y = Gy @ phi\n\n        Now we plot the original scalar, and the differencing taken along the\n        y-axis.\n\n        >>> fig = plt.figure(figsize=(13, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi, ax=ax1)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> v = np.r_[np.zeros(mesh.nFx), diff_phi_y]  # Define vector for plotting fun\n        >>> mesh.plot_image(v, ax=ax2, v_type=\"Fy\")\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Difference (y-axis)\", fontsize=14)\n        >>> plt.show()\n\n        The y-component cell gradient stencil is a sparse differencing matrix\n        that maps from cell centers to y-faces. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements\n        and a spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 4)]])\n        >>> mesh.set_cell_gradient_BC('neumann')\n        >>> fig = plt.figure(figsize=(12, 8))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.set_title(\"Mapping of Stencil\", fontsize=14, pad=15)\n        >>> ax1.plot(mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.plot(mesh.faces_y[:, 0], mesh.faces_y[:, 1], \"g^\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFy), mesh.faces_y):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii + mesh.nFx), color=\"g\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (centers)', r'$\\mathbf{Gy^* \\phi}$ (y-faces)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(122)\n        >>> ax2.spy(mesh.stencil_cell_gradient_y)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Y-Face Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 2:\n            return None\n        BC = [\"neumann\", \"neumann\"]  # TODO: remove this hard-coding\n        n = self.vnC\n        if self.dim == 2:\n            G2 = sp.kron(_ddxCellGrad(n[1], BC), speye(n[0]))\n        elif self.dim == 3:\n            G2 = kron3(speye(n[2]), _ddxCellGrad(n[1], BC), speye(n[0]))\n        return G2\n\n    @property\n    def stencil_cell_gradient_z(self):\n        r\"\"\"Differencing operator along z-direction (cell centers to z-faces).\n\n        This property constructs a differencing operator along the z-axis\n        that acts on cell centered quantities; i.e. the stencil for the\n        z-component of the cell gradient. The operator computes the\n        differences between the values at adjacent cell centers along the\n        z-direction, and places the result on the z-faces. The operator is a sparse\n        matrix :math:`\\mathbf{G_z}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            diff_phi_z = Gz @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **stencil_cell_gradient_z** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **stencil_cell_gradient_z** is called, the boundary conditions are\n        enforced for the differencing operator.\n\n        Returns\n        -------\n        (n_faces_z, n_cells) scipy.sparse.csr_matrix\n            The stencil for the z-component of the cell gradient\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the\n        z-component cell gradient stencil, construct the operator and apply it\n        to a discrete scalar quantity. The mapping of the operator and\n        its sparsity is also illustrated.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers. In this case, the scalar represents some block within\n        a homogeneous medium.\n\n        Create a uniform grid\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h, h], \"CCC\")\n\n        Create a discrete scalar at cell centers\n\n        >>> centers = mesh.cell_centers\n        >>> phi = np.zeros(mesh.nC)\n        >>> k = (\n        ...     (np.abs(mesh.cell_centers[:, 0]) < 10.) &\n        ...     (np.abs(mesh.cell_centers[:, 1]) < 10.) &\n        ...     (np.abs(mesh.cell_centers[:, 2]) < 10.)\n        ... )\n        >>> phi[k] = 1.\n\n        Before constructing the operator, we must define\n        the boundary conditions; zero Neumann for our example. Even though\n        we are only computing the difference along z, we define boundary\n        conditions for all boundary faces. Once the\n        operator is created, it is applied as a matrix-vector product.\n\n        >>> mesh.set_cell_gradient_BC(['neumann', 'neumann', 'neumann'])\n        >>> Gz = mesh.stencil_cell_gradient_z\n        >>> diff_phi_z = Gz @ phi\n\n        Now we plot the original scalar, and the differencing taken along the\n        z-axis for a slice at y = 0.\n\n        >>> fig = plt.figure(figsize=(13, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_slice(phi, ax=ax1, normal='Y', slice_loc=0)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> v = np.r_[np.zeros(mesh.nFx+mesh.nFy), diff_phi_z]  # Define vector for plotting fun\n        >>> mesh.plot_slice(v, ax=ax2, v_type='Fz', normal='Y', slice_loc=0)\n        >>> ax2.set_title(\"Difference (z-axis)\", fontsize=14)\n        >>> plt.show()\n\n        The z-component cell gradient stencil is a sparse differencing matrix\n        that maps from cell centers to z-faces. To demonstrate this, we provide\n        a spy plot\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(mesh.stencil_cell_gradient_z, ms=1)\n        >>> ax1.set_title(\"Spy Plot\", fontsize=16, pad=5)\n        >>> ax1.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> ax1.set_ylabel(\"Z-Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 3:\n            return None\n        BC = [\"neumann\", \"neumann\"]  # TODO: remove this hard-coding\n        n = self.vnC\n        G3 = kron3(_ddxCellGrad(n[2], BC), speye(n[1]), speye(n[0]))\n        return G3\n\n    @property\n    def stencil_cell_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        BC = self.set_cell_gradient_BC(self._cell_gradient_BC_list)\n        if self.dim == 1:\n            G = _ddxCellGrad(self.shape_cells[0], BC[0])\n        elif self.dim == 2:\n            G1 = sp.kron(\n                speye(self.shape_cells[1]), _ddxCellGrad(self.shape_cells[0], BC[0])\n            )\n            G2 = sp.kron(\n                _ddxCellGrad(self.shape_cells[1], BC[1]), speye(self.shape_cells[0])\n            )\n            G = sp.vstack((G1, G2), format=\"csr\")\n        elif self.dim == 3:\n            G1 = kron3(\n                speye(self.shape_cells[2]),\n                speye(self.shape_cells[1]),\n                _ddxCellGrad(self.shape_cells[0], BC[0]),\n            )\n            G2 = kron3(\n                speye(self.shape_cells[2]),\n                _ddxCellGrad(self.shape_cells[1], BC[1]),\n                speye(self.shape_cells[0]),\n            )\n            G3 = kron3(\n                _ddxCellGrad(self.shape_cells[2], BC[2]),\n                speye(self.shape_cells[1]),\n                speye(self.shape_cells[0]),\n            )\n            G = sp.vstack((G1, G2, G3), format=\"csr\")\n        return G\n\n    @property\n    def cell_gradient(self):\n        r\"\"\"Cell gradient operator (cell centers to faces).\n\n        This property constructs the 2nd order numerical gradient operator\n        that maps from cell centers to faces. The operator is a sparse matrix\n        :math:`\\mathbf{G_c}` that can be applied as a matrix-vector product\n        to a discrete scalar quantity :math:`\\boldsymbol{\\phi}` that lives\n        at the cell centers; i.e.::\n\n            grad_phi = Gc @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **cell_gradient** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **cell_gradient** is called, the boundary conditions are\n        enforced for the gradient operator. Once constructed, the operator is\n        stored as a property of the mesh. *See notes*.\n\n        This operator is defined mostly as a helper property, and is not necessarily\n        recommended to use when solving PDE's.\n\n        Returns\n        -------\n        (n_faces, n_cells) scipy.sparse.csr_matrix\n            The numerical gradient operator from cell centers to faces\n\n\n        Notes\n        -----\n        In continuous space, the gradient operator is defined as:\n\n        .. math::\n            \\vec{u} = \\nabla \\phi = \\frac{\\partial \\phi}{\\partial x}\\hat{x}\n            + \\frac{\\partial \\phi}{\\partial y}\\hat{y}\n            + \\frac{\\partial \\phi}{\\partial z}\\hat{z}\n\n        Where :math:`\\boldsymbol{\\phi}` is the discrete representation of the continuous variable\n        :math:`\\phi` at cell centers and :math:`\\mathbf{u}` is the discrete\n        representation of :math:`\\vec{u}` on the faces, **cell_gradient** constructs a\n        discrete linear operator :math:`\\mathbf{G_c}` such that:\n\n        .. math::\n            \\mathbf{u} = \\mathbf{G_c} \\, \\boldsymbol{\\phi}\n\n        Second order ghost points are used to enforce boundary conditions and map\n        appropriately to boundary faces. Along each axes direction, we are\n        effectively computing the derivative by taking the difference between the\n        values at adjacent cell centers and dividing by their distance.\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the cell gradient\n        operator, construct the cell gradient operator and apply it to a discrete\n        scalar quantity. The mapping of the operator and its sparsity is also\n        illustrated. Our example is carried out on a 2D mesh but it can\n        be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers which is zero on the boundaries (zero Dirichlet).\n\n        Create a uniform grid\n\n        >>> h = np.ones(20)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n\n        Create a discrete scalar on nodes\n\n        >>> centers = mesh.cell_centers\n        >>> phi = np.exp(-(centers[:, 0] ** 2 + centers[:, 1] ** 2) / 4 ** 2)\n\n        Once the operator is created, the gradient is performed as a\n        matrix-vector product.\n\n        Construct the gradient operator and apply to vector\n\n        >>> Gc = mesh.cell_gradient\n        >>> grad_phi = Gc @ phi\n\n        Plot the results\n\n        >>> fig = plt.figure(figsize=(13, 6))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi, ax=ax1)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(\n        ...     grad_phi, ax=ax2, v_type=\"F\", view=\"vec\",\n        ...     stream_opts={\"color\": \"w\", \"density\": 1.0}\n        ... )\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Gradient at faces\", fontsize=14)\n        >>> plt.show()\n\n        The cell gradient operator is a sparse matrix that maps\n        from cell centers to faces. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements in\n        the original discrete quantity :math:`\\boldsymbol{\\phi}` and its\n        discrete gradient as well as a spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 6)]])\n        >>> mesh.set_cell_gradient_BC('dirichlet')\n        >>> fig = plt.figure(figsize=(12, 10))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.set_title(\"Mapping of Gradient Operator\", fontsize=14, pad=15)\n        >>> ax1.plot(mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.plot(mesh.faces_x[:, 0], mesh.faces_x[:, 1], \"g^\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFx), mesh.faces_x):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.plot(mesh.faces_y[:, 0], mesh.faces_y[:, 1], \"g>\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFy), mesh.faces_y):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format((ii + mesh.nFx)), color=\"g\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (centers)', r'$\\mathbf{u}$ (faces)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(122)\n        >>> ax2.spy(mesh.cell_gradient)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Face Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_cell_gradient\", None) is None:\n            G = self.stencil_cell_gradient\n            S = self.face_areas  # Compute areas of cell faces & volumes\n            V = (\n                self.aveCC2F * self.cell_volumes\n            )  # Average volume between adjacent cells\n            self._cell_gradient = sdiag(S / V) * G\n        return self._cell_gradient\n\n    def cell_gradient_weak_form_robin(self, alpha=0.0, beta=1.0, gamma=0.0):\n        r\"\"\"Create Robin conditions pieces for weak form of the cell gradient operator (cell centers to faces).\n\n        This method returns the pieces required to impose Robin boundary conditions\n        for the discrete weak form gradient operator that maps from cell centers to faces.\n        These pieces are needed when constructing the discrete representation\n        of the inner product :math:`\\langle \\vec{u} , \\nabla \\phi \\rangle`\n        according to the finite volume method.\n\n        Where general boundary conditions are defined on :math:`\\phi`\n        according to the Robin condition:\n\n        .. math::\n            \\alpha \\phi + \\beta \\frac{\\partial \\phi}{\\partial n} = \\gamma\n\n        the user supplies values for :math:`\\alpha`, :math:`\\beta` and :math:`\\gamma`\n        for all boundary faces. **cell_gradient_weak_form_robin** returns the matrix\n        :math:`\\mathbf{B}` and vector :math:`\\mathbf{b}` required for the discrete\n        representation of :math:`\\langle \\vec{u} , \\nabla \\phi \\rangle`.\n        *See the notes section for a comprehensive description.*\n\n        Parameters\n        ----------\n        alpha : scalar or (n_boundary_faces) array_like\n            Defines :math:`\\alpha` for Robin boundary condition. Can be defined as a\n            scalar or array_like. If array_like, the length of the array must be equal\n            to the number of boundary faces.\n        beta : scalar or (n_boundary_faces) array_like\n            Defines :math:`\\beta` for Robin boundary condition. Can be defined as a\n            scalar or array_like. If array_like, must have the same length as *alpha* .\n        gamma: scalar or (n_boundary_faces) array_like or (n_boundary_faces, n_rhs) array_like\n            Defines :math:`\\gamma` for Robin boundary condition. If array like, *gamma*\n            can have shape (n_boundary_face,). Can also have shape (n_boundary_faces, n_rhs)\n            if multiple systems have the same *alpha* and *beta* parameters.\n\n        Returns\n        -------\n        B : (n_faces, n_cells) scipy.sparse.csr_matrix\n            A sparse matrix dependent on the values of *alpha*, *beta* and *gamma* supplied\n        b : (n_faces) numpy.ndarray or (n_faces, n_rhs) numpy.ndarray\n            A vector dependent on the values of *alpha*, *beta* and *gamma* supplied\n\n        Notes\n        -----\n        For the gradient of a scalar :math:`\\phi`, the weak form is implemented by taking\n        the inner product with a piecewise-constant test function :math:`\\vec{u}` and integrating\n        over the domain:\n\n        .. math::\n            \\langle \\vec{u} , \\nabla \\phi \\rangle \\; = \\int_\\Omega \\vec{u} \\cdot (\\nabla \\phi) \\, dv\n\n        For a discrete representation of :math:`\\phi` at cell centers, the gradient operator\n        maps from cell centers to faces. To implement the boundary conditions in this\n        case, we must use integration by parts and re-express the inner product as:\n\n        .. math::\n            \\langle \\vec{u} , \\nabla \\phi \\rangle \\; = - \\int_V \\phi \\, (\\nabla \\cdot \\vec{u} ) \\, dV\n            + \\oint_{\\partial \\Omega} \\phi \\hat{n} \\cdot \\vec{u} \\, da\n\n        where the Robin condition is applied to :math:`\\phi` on the\n        boundary. The discrete approximation to the above expression is given by:\n\n        .. math::\n            \\langle \\vec{u} , \\nabla \\phi \\rangle \\; \\approx - \\boldsymbol{u^T \\big ( D^T M_c - B \\big ) \\phi + u^T b}\n\n        where :math:`\\mathbf{D}` is the :py:attr:`~discretize.operators.DiffOperators.face_divergence`\n        and :math:`\\mathbf{M_c}` is the cell center inner product matrix\n        (just a diagonal matrix comprised of the cell volumes).\n        **cell_gradient_weak_form_robin** returns the matrix :math:`\\mathbf{B}`\n        and vector :math:`\\mathbf{b}` based on the parameters *alpha* , *beta*\n        and *gamma* provided.\n\n        Examples\n        --------\n        Here we form all of pieces required to construct the discrete representation\n        of the inner product between :math:`\\mathbf{u}` for specified Robin boundary\n        conditions. We define :math:`\\boldsymbol{\\phi}` at cell centers and\n        :math:`\\mathbf{u}` on the faces. We begin by creating a small 2D tensor mesh:\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import scipy.sparse as sp\n\n        >>> h = np.ones(32)\n        >>> mesh = TensorMesh([h, h])\n\n        We then define `alpha`, `beta`, and `gamma` parameters for a zero Dirichlet\n        condition on the boundary faces. This corresponds to setting:\n\n        >>> alpha = 1.0\n        >>> beta = 0.0\n        >>> gamma = 0.0\n\n        Next, we construct all of the necessary pieces required to take\n        the discrete inner product:\n\n        >>> B, b = mesh.cell_gradient_weak_form_robin(alpha, beta, gamma)\n        >>> Mc = sp.diags(mesh.cell_volumes)\n        >>> Df = mesh.face_divergence\n\n        In practice, these pieces are usually re-arranged when used to\n        solve PDEs with the finite volume method. However, if you wanted\n        to create a function which computes the discrete inner product for any\n        :math:`\\mathbf{u}` and :math:`\\boldsymbol{\\phi}`:\n\n        >>> def inner_product(u, phi):\n        ...     return u @ (-Df.T @ Mc + B) @ phi + u @ b\n        \"\"\"\n        # get length between boundary cell_centers and boundary_faces\n        Pf = self.project_face_to_boundary_face\n        aveC2BF = Pf @ self.average_cell_to_face\n        # distance from cell centers to ghost point on boundary faces\n        if self.dim == 1:\n            h = np.abs(self.boundary_faces - aveC2BF @ self.cell_centers)\n        else:\n            h = np.linalg.norm(\n                self.boundary_faces - aveC2BF @ self.cell_centers, axis=1\n            )\n\n        # for the ghost point u_k = a*u_i + b where\n        a = beta / h / (alpha + beta / h)\n        A = sp.diags(a) @ aveC2BF\n\n        gamma = np.asarray(gamma)\n        if gamma.ndim > 1:\n            b = (gamma) / (alpha + beta / h)[:, None]\n        else:\n            b = (gamma) / (alpha + beta / h)\n\n        # value at boundary = A*cells + b\n        M = self.boundary_face_scalar_integral\n        A = M @ A\n        b = M @ b\n\n        return A, b\n\n    @property\n    def cell_gradient_BC(self):\n        \"\"\"Boundary conditions matrix for the cell gradient operator (Deprecated).\"\"\"\n        warnings.warn(\n            \"cell_gradient_BC is deprecated and is not longer used. See cell_gradient\",\n            stacklevel=2,\n        )\n        if getattr(self, \"_cell_gradient_BC\", None) is None:\n            BC = self.set_cell_gradient_BC(self._cell_gradient_BC_list)\n            n = self.vnC\n            if self.dim == 1:\n                G = _ddxCellGradBC(n[0], BC[0])\n            elif self.dim == 2:\n                G1 = sp.kron(speye(n[1]), _ddxCellGradBC(n[0], BC[0]))\n                G2 = sp.kron(_ddxCellGradBC(n[1], BC[1]), speye(n[0]))\n                G = sp.block_diag((G1, G2), format=\"csr\")\n            elif self.dim == 3:\n                G1 = kron3(speye(n[2]), speye(n[1]), _ddxCellGradBC(n[0], BC[0]))\n                G2 = kron3(speye(n[2]), _ddxCellGradBC(n[1], BC[1]), speye(n[0]))\n                G3 = kron3(_ddxCellGradBC(n[2], BC[2]), speye(n[1]), speye(n[0]))\n                G = sp.block_diag((G1, G2, G3), format=\"csr\")\n            # Compute areas of cell faces & volumes\n            S = self.face_areas\n            V = (\n                self.aveCC2F * self.cell_volumes\n            )  # Average volume between adjacent cells\n            self._cell_gradient_BC = sdiag(S / V) * G\n        return self._cell_gradient_BC\n\n    @property\n    def cell_gradient_x(self):\n        r\"\"\"X-derivative operator (cell centers to x-faces).\n\n        This property constructs an x-derivative operator that acts on\n        cell centered quantities; i.e. the x-component of the cell gradient operator.\n        When applied, the x-derivative is mapped to x-faces. The operator is a\n        sparse matrix :math:`\\mathbf{G_x}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            grad_phi_x = Gx @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **cell_gradient_x** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **cell_gradient_x** is called, the boundary conditions are\n        enforced for the differencing operator.\n\n        Returns\n        -------\n        (n_faces_x, n_cells) scipy.sparse.csr_matrix\n            X-derivative operator (x-component of the cell gradient)\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the\n        x-component cell gradient, construct the operator and apply it\n        to a discrete scalar quantity. The mapping of the operator and\n        its sparsity is also illustrated. Our example is carried out on a 2D\n        mesh but it can be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers.\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n        >>> centers = mesh.cell_centers\n        >>> phi = np.exp(-(centers[:, 0] ** 2) / 8** 2)\n\n        Before constructing the operator, we must define\n        the boundary conditions; zero Neumann for our example. Even though\n        we are only computing the derivative along x, we define boundary\n        conditions for all boundary faces. Once the\n        operator is created, it is applied as a matrix-vector product.\n\n        >>> mesh.set_cell_gradient_BC(['neumann', 'neumann'])\n        >>> Gx = mesh.stencil_cell_gradient_x\n        >>> grad_phi_x = Gx @ phi\n\n        Now we plot the original scalar, and the x-derivative.\n\n        >>> fig = plt.figure(figsize=(13, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi, ax=ax1)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> v = np.r_[grad_phi_x, np.zeros(mesh.nFy)]  # Define vector for plotting fun\n        >>> mesh.plot_image(v, ax=ax2, v_type=\"Fx\")\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"X-derivative at x-faces\", fontsize=14)\n        >>> plt.show()\n\n        The operator is a sparse x-derivative matrix\n        that maps from cell centers to x-faces. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements\n        and a spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 4)]])\n        >>> mesh.set_cell_gradient_BC('neumann')\n\n        >>> fig = plt.figure(figsize=(12, 8))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.set_title(\"Mapping of Operator\", fontsize=14, pad=15)\n        >>> ax1.plot(mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.plot(mesh.faces_x[:, 0], mesh.faces_x[:, 1], \"g>\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFx), mesh.faces_x):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"g\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (centers)', r'$\\mathbf{Gx^* \\phi}$ (x-faces)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(122)\n        >>> ax2.spy(mesh.cell_gradient_x)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"X-Face Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_cell_gradient_x\", None) is None:\n            G1 = self.stencil_cell_gradient_x\n            # Compute areas of cell faces & volumes\n            V = self.aveCC2F * self.cell_volumes\n            L = self.reshape(self.face_areas / V, \"F\", \"Fx\", \"V\")\n            self._cell_gradient_x = sdiag(L) * G1\n        return self._cell_gradient_x\n\n    @property\n    def cell_gradient_y(self):\n        r\"\"\"Y-derivative operator (cell centers to y-faces).\n\n        This property constructs a y-derivative operator that acts on\n        cell centered quantities; i.e. the y-component of the cell gradient operator.\n        When applied, the y-derivative is mapped to y-faces. The operator is a\n        sparse matrix :math:`\\mathbf{G_y}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            grad_phi_y = Gy @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **cell_gradient_y** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **cell_gradient_y** is called, the boundary conditions are\n        enforced for the differencing operator.\n\n        Returns\n        -------\n        (n_faces_y, n_cells) scipy.sparse.csr_matrix\n            Y-derivative operator (y-component of the cell gradient)\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the\n        y-component cell gradient, construct the operator and apply it\n        to a discrete scalar quantity. The mapping of the operator and\n        its sparsity is also illustrated. Our example is carried out on a 2D\n        mesh but it can be done equivalently for a 3D mesh.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers.\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], \"CC\")\n        >>> centers = mesh.cell_centers\n        >>> phi = np.exp(-(centers[:, 1] ** 2) / 8** 2)\n\n        Before constructing the operator, we must define\n        the boundary conditions; zero Neumann for our example. Even though\n        we are only computing the derivative along y, we define boundary\n        conditions for all boundary faces. Once the\n        operator is created, it is applied as a matrix-vector product.\n\n        >>> mesh.set_cell_gradient_BC(['neumann', 'neumann'])\n        >>> Gy = mesh.cell_gradient_y\n        >>> grad_phi_y = Gy @ phi\n\n        Now we plot the original scalar is y-derivative.\n\n        >>> fig = plt.figure(figsize=(13, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_image(phi, ax=ax1)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> v = np.r_[np.zeros(mesh.nFx), grad_phi_y]  # Define vector for plotting fun\n        >>> mesh.plot_image(v, ax=ax2, v_type=\"Fy\")\n        >>> ax2.set_yticks([])\n        >>> ax2.set_ylabel(\"\")\n        >>> ax2.set_title(\"Y-derivative at y-faces\", fontsize=14)\n        >>> plt.show()\n\n        The operator is a sparse y-derivative matrix\n        that maps from cell centers to y-faces. To demonstrate this, we construct\n        a small 2D mesh. We then show the ordering of the elements\n        and a spy plot.\n\n        >>> mesh = TensorMesh([[(1, 3)], [(1, 4)]])\n        >>> mesh.set_cell_gradient_BC('neumann')\n        >>> fig = plt.figure(figsize=(12, 8))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_grid(ax=ax1)\n        >>> ax1.set_title(\"Mapping of Operator\", fontsize=14, pad=15)\n        >>> ax1.plot(mesh.cell_centers[:, 0], mesh.cell_centers[:, 1], \"ro\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nC), mesh.cell_centers):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii), color=\"r\")\n        >>> ax1.plot(mesh.faces_y[:, 0], mesh.faces_y[:, 1], \"g^\", markersize=8)\n        >>> for ii, loc in zip(range(mesh.nFy), mesh.faces_y):\n        ...     ax1.text(loc[0] + 0.05, loc[1] + 0.02, \"{0:d}\".format(ii + mesh.nFx), color=\"g\")\n        >>> ax1.set_xticks([])\n        >>> ax1.set_yticks([])\n        >>> ax1.spines['bottom'].set_color('white')\n        >>> ax1.spines['top'].set_color('white')\n        >>> ax1.spines['left'].set_color('white')\n        >>> ax1.spines['right'].set_color('white')\n        >>> ax1.set_xlabel('X', fontsize=16, labelpad=-5)\n        >>> ax1.set_ylabel('Y', fontsize=16, labelpad=-15)\n        >>> ax1.legend(\n        ...     ['Mesh', r'$\\mathbf{\\phi}$ (centers)', r'$\\mathbf{Gy^* \\phi}$ (y-faces)'],\n        ...     loc='upper right', fontsize=14\n        ... )\n        >>> ax2 = fig.add_subplot(122)\n        >>> ax2.spy(mesh.stencil_cell_gradient_y)\n        >>> ax2.set_title(\"Spy Plot\", fontsize=14, pad=5)\n        >>> ax2.set_ylabel(\"Y-Face Index\", fontsize=12)\n        >>> ax2.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 2:\n            return None\n        if getattr(self, \"_cell_gradient_y\", None) is None:\n            G2 = self.stencil_cell_gradient_y\n            # Compute areas of cell faces & volumes\n            V = self.aveCC2F * self.cell_volumes\n            L = self.reshape(self.face_areas / V, \"F\", \"Fy\", \"V\")\n            self._cell_gradient_y = sdiag(L) * G2\n        return self._cell_gradient_y\n\n    @property\n    def cell_gradient_z(self):\n        r\"\"\"Z-derivative operator (cell centers to z-faces).\n\n        This property constructs an z-derivative operator that acts on\n        cell centered quantities; i.e. the z-component of the cell gradient operator.\n        When applied, the z-derivative is mapped to z-faces. The operator is a\n        sparse matrix :math:`\\mathbf{G_z}` that can be applied as a matrix-vector\n        product to a cell centered quantity :math:`\\boldsymbol{\\phi}`, i.e.::\n\n            grad_phi_z = Gz @ phi\n\n        By default, the operator assumes zero-Neumann boundary conditions\n        on the scalar quantity. Before calling **cell_gradient_z** however,\n        the user can set a mix of zero Dirichlet and zero Neumann boundary\n        conditions using :py:attr:`~discretize.operators.DiffOperators.set_cell_gradient_BC`.\n        When **cell_gradient_z** is called, the boundary conditions are\n        enforced for the differencing operator.\n\n        Returns\n        -------\n        (n_faces_z, n_cells) scipy.sparse.csr_matrix\n            Z-derivative operator (z-component of the cell gradient)\n\n        Examples\n        --------\n        Below, we demonstrate how to set boundary conditions for the\n        z-component of the cell gradient, construct the operator and apply it\n        to a discrete scalar quantity. The mapping of the operator and\n        its sparsity is also illustrated.\n\n        We start by importing the necessary packages and modules.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib as mpl\n\n        We then construct a mesh and define a scalar function at cell\n        centers.\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h, h], \"CCC\")\n        >>> centers = mesh.cell_centers\n        >>> phi = np.exp(-(centers[:, 2] ** 2) / 8** 2)\n\n        Before constructing the operator, we must define\n        the boundary conditions; zero Neumann for our example. Even though\n        we are only computing the derivative along z, we define boundary\n        conditions for all boundary faces. Once the\n        operator is created, it is applied as a matrix-vector product.\n\n        >>> mesh.set_cell_gradient_BC(['neumann', 'neumann', 'neumann'])\n        >>> Gz = mesh.cell_gradient_z\n        >>> grad_phi_z = Gz @ phi\n\n        Now we plot the original scalar and the z-derivative for a slice at y = 0.\n\n        >>> fig = plt.figure(figsize=(13, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> mesh.plot_slice(phi, ax=ax1, normal='Y', slice_loc=0)\n        >>> ax1.set_title(\"Scalar at cell centers\", fontsize=14)\n        >>> ax2 = fig.add_subplot(122)\n        >>> v = np.r_[np.zeros(mesh.nFx+mesh.nFy), grad_phi_z]  # Define vector for plotting fun\n        >>> mesh.plot_slice(v, ax=ax2, v_type='Fz', normal='Y', slice_loc=0)\n        >>> ax2.set_title(\"Z-derivative (z-faces)\", fontsize=14)\n        >>> plt.show()\n\n        The z-component cell gradient is a sparse derivative matrix\n        that maps from cell centers to z-faces. To demonstrate this, we provide\n        a spy plot\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(mesh.cell_gradient_z, ms=1)\n        >>> ax1.set_title(\"Spy Plot\", fontsize=16, pad=5)\n        >>> ax1.set_xlabel(\"Cell Index\", fontsize=12)\n        >>> ax1.set_ylabel(\"Z-Face Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 3:\n            return None\n        if getattr(self, \"_cell_gradient_z\", None) is None:\n            G3 = self.stencil_cell_gradient_z\n            # Compute areas of cell faces & volumes\n            V = self.aveCC2F * self.cell_volumes\n            L = self.reshape(self.face_areas / V, \"F\", \"Fz\", \"V\")\n            self._cell_gradient_z = sdiag(L) * G3\n        return self._cell_gradient_z\n\n    ###########################################################################\n    #                                                                         #\n    #                                Edge Curl                                #\n    #                                                                         #\n    ###########################################################################\n\n    @property\n    def _edge_x_curl_stencil(self):\n        \"\"\"Stencil for the edge curl operator in the x-direction.\"\"\"\n        n = self.vnC  # The number of cell centers in each direction\n\n        D32 = kron3(ddx(n[2]), speye(n[1]), speye(n[0] + 1))\n        D23 = kron3(speye(n[2]), ddx(n[1]), speye(n[0] + 1))\n        # O1 = spzeros(np.shape(D32)[0], np.shape(D31)[1])\n        O1 = spzeros((n[0] + 1) * n[1] * n[2], n[0] * (n[1] + 1) * (n[2] + 1))\n\n        return sp.hstack((O1, -D32, D23))\n\n    @property\n    def _edge_y_curl_stencil(self):\n        \"\"\"Stencil for the edge curl operator in the y-direction.\"\"\"\n        n = self.vnC  # The number of cell centers in each direction\n\n        D31 = kron3(ddx(n[2]), speye(n[1] + 1), speye(n[0]))\n        D13 = kron3(speye(n[2]), speye(n[1] + 1), ddx(n[0]))\n        # O2 = spzeros(np.shape(D31)[0], np.shape(D32)[1])\n        O2 = spzeros(n[0] * (n[1] + 1) * n[2], (n[0] + 1) * n[1] * (n[2] + 1))\n\n        return sp.hstack((D31, O2, -D13))\n\n    @property\n    def _edge_z_curl_stencil(self):\n        \"\"\"Stencil for the edge curl operator in the z-direction.\"\"\"\n        n = self.vnC  # The number of cell centers in each direction\n\n        D21 = kron3(speye(n[2] + 1), ddx(n[1]), speye(n[0]))\n        D12 = kron3(speye(n[2] + 1), speye(n[1]), ddx(n[0]))\n        # O3 = spzeros(np.shape(D21)[0], np.shape(D13)[1])\n        O3 = spzeros(n[0] * n[1] * (n[2] + 1), (n[0] + 1) * (n[1] + 1) * n[2])\n\n        return sp.hstack((-D21, D12, O3))\n\n    @property\n    def _edge_curl_stencil(self):\n        \"\"\"Full stencil for the edge curl operator.\"\"\"\n        if self.dim <= 1:\n            raise NotImplementedError(\"Edge Curl only programed for 2 or 3D.\")\n\n        # Compute divergence operator on faces\n        if self.dim == 2:\n            n = self.vnC  # The number of cell centers in each direction\n\n            D21 = sp.kron(ddx(n[1]), speye(n[0]))\n            D12 = sp.kron(speye(n[1]), ddx(n[0]))\n            C = sp.hstack((-D21, D12), format=\"csr\")\n            return C\n\n        elif self.dim == 3:\n            # D32 = kron3(ddx(n[2]), speye(n[1]), speye(n[0]+1))\n            # D23 = kron3(speye(n[2]), ddx(n[1]), speye(n[0]+1))\n            # D31 = kron3(ddx(n[2]), speye(n[1]+1), speye(n[0]))\n            # D13 = kron3(speye(n[2]), speye(n[1]+1), ddx(n[0]))\n            # D21 = kron3(speye(n[2]+1), ddx(n[1]), speye(n[0]))\n            # D12 = kron3(speye(n[2]+1), speye(n[1]), ddx(n[0]))\n\n            # O1 = spzeros(np.shape(D32)[0], np.shape(D31)[1])\n            # O2 = spzeros(np.shape(D31)[0], np.shape(D32)[1])\n            # O3 = spzeros(np.shape(D21)[0], np.shape(D13)[1])\n\n            # C = sp.vstack((sp.hstack((O1, -D32, D23)),\n            #                sp.hstack((D31, O2, -D13)),\n            #                sp.hstack((-D21, D12, O3))), format=\"csr\")\n\n            C = sp.vstack(\n                (\n                    self._edge_x_curl_stencil,\n                    self._edge_y_curl_stencil,\n                    self._edge_z_curl_stencil,\n                ),\n                format=\"csr\",\n            )\n\n            return C\n\n    @property\n    def edge_curl(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_edge_curl\", None) is None:\n            if self.dim <= 1:\n                raise NotImplementedError(\"Edge Curl only programed for 2 or 3D.\")\n            L = self.edge_lengths  # Compute lengths of cell edges\n            if self.dim == 2:\n                S = self.cell_volumes\n            elif self.dim == 3:\n                S = self.face_areas\n            self._edge_curl = sdiag(1 / S) @ self._edge_curl_stencil @ sdiag(L)\n        return self._edge_curl\n\n    @property\n    def boundary_face_scalar_integral(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 1:\n            return sp.csr_matrix(\n                ([-1, 1], ([0, self.n_faces_x - 1], [0, 1])), shape=(self.n_faces_x, 2)\n            )\n        P = self.project_face_to_boundary_face\n\n        w_h_dot_normal = np.sum(\n            (P @ self.face_normals) * self.boundary_face_outward_normals, axis=-1\n        )\n        A = sp.diags(self.face_areas) @ P.T @ sp.diags(w_h_dot_normal)\n        return A\n\n    @property\n    def boundary_edge_vector_integral(self):\n        r\"\"\"Integrate a vector function on the boundary.\n\n        This matrix represents the boundary surface integral of a vector function\n        multiplied with a finite volume test function on the mesh.\n\n        In 1D and 2D, the operation assumes that the right array contains only a single\n        component of the vector ``u``. In 3D, however, we must assume that ``u`` will\n        contain each of the three vector components, and it must be ordered as,\n        ``[edges_1_x, ... ,edge_N_x, edge_1_y, ..., edge_N_y, edge_1_z, ..., edge_N_z]``\n        , where ``N`` is the number of boundary edges.\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            Sparse matrix of shape (n_edges, n_boundary_edges) for 1D or 2D mesh,\n            (n_edges, 3*n_boundary_edges) for a 3D mesh.\n\n        Notes\n        -----\n        The integral we are representing on the boundary of the mesh is\n\n        .. math:: \\int_{\\Omega} \\vec{w} \\cdot (\\vec{u} \\times \\hat{n}) \\partial \\Omega\n\n        In discrete form this is:\n\n        .. math:: w^T * P @ u_b\n\n        where `w` is defined on all edges, and `u_b` is all three components defined on\n        boundary edges.\n        \"\"\"\n        Pe = self.project_edge_to_boundary_edge\n        Pf = self.project_face_to_boundary_face\n        dA = self.boundary_face_outward_normals * (Pf @ self.face_areas)[:, None]\n        w = Pe @ self.edge_tangents\n\n        n_boundary_edges = len(w)\n\n        Av = Pf @ self.average_edge_to_face @ Pe.T\n        if self.dim > 2:\n            Av *= 2\n\n        av_da = Av.T @ dA\n\n        if self.dim == 2:\n            w_cross_n = cross2d(av_da, w)\n        else:\n            w_cross_n = np.cross(av_da, w)\n\n        if self.dim == 2:\n            return Pe.T @ sp.diags(w_cross_n, format=\"csr\")\n        return Pe.T @ sp.diags(\n            w_cross_n.T,\n            n_boundary_edges * np.arange(3),\n            shape=(n_boundary_edges, 3 * n_boundary_edges),\n        )\n\n    @property\n    def boundary_node_vector_integral(self):\n        r\"\"\"Integrate a vector function dotted with the boundary normal.\n\n        This matrix represents the boundary surface integral of a vector function\n        dotted with the boundary normal and multiplied with a scalar finite volume\n        test function on the mesh.\n\n        Returns\n        -------\n        (n_nodes, dim * n_boundary_nodes) scipy.sparse.csr_matrix\n            Sparse matrix of shape.\n\n        Notes\n        -----\n        The integral we are representing on the boundary of the mesh is\n\n        .. math:: \\int_{\\Omega} (w \\vec{u}) \\cdot \\hat{n} \\partial \\Omega\n\n        In discrete form this is:\n\n        .. math:: w^T * P @ u_b\n\n        where `w` is defined on all nodes, and `u_b` is all three components defined on\n        boundary nodes.\n        \"\"\"\n        if self.dim == 1:\n            return sp.csr_matrix(\n                ([-1, 1], ([0, self.shape_nodes[0] - 1], [0, 1])),\n                shape=(self.shape_nodes[0], 2),\n            )\n        Pn = self.project_node_to_boundary_node\n        Pf = self.project_face_to_boundary_face\n        n_boundary_nodes = Pn.shape[0]\n\n        dA = self.boundary_face_outward_normals * (Pf @ self.face_areas)[:, None]\n\n        Av = Pf @ self.average_node_to_face @ Pn.T\n\n        u_dot_ds = Av.T @ dA\n        diags = u_dot_ds.T\n        offsets = n_boundary_nodes * np.arange(self.dim)\n\n        return Pn.T @ sp.diags(\n            diags, offsets, shape=(n_boundary_nodes, self.dim * n_boundary_nodes)\n        )\n\n    def get_BC_projections(self, BC, discretization=\"CC\"):\n        \"\"\"Create the weak form boundary condition projection matrices.\n\n        Examples\n        --------\n        .. code:: python\n\n            # Neumann in all directions\n            BC = 'neumann'\n\n            # 3D, Dirichlet in y Neumann else\n            BC = ['neumann', 'dirichlet', 'neumann']\n\n            # 3D, Neumann in x on bottom of domain, Dirichlet else\n            BC = [['neumann', 'dirichlet'], 'dirichlet', 'dirichlet']\n        \"\"\"\n        if discretization != \"CC\":\n            raise NotImplementedError(\n                \"Boundary conditions only implemented\" \"for CC discretization.\"\n            )\n\n        if isinstance(BC, str):\n            BC = [BC for _ in self.vnC]  # Repeat the str self.dim times\n        elif isinstance(BC, list):\n            if len(BC) != self.dim:\n                raise ValueError(\"BC list must be the size of your mesh\")\n        else:\n            raise TypeError(\"BC must be a str or a list.\")\n\n        for i, bc_i in enumerate(BC):\n            BC[i] = _validate_BC(bc_i)\n\n        def projDirichlet(n, bc):\n            bc = _validate_BC(bc)\n            ij = ([0, n], [0, 1])\n            vals = [0, 0]\n            if bc[0] == \"dirichlet\":\n                vals[0] = -1\n            if bc[1] == \"dirichlet\":\n                vals[1] = 1\n            return sp.csr_matrix((vals, ij), shape=(n + 1, 2))\n\n        def projNeumannIn(n, bc):\n            bc = _validate_BC(bc)\n            P = sp.identity(n + 1).tocsr()\n            if bc[0] == \"neumann\":\n                P = P[1:, :]\n            if bc[1] == \"neumann\":\n                P = P[:-1, :]\n            return P\n\n        def projNeumannOut(n, bc):\n            bc = _validate_BC(bc)\n            ij = ([0, 1], [0, n])\n            vals = [0, 0]\n            if bc[0] == \"neumann\":\n                vals[0] = 1\n            if bc[1] == \"neumann\":\n                vals[1] = 1\n            return sp.csr_matrix((vals, ij), shape=(2, n + 1))\n\n        n = self.vnC\n        indF = self.face_boundary_indices\n        if self.dim == 1:\n            Pbc = projDirichlet(n[0], BC[0])\n            indF = indF[0] | indF[1]\n            Pbc = Pbc * sdiag(self.face_areas[indF])\n\n            Pin = projNeumannIn(n[0], BC[0])\n\n            Pout = projNeumannOut(n[0], BC[0])\n\n        elif self.dim == 2:\n            Pbc1 = sp.kron(speye(n[1]), projDirichlet(n[0], BC[0]))\n            Pbc2 = sp.kron(projDirichlet(n[1], BC[1]), speye(n[0]))\n            Pbc = sp.block_diag((Pbc1, Pbc2), format=\"csr\")\n            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3])]\n            Pbc = Pbc * sdiag(self.face_areas[indF])\n\n            P1 = sp.kron(speye(n[1]), projNeumannIn(n[0], BC[0]))\n            P2 = sp.kron(projNeumannIn(n[1], BC[1]), speye(n[0]))\n            Pin = sp.block_diag((P1, P2), format=\"csr\")\n\n            P1 = sp.kron(speye(n[1]), projNeumannOut(n[0], BC[0]))\n            P2 = sp.kron(projNeumannOut(n[1], BC[1]), speye(n[0]))\n            Pout = sp.block_diag((P1, P2), format=\"csr\")\n\n        elif self.dim == 3:\n            Pbc1 = kron3(speye(n[2]), speye(n[1]), projDirichlet(n[0], BC[0]))\n            Pbc2 = kron3(speye(n[2]), projDirichlet(n[1], BC[1]), speye(n[0]))\n            Pbc3 = kron3(projDirichlet(n[2], BC[2]), speye(n[1]), speye(n[0]))\n            Pbc = sp.block_diag((Pbc1, Pbc2, Pbc3), format=\"csr\")\n            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3]), (indF[4] | indF[5])]\n            Pbc = Pbc * sdiag(self.face_areas[indF])\n\n            P1 = kron3(speye(n[2]), speye(n[1]), projNeumannIn(n[0], BC[0]))\n            P2 = kron3(speye(n[2]), projNeumannIn(n[1], BC[1]), speye(n[0]))\n            P3 = kron3(projNeumannIn(n[2], BC[2]), speye(n[1]), speye(n[0]))\n            Pin = sp.block_diag((P1, P2, P3), format=\"csr\")\n\n            P1 = kron3(speye(n[2]), speye(n[1]), projNeumannOut(n[0], BC[0]))\n            P2 = kron3(speye(n[2]), projNeumannOut(n[1], BC[1]), speye(n[0]))\n            P3 = kron3(projNeumannOut(n[2], BC[2]), speye(n[1]), speye(n[0]))\n            Pout = sp.block_diag((P1, P2, P3), format=\"csr\")\n\n        return Pbc, Pin, Pout\n\n    def get_BC_projections_simple(self, discretization=\"CC\"):\n        \"\"\"Create weak form boundary condition projection matrices for mixed boundary condition.\"\"\"\n        if discretization != \"CC\":\n            raise NotImplementedError(\n                \"Boundary conditions only implemented\" \"for CC discretization.\"\n            )\n\n        def projBC(n):\n            ij = ([0, n], [0, 1])\n            vals = [0, 0]\n            vals[0] = 1\n            vals[1] = 1\n            return sp.csr_matrix((vals, ij), shape=(n + 1, 2))\n\n        def projDirichlet(n, bc):\n            bc = _validate_BC(bc)\n            ij = ([0, n], [0, 1])\n            vals = [0, 0]\n            if bc[0] == \"dirichlet\":\n                vals[0] = -1\n            if bc[1] == \"dirichlet\":\n                vals[1] = 1\n            return sp.csr_matrix((vals, ij), shape=(n + 1, 2))\n\n        BC = [\n            [\"dirichlet\", \"dirichlet\"],\n            [\"dirichlet\", \"dirichlet\"],\n            [\"dirichlet\", \"dirichlet\"],\n        ]\n        n = self.vnC\n        indF = self.face_boundary_indices\n\n        if self.dim == 1:\n            Pbc = projDirichlet(n[0], BC[0])\n            B = projBC(n[0])\n            indF = indF[0] | indF[1]\n            Pbc = Pbc * sdiag(self.face_areas[indF])\n\n        elif self.dim == 2:\n            Pbc1 = sp.kron(speye(n[1]), projDirichlet(n[0], BC[0]))\n            Pbc2 = sp.kron(projDirichlet(n[1], BC[1]), speye(n[0]))\n            Pbc = sp.block_diag((Pbc1, Pbc2), format=\"csr\")\n            B1 = sp.kron(speye(n[1]), projBC(n[0]))\n            B2 = sp.kron(projBC(n[1]), speye(n[0]))\n            B = sp.block_diag((B1, B2), format=\"csr\")\n            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3])]\n            Pbc = Pbc * sdiag(self.face_areas[indF])\n\n        elif self.dim == 3:\n            Pbc1 = kron3(speye(n[2]), speye(n[1]), projDirichlet(n[0], BC[0]))\n            Pbc2 = kron3(speye(n[2]), projDirichlet(n[1], BC[1]), speye(n[0]))\n            Pbc3 = kron3(projDirichlet(n[2], BC[2]), speye(n[1]), speye(n[0]))\n            Pbc = sp.block_diag((Pbc1, Pbc2, Pbc3), format=\"csr\")\n            B1 = kron3(speye(n[2]), speye(n[1]), projBC(n[0]))\n            B2 = kron3(speye(n[2]), projBC(n[1]), speye(n[0]))\n            B3 = kron3(projBC(n[2]), speye(n[1]), speye(n[0]))\n            B = sp.block_diag((B1, B2, B3), format=\"csr\")\n            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3]), (indF[4] | indF[5])]\n            Pbc = Pbc * sdiag(self.face_areas[indF])\n\n        return Pbc, B.T\n\n    ###########################################################################\n    #                                                                         #\n    #                                Averaging                                #\n    #                                                                         #\n    ###########################################################################\n\n    @property\n    def average_face_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_face_to_cell\", None) is None:\n            if self.dim == 1:\n                self._average_face_to_cell = self.aveFx2CC\n            elif self.dim == 2:\n                self._average_face_to_cell = (0.5) * sp.hstack(\n                    (self.aveFx2CC, self.aveFy2CC), format=\"csr\"\n                )\n            elif self.dim == 3:\n                self._average_face_to_cell = (1.0 / 3.0) * sp.hstack(\n                    (self.aveFx2CC, self.aveFy2CC, self.aveFz2CC), format=\"csr\"\n                )\n        return self._average_face_to_cell\n\n    @property\n    def average_face_to_cell_vector(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_face_to_cell_vector\", None) is None:\n            if self.dim == 1:\n                self._average_face_to_cell_vector = self.aveFx2CC\n            elif self.dim == 2:\n                self._average_face_to_cell_vector = sp.block_diag(\n                    (self.aveFx2CC, self.aveFy2CC), format=\"csr\"\n                )\n            elif self.dim == 3:\n                self._average_face_to_cell_vector = sp.block_diag(\n                    (self.aveFx2CC, self.aveFy2CC, self.aveFz2CC), format=\"csr\"\n                )\n        return self._average_face_to_cell_vector\n\n    @property\n    def average_face_x_to_cell(self):\n        r\"\"\"Averaging operator from x-faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from x-faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on x-faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_faces_x) scipy.sparse.csr_matrix\n            The scalar averaging operator from x-faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_x}` be a discrete scalar quantity that\n        lives on x-faces. **average_face_x_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{xc}}` that projects\n        :math:`\\boldsymbol{\\phi_x}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{xc}} \\, \\boldsymbol{\\phi_x}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its x-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Axc @ phi_x\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the x-faces. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Create a scalar variable on x-faces:\n\n        >>> phi_x = np.zeros(mesh.nFx)\n        >>> xy = mesh.faces_x\n        >>> phi_x[(xy[:, 1] > 0)] = 25.0\n        >>> phi_x[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n\n        >>> Axc = mesh.average_face_x_to_cell\n        >>> phi_c = Axc @ phi_x\n\n        And plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> v = np.r_[phi_x, np.zeros(mesh.nFy)]  # create vector for plotting function\n        >>> mesh.plot_image(v, ax=ax1, v_type=\"Fx\")\n        >>> ax1.set_title(\"Variable at x-faces\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Axc, ms=1)\n        >>> ax1.set_title(\"X-Face Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_average_face_x_to_cell\", None) is None:\n            n = self.vnC\n            if self.dim == 1:\n                self._average_face_x_to_cell = av(n[0])\n            elif self.dim == 2:\n                self._average_face_x_to_cell = sp.kron(speye(n[1]), av(n[0]))\n            elif self.dim == 3:\n                self._average_face_x_to_cell = kron3(speye(n[2]), speye(n[1]), av(n[0]))\n        return self._average_face_x_to_cell\n\n    @property\n    def average_face_y_to_cell(self):\n        r\"\"\"Averaging operator from y-faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from y-faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on x-faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_faces_y) scipy.sparse.csr_matrix\n            The scalar averaging operator from y-faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_y}` be a discrete scalar quantity that\n        lives on y-faces. **average_face_y_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{yc}}` that projects\n        :math:`\\boldsymbol{\\phi_y}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{yc}} \\, \\boldsymbol{\\phi_y}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its y-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Ayc @ phi_y\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the y-faces. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Create a scalar variable on y-faces,\n\n        >>> phi_y = np.zeros(mesh.nFy)\n        >>> xy = mesh.faces_y\n        >>> phi_y[(xy[:, 1] > 0)] = 25.0\n        >>> phi_y[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n\n        >>> Ayc = mesh.average_face_y_to_cell\n        >>> phi_c = Ayc @ phi_y\n\n        And finally, plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> v = np.r_[np.zeros(mesh.nFx), phi_y]  # create vector for plotting function\n        >>> mesh.plot_image(v, ax=ax1, v_type=\"Fy\")\n        >>> ax1.set_title(\"Variable at y-faces\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Ayc, ms=1)\n        >>> ax1.set_title(\"Y-Face Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 2:\n            return None\n        if getattr(self, \"_average_face_y_to_cell\", None) is None:\n            n = self.vnC\n            if self.dim == 2:\n                self._average_face_y_to_cell = sp.kron(av(n[1]), speye(n[0]))\n            elif self.dim == 3:\n                self._average_face_y_to_cell = kron3(speye(n[2]), av(n[1]), speye(n[0]))\n        return self._average_face_y_to_cell\n\n    @property\n    def average_face_z_to_cell(self):\n        r\"\"\"Averaging operator from z-faces to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from z-faces to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on z-faces must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_faces_z) scipy.sparse.csr_matrix\n            The scalar averaging operator from z-faces to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_z}` be a discrete scalar quantity that\n        lives on z-faces. **average_face_z_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{zc}}` that projects\n        :math:`\\boldsymbol{\\phi_z}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{zc}} \\, \\boldsymbol{\\phi_z}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its z-faces. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Azc @ phi_z\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the z-faces. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h, h], x0=\"CCC\")\n\n        Create a scalar variable on z-faces\n\n        >>> phi_z = np.zeros(mesh.nFz)\n        >>> xyz = mesh.faces_z\n        >>> phi_z[(xyz[:, 2] > 0)] = 25.0\n        >>> phi_z[(xyz[:, 2] < -10.0) & (xyz[:, 0] > -10.0) & (xyz[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n        We plot the original scalar and its average at cell centers for a\n        slice at y=0.\n\n        >>> Azc = mesh.average_face_z_to_cell\n        >>> phi_c = Azc @ phi_z\n\n        And plot the results:\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> v = np.r_[np.zeros(mesh.nFx+mesh.nFy), phi_z]  # create vector for plotting\n        >>> mesh.plot_slice(v, ax=ax1, normal='Y', slice_loc=0, v_type=\"Fz\")\n        >>> ax1.set_title(\"Variable at z-faces\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, normal='Y', slice_loc=0, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Azc, ms=1)\n        >>> ax1.set_title(\"Z-Face Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 3:\n            return None\n        if getattr(self, \"_average_face_z_to_cell\", None) is None:\n            n = self.vnC\n            if self.dim == 3:\n                self._average_face_z_to_cell = kron3(av(n[2]), speye(n[1]), speye(n[0]))\n        return self._average_face_z_to_cell\n\n    @property\n    def average_cell_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_cell_to_face\", None) is None:\n            if self.dim == 1:\n                self._average_cell_to_face = av_extrap(self.shape_cells[0])\n            elif self.dim == 2:\n                self._average_cell_to_face = sp.vstack(\n                    (\n                        sp.kron(\n                            speye(self.shape_cells[1]), av_extrap(self.shape_cells[0])\n                        ),\n                        sp.kron(\n                            av_extrap(self.shape_cells[1]), speye(self.shape_cells[0])\n                        ),\n                    ),\n                    format=\"csr\",\n                )\n            elif self.dim == 3:\n                self._average_cell_to_face = sp.vstack(\n                    (\n                        kron3(\n                            speye(self.shape_cells[2]),\n                            speye(self.shape_cells[1]),\n                            av_extrap(self.shape_cells[0]),\n                        ),\n                        kron3(\n                            speye(self.shape_cells[2]),\n                            av_extrap(self.shape_cells[1]),\n                            speye(self.shape_cells[0]),\n                        ),\n                        kron3(\n                            av_extrap(self.shape_cells[2]),\n                            speye(self.shape_cells[1]),\n                            speye(self.shape_cells[0]),\n                        ),\n                    ),\n                    format=\"csr\",\n                )\n        return self._average_cell_to_face\n\n    @property\n    def average_cell_vector_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_cell_vector_to_face\", None) is None:\n            if self.dim == 1:\n                self._average_cell_vector_to_face = self.aveCC2F\n            elif self.dim == 2:\n                aveCCV2Fx = sp.kron(\n                    speye(self.shape_cells[1]), av_extrap(self.shape_cells[0])\n                )\n                aveCC2VFy = sp.kron(\n                    av_extrap(self.shape_cells[1]), speye(self.shape_cells[0])\n                )\n                self._average_cell_vector_to_face = sp.block_diag(\n                    (aveCCV2Fx, aveCC2VFy), format=\"csr\"\n                )\n            elif self.dim == 3:\n                aveCCV2Fx = kron3(\n                    speye(self.shape_cells[2]),\n                    speye(self.shape_cells[1]),\n                    av_extrap(self.shape_cells[0]),\n                )\n                aveCC2VFy = kron3(\n                    speye(self.shape_cells[2]),\n                    av_extrap(self.shape_cells[1]),\n                    speye(self.shape_cells[0]),\n                )\n                aveCC2BFz = kron3(\n                    av_extrap(self.shape_cells[2]),\n                    speye(self.shape_cells[1]),\n                    speye(self.shape_cells[0]),\n                )\n                self._average_cell_vector_to_face = sp.block_diag(\n                    (aveCCV2Fx, aveCC2VFy, aveCC2BFz), format=\"csr\"\n                )\n        return self._average_cell_vector_to_face\n\n    @property\n    def average_cell_to_edge(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_cell_to_edge\", None) is None:\n            n = self.shape_cells\n            if self.dim == 1:\n                avg = sp.eye(n[0])\n            elif self.dim == 2:\n                avg = sp.vstack(\n                    (\n                        sp.kron(av_extrap(n[1]), speye(n[0])),\n                        sp.kron(speye(n[1]), av_extrap(n[0])),\n                    ),\n                    format=\"csr\",\n                )\n            elif self.dim == 3:\n                avg = sp.vstack(\n                    (\n                        kron3(av_extrap(n[2]), av_extrap(n[1]), speye(n[0])),\n                        kron3(av_extrap(n[2]), speye(n[1]), av_extrap(n[0])),\n                        kron3(speye(n[2]), av_extrap(n[1]), av_extrap(n[0])),\n                    ),\n                    format=\"csr\",\n                )\n            self._average_cell_to_edge = avg\n        return self._average_cell_to_edge\n\n    @property\n    def average_edge_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_edge_to_cell\", None) is None:\n            if self.dim == 1:\n                self._avE2CC = self.aveEx2CC\n            elif self.dim == 2:\n                self._avE2CC = 0.5 * sp.hstack(\n                    (self.aveEx2CC, self.aveEy2CC), format=\"csr\"\n                )\n            elif self.dim == 3:\n                self._avE2CC = (1.0 / 3) * sp.hstack(\n                    (self.aveEx2CC, self.aveEy2CC, self.aveEz2CC), format=\"csr\"\n                )\n        return self._avE2CC\n\n    @property\n    def average_edge_to_cell_vector(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_edge_to_cell_vector\", None) is None:\n            if self.dim == 1:\n                self._average_edge_to_cell_vector = self.aveEx2CC\n            elif self.dim == 2:\n                self._average_edge_to_cell_vector = sp.block_diag(\n                    (self.aveEx2CC, self.aveEy2CC), format=\"csr\"\n                )\n            elif self.dim == 3:\n                self._average_edge_to_cell_vector = sp.block_diag(\n                    (self.aveEx2CC, self.aveEy2CC, self.aveEz2CC), format=\"csr\"\n                )\n        return self._average_edge_to_cell_vector\n\n    @property\n    def average_edge_x_to_cell(self):\n        r\"\"\"Averaging operator from x-edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from x-edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on x-edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges_x) scipy.sparse.csr_matrix\n            The scalar averaging operator from x-edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_x}` be a discrete scalar quantity that\n        lives on x-edges. **average_edge_x_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{xc}}` that projects\n        :math:`\\boldsymbol{\\phi_x}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{xc}} \\, \\boldsymbol{\\phi_x}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its x-edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Axc @ phi_x\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the x-edges. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a scalar variable on x-edges,\n\n        >>> phi_x = np.zeros(mesh.nEx)\n        >>> xy = mesh.edges_x\n        >>> phi_x[(xy[:, 1] > 0)] = 25.0\n        >>> phi_x[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n\n        >>> Axc = mesh.average_edge_x_to_cell\n        >>> phi_c = Axc @ phi_x\n\n        And plot the results,\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> v = np.r_[phi_x, np.zeros(mesh.nEy)]  # create vector for plotting function\n        >>> mesh.plot_image(v, ax=ax1, v_type=\"Ex\")\n        >>> ax1.set_title(\"Variable at x-edges\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Axc, ms=1)\n        >>> ax1.set_title(\"X-Edge Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if getattr(self, \"_average_edge_x_to_cell\", None) is None:\n            # The number of cell centers in each direction\n            n = self.vnC\n            if self.dim == 1:\n                self._average_edge_x_to_cell = speye(n[0])\n            elif self.dim == 2:\n                self._average_edge_x_to_cell = sp.kron(av(n[1]), speye(n[0]))\n            elif self.dim == 3:\n                self._average_edge_x_to_cell = kron3(av(n[2]), av(n[1]), speye(n[0]))\n        return self._average_edge_x_to_cell\n\n    @property\n    def average_edge_y_to_cell(self):\n        r\"\"\"Averaging operator from y-edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from y-edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on y-edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges_y) scipy.sparse.csr_matrix\n            The scalar averaging operator from y-edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_y}` be a discrete scalar quantity that\n        lives on y-edges. **average_edge_y_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{yc}}` that projects\n        :math:`\\boldsymbol{\\phi_y}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{yc}} \\, \\boldsymbol{\\phi_y}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its y-edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Ayc @ phi_y\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the y-edges. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h], x0=\"CC\")\n\n        Then we create a scalar variable on y-edges,\n\n        >>> phi_y = np.zeros(mesh.nEy)\n        >>> xy = mesh.edges_y\n        >>> phi_y[(xy[:, 1] > 0)] = 25.0\n        >>> phi_y[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n\n        >>> Ayc = mesh.average_edge_y_to_cell\n        >>> phi_c = Ayc @ phi_y\n\n        And plot the results,\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> v = np.r_[np.zeros(mesh.nEx), phi_y]  # create vector for plotting function\n        >>> mesh.plot_image(v, ax=ax1, v_type=\"Ey\")\n        >>> ax1.set_title(\"Variable at y-edges\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Ayc, ms=1)\n        >>> ax1.set_title(\"Y-Edge Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 2:\n            return None\n        if getattr(self, \"_average_edge_y_to_cell\", None) is None:\n            # The number of cell centers in each direction\n            n = self.vnC\n            if self.dim == 2:\n                self._average_edge_y_to_cell = sp.kron(speye(n[1]), av(n[0]))\n            elif self.dim == 3:\n                self._average_edge_y_to_cell = kron3(av(n[2]), speye(n[1]), av(n[0]))\n        return self._average_edge_y_to_cell\n\n    @property\n    def average_edge_z_to_cell(self):\n        r\"\"\"Averaging operator from z-edges to cell centers (scalar quantities).\n\n        This property constructs a 2nd order averaging operator that maps scalar\n        quantities from z-edges to cell centers. This averaging operator is\n        used when a discrete scalar quantity defined on z-edges must be\n        projected to cell centers. Once constructed, the operator is stored\n        permanently as a property of the mesh. *See notes*.\n\n        Returns\n        -------\n        (n_cells, n_edges_z) scipy.sparse.csr_matrix\n            The scalar averaging operator from z-edges to cell centers\n\n        Notes\n        -----\n        Let :math:`\\boldsymbol{\\phi_z}` be a discrete scalar quantity that\n        lives on z-edges. **average_edge_z_to_cell** constructs a discrete\n        linear operator :math:`\\mathbf{A_{zc}}` that projects\n        :math:`\\boldsymbol{\\phi_z}` to cell centers, i.e.:\n\n        .. math::\n            \\boldsymbol{\\phi_c} = \\mathbf{A_{zc}} \\, \\boldsymbol{\\phi_z}\n\n        where :math:`\\boldsymbol{\\phi_c}` approximates the value of the scalar\n        quantity at cell centers. For each cell, we are simply averaging\n        the values defined on its z-edges. The operation is implemented as a\n        matrix vector product, i.e.::\n\n            phi_c = Azc @ phi_z\n\n        Examples\n        --------\n        Here we compute the values of a scalar function on the z-edges. We then create\n        an averaging operator to approximate the function at cell centers. We choose\n        to define a scalar function that is strongly discontinuous in some places to\n        demonstrate how the averaging operator will smooth out discontinuities.\n\n        We start by importing the necessary packages and defining a mesh.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> h = np.ones(40)\n        >>> mesh = TensorMesh([h, h, h], x0=\"CCC\")\n\n        The we create a scalar variable on z-edges,\n\n        >>> phi_z = np.zeros(mesh.nEz)\n        >>> xyz = mesh.edges_z\n        >>> phi_z[(xyz[:, 2] > 0)] = 25.0\n        >>> phi_z[(xyz[:, 2] < -10.0) & (xyz[:, 0] > -10.0) & (xyz[:, 0] < 10.0)] = 50.0\n\n        Next, we construct the averaging operator and apply it to\n        the discrete scalar quantity to approximate the value at cell centers.\n        We plot the original scalar and its average at cell centers for a\n        slice at y=0.\n\n        >>> Azc = mesh.average_edge_z_to_cell\n        >>> phi_c = Azc @ phi_z\n\n        Plot the results,\n\n        >>> fig = plt.figure(figsize=(11, 5))\n        >>> ax1 = fig.add_subplot(121)\n        >>> v = np.r_[np.zeros(mesh.nEx+mesh.nEy), phi_z]  # create vector for plotting\n        >>> mesh.plot_slice(v, ax=ax1, normal='Y', slice_loc=0, v_type=\"Ez\")\n        >>> ax1.set_title(\"Variable at z-edges\", fontsize=16)\n        >>> ax2 = fig.add_subplot(122)\n        >>> mesh.plot_image(phi_c, ax=ax2, normal='Y', slice_loc=0, v_type=\"CC\")\n        >>> ax2.set_title(\"Averaged to cell centers\", fontsize=16)\n        >>> plt.show()\n\n        Below, we show a spy plot illustrating the sparsity and mapping\n        of the operator\n\n        >>> fig = plt.figure(figsize=(9, 9))\n        >>> ax1 = fig.add_subplot(111)\n        >>> ax1.spy(Azc, ms=1)\n        >>> ax1.set_title(\"Z-Edge Index\", fontsize=12, pad=5)\n        >>> ax1.set_ylabel(\"Cell Index\", fontsize=12)\n        >>> plt.show()\n        \"\"\"\n        if self.dim < 3:\n            return None\n        if getattr(self, \"_average_edge_z_to_cell\", None) is None:\n            # The number of cell centers in each direction\n            n = self.vnC\n            if self.dim == 3:\n                self._average_edge_z_to_cell = kron3(speye(n[2]), av(n[1]), av(n[0]))\n        return self._average_edge_z_to_cell\n\n    @property\n    def average_edge_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 1:\n            return self.average_cell_to_face\n        elif self.dim == 2:\n            return sp.diags(\n                [1, 1],\n                [-self.n_faces_x, self.n_faces_y],\n                shape=(self.n_faces, self.n_edges),\n            )\n        n1, n2, n3 = self.shape_cells\n        ex_to_fy = kron3(av(n3), speye(n2 + 1), speye(n1))\n        ex_to_fz = kron3(speye(n3 + 1), av(n2), speye(n1))\n\n        ey_to_fx = kron3(av(n3), speye(n2), speye(n1 + 1))\n        ey_to_fz = kron3(speye(n3 + 1), speye(n2), av(n1))\n\n        ez_to_fx = kron3(speye(n3), av(n2), speye(n1 + 1))\n        ez_to_fy = kron3(speye(n3), speye(n2 + 1), av(n1))\n\n        e_to_f = 0.5 * sp.bmat(\n            [\n                [None, ey_to_fx, ez_to_fx],\n                [ex_to_fy, None, ez_to_fy],\n                [ex_to_fz, ey_to_fz, None],\n            ],\n            format=\"csr\",\n        )\n        return e_to_f\n\n    @property\n    def average_node_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_cell\", None) is None:\n            # The number of cell centers in each direction\n            if self.dim == 1:\n                self._average_node_to_cell = av(self.shape_cells[0])\n            elif self.dim == 2:\n                self._average_node_to_cell = sp.kron(\n                    av(self.shape_cells[1]), av(self.shape_cells[0])\n                ).tocsr()\n            elif self.dim == 3:\n                self._average_node_to_cell = kron3(\n                    av(self.shape_cells[2]),\n                    av(self.shape_cells[1]),\n                    av(self.shape_cells[0]),\n                ).tocsr()\n        return self._average_node_to_cell\n\n    @property\n    def _average_node_to_edge_x(self):\n        \"\"\"Averaging operator on cell nodes to x-edges.\"\"\"\n        if self.dim == 1:\n            aveN2Ex = av(self.shape_cells[0])\n        elif self.dim == 2:\n            aveN2Ex = sp.kron(speye(self.shape_nodes[1]), av(self.shape_cells[0]))\n        elif self.dim == 3:\n            aveN2Ex = kron3(\n                speye(self.shape_nodes[2]),\n                speye(self.shape_nodes[1]),\n                av(self.shape_cells[0]),\n            )\n        return aveN2Ex\n\n    @property\n    def _average_node_to_edge_y(self):\n        \"\"\"Averaging operator on cell nodes to y-edges.\"\"\"\n        if self.dim == 1:\n            return None\n        elif self.dim == 2:\n            aveN2Ey = sp.kron(av(self.shape_cells[1]), speye(self.shape_nodes[0]))\n        elif self.dim == 3:\n            aveN2Ey = kron3(\n                speye(self.shape_nodes[2]),\n                av(self.shape_cells[1]),\n                speye(self.shape_nodes[0]),\n            )\n        return aveN2Ey\n\n    @property\n    def _average_node_to_edge_z(self):\n        \"\"\"Averaging operator on cell nodes to z-edges.\"\"\"\n        if self.dim == 1 or self.dim == 2:\n            return None\n        elif self.dim == 3:\n            aveN2Ez = kron3(\n                av(self.shape_cells[2]),\n                speye(self.shape_nodes[1]),\n                speye(self.shape_nodes[0]),\n            )\n        return aveN2Ez\n\n    @property\n    def average_node_to_edge(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_edge\", None) is None:\n            # The number of cell centers in each direction\n            if self.dim == 1:\n                self._average_node_to_edge = self._average_node_to_edge_x\n            elif self.dim == 2:\n                self._average_node_to_edge = sp.vstack(\n                    (self._average_node_to_edge_x, self._average_node_to_edge_y),\n                    format=\"csr\",\n                )\n            elif self.dim == 3:\n                self._average_node_to_edge = sp.vstack(\n                    (\n                        self._average_node_to_edge_x,\n                        self._average_node_to_edge_y,\n                        self._average_node_to_edge_z,\n                    ),\n                    format=\"csr\",\n                )\n        return self._average_node_to_edge\n\n    @property\n    def _average_node_to_face_x(self):\n        if self.dim == 1:\n            aveN2Fx = speye(self.shape_nodes[0])\n        elif self.dim == 2:\n            aveN2Fx = sp.kron(av(self.shape_cells[1]), speye(self.shape_nodes[0]))\n        elif self.dim == 3:\n            aveN2Fx = kron3(\n                av(self.shape_cells[2]),\n                av(self.shape_cells[1]),\n                speye(self.shape_nodes[0]),\n            )\n        return aveN2Fx\n\n    @property\n    def _average_node_to_face_y(self):\n        if self.dim == 1:\n            return None\n        elif self.dim == 2:\n            aveN2Fy = sp.kron(speye(self.shape_nodes[1]), av(self.shape_cells[0]))\n        elif self.dim == 3:\n            aveN2Fy = kron3(\n                av(self.shape_cells[2]),\n                speye(self.shape_nodes[1]),\n                av(self.shape_cells[0]),\n            )\n        return aveN2Fy\n\n    @property\n    def _average_node_to_face_z(self):\n        if self.dim == 1 or self.dim == 2:\n            return None\n        else:\n            aveN2Fz = kron3(\n                speye(self.shape_nodes[2]),\n                av(self.shape_cells[1]),\n                av(self.shape_cells[0]),\n            )\n        return aveN2Fz\n\n    @property\n    def average_node_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_face\", None) is None:\n            # The number of cell centers in each direction\n            if self.dim == 1:\n                self._average_node_to_face = self._average_node_to_face_x\n            elif self.dim == 2:\n                self._average_node_to_face = sp.vstack(\n                    (self._average_node_to_face_x, self._average_node_to_face_y),\n                    format=\"csr\",\n                )\n            elif self.dim == 3:\n                self._average_node_to_face = sp.vstack(\n                    (\n                        self._average_node_to_face_x,\n                        self._average_node_to_face_y,\n                        self._average_node_to_face_z,\n                    ),\n                    format=\"csr\",\n                )\n        return self._average_node_to_face\n\n    @property\n    def project_face_to_boundary_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        # Simple matrix which projects the values of the faces onto the boundary faces\n        # Can also be used to \"select\" the boundary faces\n\n        # Create a matrix that projects all faces onto boundary faces\n        # The below should work for a regular structured mesh\n        is_b = make_boundary_bool(self.shape_faces_x, bdir=\"x\")\n        if self.dim > 1:\n            is_b = np.r_[is_b, make_boundary_bool(self.shape_faces_y, bdir=\"y\")]\n        if self.dim == 3:\n            is_b = np.r_[is_b, make_boundary_bool(self.shape_faces_z, bdir=\"z\")]\n        return sp.eye(self.n_faces, format=\"csr\")[is_b]\n\n    @property\n    def project_edge_to_boundary_edge(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        # Simple matrix which projects the values of the faces onto the boundary faces\n        # Can also be used to \"select\" the boundary faces\n\n        # Create a matrix that projects all edges onto boundary edges\n        # The below should work for a regular structured mesh\n        if self.dim == 1:\n            return None  # No edges are on the boundary in 1D\n\n        is_b = np.r_[\n            make_boundary_bool(self.shape_edges_x, bdir=\"yz\"),\n            make_boundary_bool(self.shape_edges_y, bdir=\"xz\"),\n        ]\n        if self.dim == 3:\n            is_b = np.r_[is_b, make_boundary_bool(self.shape_edges_z, bdir=\"xy\")]\n        return sp.eye(self.n_edges, format=\"csr\")[is_b]\n\n    @property\n    def project_node_to_boundary_node(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        # Simple matrix which projects the values of the nodes onto the boundary nodes\n        # Can also be used to \"select\" the boundary nodes\n\n        # Create a matrix that projects all nodes onto boundary nodes\n        # The below should work for a regular structured mesh\n\n        is_b = make_boundary_bool(self.shape_nodes)\n        return sp.eye(self.n_nodes, format=\"csr\")[is_b]\n\n    # DEPRECATED\n    cellGrad = deprecate_property(\n        \"cell_gradient\", \"cellGrad\", removal_version=\"1.0.0\", error=True\n    )\n    cellGradBC = deprecate_property(\n        \"cell_gradient_BC\", \"cellGradBC\", removal_version=\"1.0.0\", error=True\n    )\n    cellGradx = deprecate_property(\n        \"cell_gradient_x\", \"cellGradx\", removal_version=\"1.0.0\", error=True\n    )\n    cellGrady = deprecate_property(\n        \"cell_gradient_y\", \"cellGrady\", removal_version=\"1.0.0\", error=True\n    )\n    cellGradz = deprecate_property(\n        \"cell_gradient_z\", \"cellGradz\", removal_version=\"1.0.0\", error=True\n    )\n    faceDivx = deprecate_property(\n        \"face_x_divergence\", \"faceDivx\", removal_version=\"1.0.0\", error=True\n    )\n    faceDivy = deprecate_property(\n        \"face_y_divergence\", \"faceDivy\", removal_version=\"1.0.0\", error=True\n    )\n    faceDivz = deprecate_property(\n        \"face_z_divergence\", \"faceDivz\", removal_version=\"1.0.0\", error=True\n    )\n    _cellGradStencil = deprecate_property(\n        \"stencil_cell_gradient\",\n        \"_cellGradStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _cellGradxStencil = deprecate_property(\n        \"stencil_cell_gradient_x\",\n        \"_cellGradxStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _cellGradyStencil = deprecate_property(\n        \"stencil_cell_gradient_y\",\n        \"_cellGradyStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _cellGradzStencil = deprecate_property(\n        \"stencil_cell_gradient_z\",\n        \"_cellGradzStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n\n    setCellGradBC = deprecate_method(\n        \"set_cell_gradient_BC\",\n        \"setCellGradBC\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    getBCProjWF = deprecate_method(\n        \"get_BC_projections\", \"getBCProjWF\", removal_version=\"1.0.0\", error=True\n    )\n    getBCProjWF_simple = deprecate_method(\n        \"get_BC_projections_simple\",\n        \"getBCProjWF_simple\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n"
  },
  {
    "path": "discretize/operators/inner_products.py",
    "content": "\"\"\"Construct inner product operators for tensor like meshes.\"\"\"\n\nfrom scipy import sparse as sp\nfrom discretize.base import BaseMesh\nfrom discretize.utils import (\n    sub2ind,\n    sdiag,\n    inverse_property_tensor,\n    TensorType,\n    make_property_tensor,\n    ndgrid,\n    inverse_2x2_block_diagonal,\n    get_subarray,\n    inverse_3x3_block_diagonal,\n    spzeros,\n    sdinv,\n    mkvc,\n    is_scalar,\n)\nimport numpy as np\n\n\nclass InnerProducts(BaseMesh):\n    \"\"\"Class for constructing inner product matrices.\n\n    ``InnerProducts`` is a mixin class for constructing inner product matrices,\n    their inverses and their derivatives with respect to model parameters.\n    The ``InnerProducts`` class is inherited by all ``discretize`` mesh classes.\n    In practice, we don't create instances of the ``InnerProducts`` class in\n    order to construct inner product matrices, their inverses or their derivatives.\n    These quantities are instead constructed from instances of ``discretize``\n    meshes using the appropriate method.\n    \"\"\"\n\n    def get_face_inner_product(  # NOQA D102\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        do_fast=True,\n        **kwargs,\n    ):\n        # Inherited documentation from discretize.base.BaseMesh\n        if \"invProp\" in kwargs:\n            raise TypeError(\n                \"The invProp keyword argument has been removed, please use invert_model. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"invMat\" in kwargs:\n            raise TypeError(\n                \"The invMat keyword argument has been removed, please use invert_matrix. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"doFast\" in kwargs:\n            raise TypeError(\n                \"The doFast keyword argument has been removed, please use do_fast. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n            do_fast = kwargs[\"doFast\"]\n\n        return self._getInnerProduct(\n            \"F\",\n            model=model,\n            invert_model=invert_model,\n            invert_matrix=invert_matrix,\n            do_fast=do_fast,\n        )\n\n    def get_edge_inner_product(  # NOQA D102\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        do_fast=True,\n        **kwargs,\n    ):\n        # Inherited documentation from discretize.base.BaseMesh\n        if \"invProp\" in kwargs:\n            raise TypeError(\n                \"The invProp keyword argument has been removed, please use invert_model. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"invMat\" in kwargs:\n            raise TypeError(\n                \"The invMat keyword argument has been removed, please use invert_matrix. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"doFast\" in kwargs:\n            raise TypeError(\n                \"The doFast keyword argument has been removed, please use do_fast. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        return self._getInnerProduct(\n            \"E\",\n            model=model,\n            invert_model=invert_model,\n            invert_matrix=invert_matrix,\n            do_fast=do_fast,\n        )\n\n    def get_edge_inner_product_surface(  # NOQA D102\n        self, model=None, invert_model=False, invert_matrix=False, **kwargs\n    ):\n        # Inherited documentation from discretize.base.BaseMesh\n        if model is None:\n            model = np.ones(self.nF)\n\n        if invert_model:\n            model = 1.0 / model\n\n        if is_scalar(model):\n            model = model * np.ones(self.nF)\n        # COULD ADD THIS CASE IF DESIRED\n        # elif len(model) == self.dim:\n        #     model = np.r_[\n        #         [model[ii]*self.vnF[ii] for ii in range(0, self.dim)]\n        #     ]\n\n        # Isotropic case only\n        if model.size != self.nF:\n            raise ValueError(\n                \"Unexpected shape of tensor: {}\".format(model.shape),\n                \"Must be scalar or have length equal to total number of faces.\",\n            )\n\n        # number of elements we are averaging (equals dim for regular\n        # meshes, but for cyl, where we use symmetry, it is 1 for edge\n        # variables and 2 for face variables)\n        if self._meshType == \"CYL\":\n            shape = self.vnE\n            if self.is_symmetric:\n                n_elements = 1\n            else:\n                n_elements = sum([1 if x != 0 else 0 for x in shape]) - 1\n        else:\n            n_elements = self.dim - 1\n\n        Aprop = self.face_areas * mkvc(model)\n        Av = self.average_edge_to_face\n        M = n_elements * sdiag(Av.T * Aprop)\n\n        if invert_matrix:\n            return sdinv(M)\n        else:\n            return M\n\n    def _getInnerProduct(\n        self,\n        projection_type,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        do_fast=True,\n        **kwargs,\n    ):\n        \"\"\"Get the inner product matrix.\n\n        Parameters\n        ----------\n        str : projection_type\n            'F' for faces 'E' for edges\n        numpy.ndarray : model\n            material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6))\n        bool : invert_model\n            inverts the material property\n        bool : invert_matrix\n            inverts the matrix\n        bool : do_fast\n            do a faster implementation if available.\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            M, the inner product matrix (nE, nE)\n        \"\"\"\n        if \"invProp\" in kwargs:\n            raise TypeError(\n                \"The invProp keyword argument has been removed, please use invert_model. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"invMat\" in kwargs:\n            raise TypeError(\n                \"The invMat keyword argument has been removed, please use invert_matrix. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"doFast\" in kwargs:\n            raise TypeError(\n                \"The doFast keyword argument has been removed, please use do_fast. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if projection_type not in [\"F\", \"E\"]:\n            raise TypeError(\"projection_type must be 'F' for faces or 'E' for edges\")\n\n        fast = None\n        if hasattr(self, \"_fastInnerProduct\") and do_fast:\n            fast = self._fastInnerProduct(\n                projection_type,\n                model=model,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n        if fast is not None:\n            return fast\n\n        if invert_model:\n            model = inverse_property_tensor(self, model)\n\n        tensorType = TensorType(self, model)\n\n        Mu = make_property_tensor(self, model)\n        Ps = self._getInnerProductProjectionMatrices(projection_type, tensorType)\n        A = np.sum([P.T * Mu * P for P in Ps])\n\n        if invert_matrix and tensorType < 3:\n            A = sdinv(A)\n        elif invert_matrix and tensorType == 3:\n            raise Exception(\"Solver needed to invert A.\")\n\n        return A\n\n    def _getInnerProductProjectionMatrices(self, projection_type, tensorType):\n        \"\"\"Get the inner product projection matrices.\n\n        Parameters\n        ----------\n        projection_type : str\n            'F' for faces 'E' for edges\n        tensorType : TensorType\n            type of the tensor: TensorType(mesh, sigma)\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n        \"\"\"\n        if not isinstance(tensorType, TensorType):\n            raise TypeError(\"tensorType must be an instance of TensorType.\")\n        if projection_type not in [\"F\", \"E\"]:\n            raise TypeError(\"projection_type must be 'F' for faces or 'E' for edges\")\n\n        d = self.dim\n        # We will multiply by sqrt on each side to keep symmetry\n        V = sp.kron(sp.identity(d), sdiag(np.sqrt((2 ** (-d)) * self.cell_volumes)))\n\n        nodes = [\"000\", \"100\", \"010\", \"110\", \"001\", \"101\", \"011\", \"111\"][: 2**d]\n\n        if projection_type == \"F\":\n            locs = {\n                \"000\": [(\"fXm\",), (\"fXm\", \"fYm\"), (\"fXm\", \"fYm\", \"fZm\")],\n                \"100\": [(\"fXp\",), (\"fXp\", \"fYm\"), (\"fXp\", \"fYm\", \"fZm\")],\n                \"010\": [None, (\"fXm\", \"fYp\"), (\"fXm\", \"fYp\", \"fZm\")],\n                \"110\": [None, (\"fXp\", \"fYp\"), (\"fXp\", \"fYp\", \"fZm\")],\n                \"001\": [None, None, (\"fXm\", \"fYm\", \"fZp\")],\n                \"101\": [None, None, (\"fXp\", \"fYm\", \"fZp\")],\n                \"011\": [None, None, (\"fXm\", \"fYp\", \"fZp\")],\n                \"111\": [None, None, (\"fXp\", \"fYp\", \"fZp\")],\n            }\n            proj = getattr(self, \"_getFaceP\" + (\"x\" * d))()\n\n        elif projection_type == \"E\":\n            locs = {\n                \"000\": [(\"eX0\",), (\"eX0\", \"eY0\"), (\"eX0\", \"eY0\", \"eZ0\")],\n                \"100\": [(\"eX0\",), (\"eX0\", \"eY1\"), (\"eX0\", \"eY1\", \"eZ1\")],\n                \"010\": [None, (\"eX1\", \"eY0\"), (\"eX1\", \"eY0\", \"eZ2\")],\n                \"110\": [None, (\"eX1\", \"eY1\"), (\"eX1\", \"eY1\", \"eZ3\")],\n                \"001\": [None, None, (\"eX2\", \"eY2\", \"eZ0\")],\n                \"101\": [None, None, (\"eX2\", \"eY3\", \"eZ1\")],\n                \"011\": [None, None, (\"eX3\", \"eY2\", \"eZ2\")],\n                \"111\": [None, None, (\"eX3\", \"eY3\", \"eZ3\")],\n            }\n            proj = getattr(self, \"_getEdgeP\" + (\"x\" * d))()\n\n        return [V * proj(*locs[node][d - 1]) for node in nodes]\n\n    def get_face_inner_product_deriv(  # NOQA D102\n        self, model, do_fast=True, invert_model=False, invert_matrix=False, **kwargs\n    ):\n        # Inherited documentation from discretize.base.BaseMesh\n        if \"invProp\" in kwargs:\n            raise TypeError(\n                \"The invProp keyword argument has been removed, please use invert_model. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"invMat\" in kwargs:\n            raise TypeError(\n                \"The invMat keyword argument has been removed, please use invert_matrix. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"doFast\" in kwargs:\n            raise TypeError(\n                \"The doFast keyword argument has been removed, please use do_fast. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        return self._getInnerProductDeriv(\n            model,\n            \"F\",\n            do_fast=do_fast,\n            invert_model=invert_model,\n            invert_matrix=invert_matrix,\n        )\n\n    def get_edge_inner_product_deriv(  # NOQA D102\n        self, model, do_fast=True, invert_model=False, invert_matrix=False, **kwargs\n    ):\n        # Inherited documentation from discretize.base.BaseMesh\n        if \"invProp\" in kwargs:\n            raise TypeError(\n                \"The invProp keyword argument has been removed, please use invert_model. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"invMat\" in kwargs:\n            raise TypeError(\n                \"The invMat keyword argument has been removed, please use invert_matrix. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        if \"doFast\" in kwargs:\n            raise TypeError(\n                \"The doFast keyword argument has been removed, please use do_fast. \"\n                \"This will be removed in discretize 1.0.0\",\n            )\n        return self._getInnerProductDeriv(\n            model,\n            \"E\",\n            do_fast=do_fast,\n            invert_model=invert_model,\n            invert_matrix=invert_matrix,\n        )\n\n    def get_edge_inner_product_surface_deriv(  # NOQA D102\n        self, model, invert_model=False, invert_matrix=False, **kwargs\n    ):\n        # Inherited documentation from discretize.base.BaseMesh\n        if model is None:\n            tensorType = -1\n        elif is_scalar(model):\n            tensorType = 0\n        elif model.size == self.nF:\n            tensorType = 1\n        else:\n            raise ValueError(\n                \"Unexpected shape of tensor: {}\".format(model.shape),\n                \"Must be scalar or have length equal to total number of faces.\",\n            )\n\n        dMdprop = None\n\n        if invert_matrix or invert_model:\n            MI = self.get_edge_inner_product_surface(\n                model,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n\n        # number of elements we are averaging (equals dim for regular\n        # meshes, but for cyl, where we use symmetry, it is 1 for edge\n        # variables and 2 for face variables)\n        if self._meshType == \"CYL\":\n            shape = self.vnE\n            if self.is_symmetric:\n                n_elements = 1\n            else:\n                n_elements = sum([1 if x != 0 else 0 for x in shape]) - 1\n        else:\n            n_elements = self.dim - 1\n\n        A = sdiag(self.face_areas)\n        Av = self.average_edge_to_face\n\n        if tensorType == 0:  # isotropic, constant\n            ones = sp.csr_matrix(\n                (np.ones(self.nF), (range(self.nF), np.zeros(self.nF))),\n                shape=(self.nF, 1),\n            )\n            if not invert_matrix and not invert_model:\n                dMdprop = n_elements * Av.T * A * ones\n            elif invert_matrix and invert_model:\n                dMdprop = n_elements * (\n                    sdiag(MI.diagonal() ** 2) * Av.T * A * ones * sdiag(1.0 / model**2)\n                )\n            elif invert_model:\n                dMdprop = n_elements * Av.T * A * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = n_elements * (sdiag(-MI.diagonal() ** 2) * Av.T * A)\n\n        elif tensorType == 1:  # isotropic, variable in space\n            if not invert_matrix and not invert_model:\n                dMdprop = n_elements * Av.T * A\n            elif invert_matrix and invert_model:\n                dMdprop = n_elements * (\n                    sdiag(MI.diagonal() ** 2) * Av.T * A * sdiag(1.0 / model**2)\n                )\n            elif invert_model:\n                dMdprop = n_elements * Av.T * A * sdiag(-1.0 / model**2)\n            elif invert_matrix:\n                dMdprop = n_elements * (sdiag(-MI.diagonal() ** 2) * Av.T * A)\n\n        if dMdprop is not None:\n\n            def innerProductDeriv(v):\n                return sdiag(v) * dMdprop\n\n            return innerProductDeriv\n        else:\n            return None\n\n    def _getInnerProductDeriv(\n        self,\n        model,\n        projection_type,\n        do_fast=True,\n        invert_model=False,\n        invert_matrix=False,\n    ):\n        \"\"\"Get the inner product projection derivative function.\n\n        Parameters\n        ----------\n        model : numpy.ndarray\n            material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6))\n        projection_type : str\n            'F' for faces 'E' for edges\n        do_fast : bool\n            do a faster implementation if available.\n        invert_model : bool\n            inverts the material property\n        invert_matrix : bool\n            inverts the matrix\n\n        Returns\n        -------\n        callable\n            dMdm, the derivative of the inner product matrix (nE, nC*nA)\n        \"\"\"\n        fast = None\n        if hasattr(self, \"_fastInnerProductDeriv\") and do_fast:\n            fast = self._fastInnerProductDeriv(\n                projection_type,\n                model,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n        if fast is not None:\n            return fast\n\n        if invert_model or invert_matrix:\n            raise NotImplementedError(\n                \"inverting the property or the matrix is not yet implemented for this mesh/tensorType. You should write it!\"\n            )\n\n        tensorType = TensorType(self, model)\n        P = self._getInnerProductProjectionMatrices(\n            projection_type, tensorType=tensorType\n        )\n\n        def innerProductDeriv(v):\n            return self._getInnerProductDerivFunction(tensorType, P, projection_type, v)\n\n        return innerProductDeriv\n\n    def _getInnerProductDerivFunction(self, tensorType, P, projection_type, v):\n        \"\"\"Get the inner product projection derivative function depending on the tensor type.\n\n        Parameters\n        ----------\n        model : numpy.ndarray\n            material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6))\n        v : numpy.ndarray\n            vector to multiply (required in the general implementation)\n        P : list\n            list of projection matrices\n        projection_type : str\n            'F' for faces 'E' for edges\n\n        Returns\n        -------\n        scipy.sparse.csr_matrix\n            dMdm, the derivative of the inner product matrix (n, nC*nA)\n        \"\"\"\n        if projection_type not in [\"F\", \"E\"]:\n            raise TypeError(\"projection_type must be 'F' for faces or 'E' for edges\")\n\n        n = getattr(self, \"n\" + projection_type)\n\n        if tensorType == -1:\n            return None\n\n        if v is None:\n            raise Exception(\"v must be supplied for this implementation.\")\n\n        d = self.dim\n        Z = spzeros(self.nC, self.nC)\n\n        if tensorType == 0:\n            dMdm = spzeros(n, 1)\n            for p in P:\n                dMdm = dMdm + sp.csr_matrix(\n                    (p.T * (p * v), (range(n), np.zeros(n))), shape=(n, 1)\n                )\n        if d == 1:\n            if tensorType == 1:\n                dMdm = spzeros(n, self.nC)\n                for p in P:\n                    dMdm = dMdm + p.T * sdiag(p * v)\n        elif d == 2:\n            if tensorType == 1:\n                dMdm = spzeros(n, self.nC)\n                for p in P:\n                    Y = p * v\n                    y1 = Y[: self.nC]\n                    y2 = Y[self.nC :]\n                    dMdm = dMdm + p.T * sp.vstack((sdiag(y1), sdiag(y2)))\n            elif tensorType == 2:\n                dMdms = [spzeros(n, self.nC) for _ in range(2)]\n                for p in P:\n                    Y = p * v\n                    y1 = Y[: self.nC]\n                    y2 = Y[self.nC :]\n                    dMdms[0] = dMdms[0] + p.T * sp.vstack((sdiag(y1), Z))\n                    dMdms[1] = dMdms[1] + p.T * sp.vstack((Z, sdiag(y2)))\n                dMdm = sp.hstack(dMdms)\n            elif tensorType == 3:\n                dMdms = [spzeros(n, self.nC) for _ in range(3)]\n                for p in P:\n                    Y = p * v\n                    y1 = Y[: self.nC]\n                    y2 = Y[self.nC :]\n                    dMdms[0] = dMdms[0] + p.T * sp.vstack((sdiag(y1), Z))\n                    dMdms[1] = dMdms[1] + p.T * sp.vstack((Z, sdiag(y2)))\n                    dMdms[2] = dMdms[2] + p.T * sp.vstack((sdiag(y2), sdiag(y1)))\n                dMdm = sp.hstack(dMdms)\n        elif d == 3:\n            if tensorType == 1:\n                dMdm = spzeros(n, self.nC)\n                for p in P:\n                    Y = p * v\n                    y1 = Y[: self.nC]\n                    y2 = Y[self.nC : self.nC * 2]\n                    y3 = Y[self.nC * 2 :]\n                    dMdm = dMdm + p.T * sp.vstack((sdiag(y1), sdiag(y2), sdiag(y3)))\n            elif tensorType == 2:\n                dMdms = [spzeros(n, self.nC) for _ in range(3)]\n                for p in P:\n                    Y = p * v\n                    y1 = Y[: self.nC]\n                    y2 = Y[self.nC : self.nC * 2]\n                    y3 = Y[self.nC * 2 :]\n                    dMdms[0] = dMdms[0] + p.T * sp.vstack((sdiag(y1), Z, Z))\n                    dMdms[1] = dMdms[1] + p.T * sp.vstack((Z, sdiag(y2), Z))\n                    dMdms[2] = dMdms[2] + p.T * sp.vstack((Z, Z, sdiag(y3)))\n                dMdm = sp.hstack(dMdms)\n            elif tensorType == 3:\n                dMdms = [spzeros(n, self.nC) for _ in range(6)]\n                for p in P:\n                    Y = p * v\n                    y1 = Y[: self.nC]\n                    y2 = Y[self.nC : self.nC * 2]\n                    y3 = Y[self.nC * 2 :]\n                    dMdms[0] = dMdms[0] + p.T * sp.vstack((sdiag(y1), Z, Z))\n                    dMdms[1] = dMdms[1] + p.T * sp.vstack((Z, sdiag(y2), Z))\n                    dMdms[2] = dMdms[2] + p.T * sp.vstack((Z, Z, sdiag(y3)))\n                    dMdms[3] = dMdms[3] + p.T * sp.vstack((sdiag(y2), sdiag(y1), Z))\n                    dMdms[4] = dMdms[4] + p.T * sp.vstack((sdiag(y3), Z, sdiag(y1)))\n                    dMdms[5] = dMdms[5] + p.T * sp.vstack((Z, sdiag(y3), sdiag(y2)))\n                dMdm = sp.hstack(dMdms)\n\n        return dMdm\n\n    # ------------------------ Geometries ------------------------------\n    #\n    #\n    #         node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1)\n    #              /                                    /\n    #             /                                    / |\n    #         edge3(i,j,k)     face1(i,j,k)        edge3(i,j+1,k)\n    #           /                                    /   |\n    #          /                                    /    |\n    #    node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k)\n    #         |                                     |    |\n    #         |                                     |   node(i+1,j+1,k+1)\n    #         |                                     |    /\n    #    edge1(i,j,k)      face3(i,j,k)        edge1(i,j+1,k)\n    #         |                                     |  /\n    #         |                                     | /\n    #         |                                     |/\n    #    node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k)\n\n    def _getFacePx(M):\n        \"\"\"Return a function for creating face projection matrices in 1D.\"\"\"\n        ii = np.arange(M.shape_cells[0])\n\n        def Px(xFace):\n            # xFace is 'fXp' or 'fXm'\n            posFx = 0 if xFace == \"fXm\" else 1\n            IND = ii + posFx\n            PX = sp.csr_matrix((np.ones(M.nC), (range(M.nC), IND)), shape=(M.nC, M.nF))\n            return PX\n\n        return Px\n\n    def _getFacePxx(M):\n        \"\"\"Return a function for creating face projection matrices in 2D.\"\"\"\n        # returns a function for creating projection matrices\n        #\n        # Mats takes you from faces a subset of all faces on only the\n        # faces that you ask for.\n        #\n        # These are centered around a single nodes.\n        #\n        # For example, if this was your entire mesh:\n        #\n        #                 f3(Yp)\n        #           2_______________3\n        #           |               |\n        #           |               |\n        #           |               |\n        #   f0(Xm)  |       x       |  f1(Xp)\n        #           |               |\n        #           |               |\n        #           |_______________|\n        #           0               1\n        #                 f2(Ym)\n        #\n        # Pxx('fXm','fYm') = | 1, 0, 0, 0 |\n        #                    | 0, 0, 1, 0 |\n        #\n        # Pxx('fXp','fYm') = | 0, 1, 0, 0 |\n        #                    | 0, 0, 1, 0 |\n        i, j = np.arange(M.shape_cells[0]), np.arange(M.shape_cells[1])\n\n        iijj = ndgrid(i, j)\n        ii, jj = iijj[:, 0], iijj[:, 1]\n\n        if M._meshType == \"Curv\":\n            fN1 = M.reshape(M.face_normals, \"F\", \"Fx\", \"M\")\n            fN2 = M.reshape(M.face_normals, \"F\", \"Fy\", \"M\")\n\n        def Pxx(xFace, yFace):\n            # xFace is 'fXp' or 'fXm'\n            # yFace is 'fYp' or 'fYm'\n\n            # no | node      | f1     | f2\n            # 00 | i  ,j     | i  , j | i, j\n            # 10 | i+1,j     | i+1, j | i, j\n            # 01 | i  ,j+1   | i  , j | i, j+1\n            # 11 | i+1,j+1   | i+1, j | i, j+1\n\n            posFx = 0 if xFace == \"fXm\" else 1\n            posFy = 0 if yFace == \"fYm\" else 1\n\n            ind1 = sub2ind(M.vnFx, np.c_[ii + posFx, jj])\n            ind2 = sub2ind(M.vnFy, np.c_[ii, jj + posFy]) + M.nFx\n\n            IND = np.r_[ind1, ind2].flatten()\n\n            PXX = sp.csr_matrix(\n                (np.ones(2 * M.nC), (range(2 * M.nC), IND)), shape=(2 * M.nC, M.nF)\n            )\n\n            if M._meshType == \"Curv\":\n                I2x2 = inverse_2x2_block_diagonal(\n                    get_subarray(fN1[0], [i + posFx, j]),\n                    get_subarray(fN1[1], [i + posFx, j]),\n                    get_subarray(fN2[0], [i, j + posFy]),\n                    get_subarray(fN2[1], [i, j + posFy]),\n                )\n                PXX = I2x2 * PXX\n\n            return PXX\n\n        return Pxx\n\n    def _getFacePxxx(M):\n        \"\"\"Return a function for creating face projection matrices in 3D.\n\n        Mats takes you from faces a subset of all faces on only the\n        faces that you ask for.\n\n        These are centered around a single nodes.\n        \"\"\"\n        i, j, k = (\n            np.arange(M.shape_cells[0]),\n            np.arange(M.shape_cells[1]),\n            np.arange(M.shape_cells[2]),\n        )\n\n        iijjkk = ndgrid(i, j, k)\n        ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2]\n\n        if M._meshType == \"Curv\":\n            fN1 = M.reshape(M.face_normals, \"F\", \"Fx\", \"M\")\n            fN2 = M.reshape(M.face_normals, \"F\", \"Fy\", \"M\")\n            fN3 = M.reshape(M.face_normals, \"F\", \"Fz\", \"M\")\n\n        def Pxxx(xFace, yFace, zFace):\n            # xFace is 'fXp' or 'fXm'\n            # yFace is 'fYp' or 'fYm'\n            # zFace is 'fZp' or 'fZm'\n\n            # no  | node        | f1        | f2        | f3\n            # 000 | i  ,j  ,k   | i  , j, k | i, j  , k | i, j, k\n            # 100 | i+1,j  ,k   | i+1, j, k | i, j  , k | i, j, k\n            # 010 | i  ,j+1,k   | i  , j, k | i, j+1, k | i, j, k\n            # 110 | i+1,j+1,k   | i+1, j, k | i, j+1, k | i, j, k\n            # 001 | i  ,j  ,k+1 | i  , j, k | i, j  , k | i, j, k+1\n            # 101 | i+1,j  ,k+1 | i+1, j, k | i, j  , k | i, j, k+1\n            # 011 | i  ,j+1,k+1 | i  , j, k | i, j+1, k | i, j, k+1\n            # 111 | i+1,j+1,k+1 | i+1, j, k | i, j+1, k | i, j, k+1\n\n            posX = 0 if xFace == \"fXm\" else 1\n            posY = 0 if yFace == \"fYm\" else 1\n            posZ = 0 if zFace == \"fZm\" else 1\n\n            ind1 = sub2ind(M.vnFx, np.c_[ii + posX, jj, kk])\n            ind2 = sub2ind(M.vnFy, np.c_[ii, jj + posY, kk]) + M.nFx\n            ind3 = sub2ind(M.vnFz, np.c_[ii, jj, kk + posZ]) + M.nFx + M.nFy\n\n            IND = np.r_[ind1, ind2, ind3].flatten()\n\n            PXXX = sp.coo_matrix(\n                (np.ones(3 * M.nC), (range(3 * M.nC), IND)), shape=(3 * M.nC, M.nF)\n            ).tocsr()\n\n            if M._meshType == \"Curv\":\n                I3x3 = inverse_3x3_block_diagonal(\n                    get_subarray(fN1[0], [i + posX, j, k]),\n                    get_subarray(fN1[1], [i + posX, j, k]),\n                    get_subarray(fN1[2], [i + posX, j, k]),\n                    get_subarray(fN2[0], [i, j + posY, k]),\n                    get_subarray(fN2[1], [i, j + posY, k]),\n                    get_subarray(fN2[2], [i, j + posY, k]),\n                    get_subarray(fN3[0], [i, j, k + posZ]),\n                    get_subarray(fN3[1], [i, j, k + posZ]),\n                    get_subarray(fN3[2], [i, j, k + posZ]),\n                )\n                PXXX = I3x3 * PXXX\n\n            return PXXX\n\n        return Pxxx\n\n    def _getEdgePx(M):\n        \"\"\"Return a function for creating edge projection matrices in 1D.\"\"\"\n\n        def Px(xEdge):\n            if xEdge != \"eX0\":\n                raise TypeError(\"xEdge = {0!s}, not eX0\".format(xEdge))\n            return sp.identity(M.nC)\n\n        return Px\n\n    def _getEdgePxx(M):\n        \"\"\"Return a function for creating edge projection matrices in 2D.\"\"\"\n        i, j = np.arange(M.shape_cells[0]), np.arange(M.shape_cells[1])\n\n        iijj = ndgrid(i, j)\n        ii, jj = iijj[:, 0], iijj[:, 1]\n\n        if M._meshType == \"Curv\":\n            eT1 = M.reshape(M.edge_tangents, \"E\", \"Ex\", \"M\")\n            eT2 = M.reshape(M.edge_tangents, \"E\", \"Ey\", \"M\")\n\n        def Pxx(xEdge, yEdge):\n            # no | node      | e1      | e2\n            # 00 | i  ,j     | i  ,j   | i  ,j\n            # 10 | i+1,j     | i  ,j   | i+1,j\n            # 01 | i  ,j+1   | i  ,j+1 | i  ,j\n            # 11 | i+1,j+1   | i  ,j+1 | i+1,j\n            posX = 0 if xEdge == \"eX0\" else 1\n            posY = 0 if yEdge == \"eY0\" else 1\n\n            ind1 = sub2ind(M.vnEx, np.c_[ii, jj + posX])\n            ind2 = sub2ind(M.vnEy, np.c_[ii + posY, jj]) + M.nEx\n\n            IND = np.r_[ind1, ind2].flatten()\n\n            PXX = sp.coo_matrix(\n                (np.ones(2 * M.nC), (range(2 * M.nC), IND)), shape=(2 * M.nC, M.nE)\n            ).tocsr()\n\n            if M._meshType == \"Curv\":\n                I2x2 = inverse_2x2_block_diagonal(\n                    get_subarray(eT1[0], [i, j + posX]),\n                    get_subarray(eT1[1], [i, j + posX]),\n                    get_subarray(eT2[0], [i + posY, j]),\n                    get_subarray(eT2[1], [i + posY, j]),\n                )\n                PXX = I2x2 * PXX\n\n            return PXX\n\n        return Pxx\n\n    def _getEdgePxxx(M):\n        \"\"\"Return a function for creating edge projection matrices in 3D.\"\"\"\n        i, j, k = (\n            np.arange(M.shape_cells[0]),\n            np.arange(M.shape_cells[1]),\n            np.arange(M.shape_cells[2]),\n        )\n\n        iijjkk = ndgrid(i, j, k)\n        ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2]\n\n        if M._meshType == \"Curv\":\n            eT1 = M.reshape(M.edge_tangents, \"E\", \"Ex\", \"M\")\n            eT2 = M.reshape(M.edge_tangents, \"E\", \"Ey\", \"M\")\n            eT3 = M.reshape(M.edge_tangents, \"E\", \"Ez\", \"M\")\n\n        def Pxxx(xEdge, yEdge, zEdge):\n            # no  | node        | e1          | e2          | e3\n            # 000 | i  ,j  ,k   | i  ,j  ,k   | i  ,j  ,k   | i  ,j  ,k\n            # 100 | i+1,j  ,k   | i  ,j  ,k   | i+1,j  ,k   | i+1,j  ,k\n            # 010 | i  ,j+1,k   | i  ,j+1,k   | i  ,j  ,k   | i  ,j+1,k\n            # 110 | i+1,j+1,k   | i  ,j+1,k   | i+1,j  ,k   | i+1,j+1,k\n            # 001 | i  ,j  ,k+1 | i  ,j  ,k+1 | i  ,j  ,k+1 | i  ,j  ,k\n            # 101 | i+1,j  ,k+1 | i  ,j  ,k+1 | i+1,j  ,k+1 | i+1,j  ,k\n            # 011 | i  ,j+1,k+1 | i  ,j+1,k+1 | i  ,j  ,k+1 | i  ,j+1,k\n            # 111 | i+1,j+1,k+1 | i  ,j+1,k+1 | i+1,j  ,k+1 | i+1,j+1,k\n\n            posX = (\n                [0, 0]\n                if xEdge == \"eX0\"\n                else [1, 0] if xEdge == \"eX1\" else [0, 1] if xEdge == \"eX2\" else [1, 1]\n            )\n            posY = (\n                [0, 0]\n                if yEdge == \"eY0\"\n                else [1, 0] if yEdge == \"eY1\" else [0, 1] if yEdge == \"eY2\" else [1, 1]\n            )\n            posZ = (\n                [0, 0]\n                if zEdge == \"eZ0\"\n                else [1, 0] if zEdge == \"eZ1\" else [0, 1] if zEdge == \"eZ2\" else [1, 1]\n            )\n\n            ind1 = sub2ind(M.vnEx, np.c_[ii, jj + posX[0], kk + posX[1]])\n            ind2 = sub2ind(M.vnEy, np.c_[ii + posY[0], jj, kk + posY[1]]) + M.nEx\n            ind3 = (\n                sub2ind(M.vnEz, np.c_[ii + posZ[0], jj + posZ[1], kk]) + M.nEx + M.nEy\n            )\n\n            IND = np.r_[ind1, ind2, ind3].flatten()\n\n            PXXX = sp.coo_matrix(\n                (np.ones(3 * M.nC), (range(3 * M.nC), IND)), shape=(3 * M.nC, M.nE)\n            ).tocsr()\n\n            if M._meshType == \"Curv\":\n                I3x3 = inverse_3x3_block_diagonal(\n                    get_subarray(eT1[0], [i, j + posX[0], k + posX[1]]),\n                    get_subarray(eT1[1], [i, j + posX[0], k + posX[1]]),\n                    get_subarray(eT1[2], [i, j + posX[0], k + posX[1]]),\n                    get_subarray(eT2[0], [i + posY[0], j, k + posY[1]]),\n                    get_subarray(eT2[1], [i + posY[0], j, k + posY[1]]),\n                    get_subarray(eT2[2], [i + posY[0], j, k + posY[1]]),\n                    get_subarray(eT3[0], [i + posZ[0], j + posZ[1], k]),\n                    get_subarray(eT3[1], [i + posZ[0], j + posZ[1], k]),\n                    get_subarray(eT3[2], [i + posZ[0], j + posZ[1], k]),\n                )\n                PXXX = I3x3 * PXXX\n\n            return PXXX\n\n        return Pxxx\n"
  },
  {
    "path": "discretize/operators/meson.build",
    "content": "\npython_sources = [\n  '__init__.py',\n  'differential_operators.py',\n  'inner_products.py',\n]\n\npy.install_sources(\n  python_sources,\n  subdir: 'discretize/operators'\n)\n"
  },
  {
    "path": "discretize/tensor_cell.py",
    "content": "\"\"\"Cell class for TensorMesh.\"\"\"\n\nimport itertools\nimport numpy as np\n\n\nclass TensorCell:\n    \"\"\"\n    Representation of a cell in a TensorMesh.\n\n    Parameters\n    ----------\n    h : (dim) numpy.ndarray\n        Array with the cell widths along each direction. For a 2D mesh, it\n        must have two elements (``hx``, ``hy``). For a 3D mesh it must have\n        three elements (``hx``, ``hy``, ``hz``).\n    origin : (dim) numpy.ndarray\n        Array with the coordinates of the origin of the cell, i.e. the\n        bottom-left-frontmost corner.\n    index_unraveled : (dim) tuple\n        Array with the unraveled indices of the cell in its parent mesh.\n    mesh_shape : (dim) tuple\n        Shape of the parent mesh.\n\n    Examples\n    --------\n    Define a simple :class:`discretize.TensorMesh`.\n\n    >>> from discretize import TensorMesh\n    >>> mesh = TensorMesh([5, 8, 10])\n\n    We can obtain a particular cell in the mesh by its index:\n\n    >>> cell = mesh[3]\n    >>> cell\n    TensorCell(h=[0.2   0.125 0.1  ], origin=[0.6 0.  0. ], index=3, mesh_shape=(5, 8, 10))\n\n    And then obtain information about it, like its\n    :attr:`discretize.tensor_cell.TensorCell.origin`:\n\n    >>> cell.origin\n    array([0.6, 0. , 0. ])\n\n    Or its\n    :attr:`discretize.tensor_cell.TensorCell.bounds`:\n\n    >>> cell.bounds\n    array([0.6  , 0.8  , 0.   , 0.125, 0.   , 0.1  ])\n\n    We can also get its neighboring cells:\n\n    >>> neighbours = cell.get_neighbors(mesh)\n    >>> for neighbor in neighbours:\n    ...     print(neighbor.center)\n    [0.5    0.0625 0.05  ]\n    [0.9    0.0625 0.05  ]\n    [0.7    0.1875 0.05  ]\n    [0.7    0.0625 0.15  ]\n\n\n    Alternatively, we can iterate over all cells in the mesh with a simple\n    *for loop* or list comprehension:\n\n    >>> cells = [cell for cell in mesh]\n    >>> len(cells)\n    400\n\n    \"\"\"\n\n    def __init__(self, h, origin, index_unraveled, mesh_shape):\n        self._h = h\n        self._origin = origin\n        self._index_unraveled = index_unraveled\n        self._mesh_shape = mesh_shape\n\n    def __repr__(self):\n        \"\"\"Represent a TensorCell.\"\"\"\n        attributes = \", \".join(\n            [\n                f\"{attr}={getattr(self, attr)}\"\n                for attr in (\"h\", \"origin\", \"index\", \"mesh_shape\")\n            ]\n        )\n        return f\"TensorCell({attributes})\"\n\n    def __eq__(self, other):\n        \"\"\"Check if this cell is the same as other one.\"\"\"\n        if not isinstance(other, TensorCell):\n            raise TypeError(\n                f\"Cannot compare an object of type '{other.__class__.__name__}' \"\n                \"with a TensorCell\"\n            )\n        are_equal = (\n            np.all(self.h == other.h)\n            and np.all(self.origin == other.origin)\n            and self.index == other.index\n            and self.mesh_shape == other.mesh_shape\n        )\n        return are_equal\n\n    @property\n    def h(self):\n        \"\"\"Cell widths.\"\"\"\n        return self._h\n\n    @property\n    def origin(self):\n        \"\"\"Coordinates of the origin of the cell.\"\"\"\n        return self._origin\n\n    @property\n    def index(self):\n        \"\"\"Index of the cell in a TensorMesh.\"\"\"\n        return np.ravel_multi_index(\n            self.index_unraveled, dims=self.mesh_shape, order=\"F\"\n        )\n\n    @property\n    def index_unraveled(self):\n        \"\"\"Unraveled index of the cell in a TensorMesh.\"\"\"\n        return self._index_unraveled\n\n    @property\n    def mesh_shape(self):\n        \"\"\"Shape of the parent mesh.\"\"\"\n        return self._mesh_shape\n\n    @property\n    def dim(self):\n        \"\"\"Dimensions of the cell (1, 2 or 3).\"\"\"\n        return len(self.h)\n\n    @property\n    def center(self):\n        \"\"\"\n        Coordinates of the cell center.\n\n        Returns\n        -------\n        center : (dim) array\n            Array with the coordinates of the cell center.\n        \"\"\"\n        center = np.array(self.origin) + np.array(self.h) / 2\n        return center\n\n    @property\n    def bounds(self):\n        \"\"\"\n        Bounds of the cell.\n\n        Coordinates that define the bounds of the cell. Bounds are returned in\n        the following order: ``x1``, ``x2``, ``y1``, ``y2``, ``z1``, ``z2``.\n\n        Returns\n        -------\n        bounds : (2 * dim) array\n            Array with the cell bounds.\n        \"\"\"\n        bounds = np.array(\n            [\n                origin_i + factor * h_i\n                for origin_i, h_i in zip(self.origin, self.h)\n                for factor in (0, 1)\n            ]\n        )\n        return bounds\n\n    @property\n    def neighbors(self):\n        \"\"\"\n        Indices for this cell's neighbors within its parent mesh.\n\n        Returns\n        -------\n        list of list of int\n        \"\"\"\n        neighbor_indices = []\n        for dim in range(self.dim):\n            for delta in (-1, 1):\n                index = list(self.index_unraveled)\n                index[dim] += delta\n                if 0 <= index[dim] < self._mesh_shape[dim]:\n                    neighbor_indices.append(\n                        np.ravel_multi_index(index, dims=self.mesh_shape, order=\"F\")\n                    )\n        return neighbor_indices\n\n    @property\n    def nodes(self):\n        \"\"\"\n        Indices for this cell's nodes within its parent mesh.\n\n        Returns\n        -------\n        list of int\n        \"\"\"\n        # Define shape of nodes in parent mesh\n        nodes_shape = [s + 1 for s in self.mesh_shape]\n        # Get indices of nodes per dimension\n        nodes_index_per_dim = [[index, index + 1] for index in self.index_unraveled]\n        # Combine the nodes_index_per_dim using itertools.product.\n        # Because we want to follow a FORTRAN order, we need to reverse the\n        # order of the nodes_index_per_dim and the indices.\n        nodes_indices = [i[::-1] for i in itertools.product(*nodes_index_per_dim[::-1])]\n        # Ravel indices\n        nodes_indices = [\n            np.ravel_multi_index(index, dims=nodes_shape, order=\"F\")\n            for index in nodes_indices\n        ]\n        return nodes_indices\n\n    @property\n    def edges(self):\n        \"\"\"\n        Indices for this cell's edges within its parent mesh.\n\n        Returns\n        -------\n        list of int\n        \"\"\"\n        if self.dim == 1:\n            edges_indices = [self.index]\n        elif self.dim == 2:\n            # Get shape of edges grids (for edges_x and edges_y)\n            edges_x_shape = [self.mesh_shape[0], self.mesh_shape[1] + 1]\n            edges_y_shape = [self.mesh_shape[0] + 1, self.mesh_shape[1]]\n            # Calculate total amount of edges_x\n            n_edges_x = edges_x_shape[0] * edges_x_shape[1]\n            # Get indices of edges_x\n            edges_x_indices = [\n                [self.index_unraveled[0], self.index_unraveled[1] + delta]\n                for delta in (0, 1)\n            ]\n            edges_x_indices = [\n                np.ravel_multi_index(index, dims=edges_x_shape, order=\"F\")\n                for index in edges_x_indices\n            ]\n            # Get indices of edges_y\n            edges_y_indices = [\n                [self.index_unraveled[0] + delta, self.index_unraveled[1]]\n                for delta in (0, 1)\n            ]\n            edges_y_indices = [\n                n_edges_x + np.ravel_multi_index(index, dims=edges_y_shape, order=\"F\")\n                for index in edges_y_indices\n            ]\n            edges_indices = edges_x_indices + edges_y_indices\n        elif self.dim == 3:\n            edges_x_shape = [\n                n if i == 0 else n + 1 for i, n in enumerate(self.mesh_shape)\n            ]\n            edges_y_shape = [\n                n if i == 1 else n + 1 for i, n in enumerate(self.mesh_shape)\n            ]\n            edges_z_shape = [\n                n if i == 2 else n + 1 for i, n in enumerate(self.mesh_shape)\n            ]\n            # Calculate total amount of edges_x and edges_y\n            n_edges_x = edges_x_shape[0] * edges_x_shape[1] * edges_x_shape[2]\n            n_edges_y = edges_y_shape[0] * edges_y_shape[1] * edges_y_shape[2]\n            # Get indices of edges_x\n            edges_x_indices = [\n                [\n                    self.index_unraveled[0],\n                    self.index_unraveled[1] + delta_y,\n                    self.index_unraveled[2] + delta_z,\n                ]\n                for delta_z in (0, 1)\n                for delta_y in (0, 1)\n            ]\n            edges_x_indices = [\n                np.ravel_multi_index(index, dims=edges_x_shape, order=\"F\")\n                for index in edges_x_indices\n            ]\n            # Get indices of edges_y\n            edges_y_indices = [\n                [\n                    self.index_unraveled[0] + delta_x,\n                    self.index_unraveled[1],\n                    self.index_unraveled[2] + delta_z,\n                ]\n                for delta_z in (0, 1)\n                for delta_x in (0, 1)\n            ]\n            edges_y_indices = [\n                n_edges_x + np.ravel_multi_index(index, dims=edges_y_shape, order=\"F\")\n                for index in edges_y_indices\n            ]\n            # Get indices of edges_z\n            edges_z_indices = [\n                [\n                    self.index_unraveled[0] + delta_x,\n                    self.index_unraveled[1] + delta_y,\n                    self.index_unraveled[2],\n                ]\n                for delta_y in (0, 1)\n                for delta_x in (0, 1)\n            ]\n            edges_z_indices = [\n                n_edges_x\n                + n_edges_y\n                + np.ravel_multi_index(index, dims=edges_z_shape, order=\"F\")\n                for index in edges_z_indices\n            ]\n            edges_indices = edges_x_indices + edges_y_indices + edges_z_indices\n        return edges_indices\n\n    @property\n    def faces(self):\n        \"\"\"\n        Indices for cell's faces within its parent mesh.\n\n        Returns\n        -------\n        list of int\n        \"\"\"\n        if self.dim == 1:\n            faces_indices = [self.index, self.index + 1]\n        elif self.dim == 2:\n            # Get shape of faces grids\n            # (faces_x are normal to x and faces_y are normal to y)\n            faces_x_shape = [self.mesh_shape[0] + 1, self.mesh_shape[1]]\n            faces_y_shape = [self.mesh_shape[0], self.mesh_shape[1] + 1]\n            # Calculate total amount of faces_x\n            n_faces_x = faces_x_shape[0] * faces_x_shape[1]\n            # Get indices of faces_x\n            faces_x_indices = [\n                [self.index_unraveled[0] + delta, self.index_unraveled[1]]\n                for delta in (0, 1)\n            ]\n            faces_x_indices = [\n                np.ravel_multi_index(index, dims=faces_x_shape, order=\"F\")\n                for index in faces_x_indices\n            ]\n            # Get indices of faces_y\n            faces_y_indices = [\n                [self.index_unraveled[0], self.index_unraveled[1] + delta]\n                for delta in (0, 1)\n            ]\n            faces_y_indices = [\n                n_faces_x + np.ravel_multi_index(index, dims=faces_y_shape, order=\"F\")\n                for index in faces_y_indices\n            ]\n            faces_indices = faces_x_indices + faces_y_indices\n        elif self.dim == 3:\n            # Get shape of faces grids\n            faces_x_shape = [\n                n + 1 if i == 0 else n for i, n in enumerate(self.mesh_shape)\n            ]\n            faces_y_shape = [\n                n + 1 if i == 1 else n for i, n in enumerate(self.mesh_shape)\n            ]\n            faces_z_shape = [\n                n + 1 if i == 2 else n for i, n in enumerate(self.mesh_shape)\n            ]\n            # Calculate total amount of faces_x and faces_y\n            n_faces_x = faces_x_shape[0] * faces_x_shape[1] * faces_x_shape[2]\n            n_faces_y = faces_y_shape[0] * faces_y_shape[1] * faces_y_shape[2]\n            # Get indices of faces_x\n            faces_x_indices = [\n                [\n                    self.index_unraveled[0] + delta,\n                    self.index_unraveled[1],\n                    self.index_unraveled[2],\n                ]\n                for delta in (0, 1)\n            ]\n            faces_x_indices = [\n                np.ravel_multi_index(index, dims=faces_x_shape, order=\"F\")\n                for index in faces_x_indices\n            ]\n            # Get indices of faces_y\n            faces_y_indices = [\n                [\n                    self.index_unraveled[0],\n                    self.index_unraveled[1] + delta,\n                    self.index_unraveled[2],\n                ]\n                for delta in (0, 1)\n            ]\n            faces_y_indices = [\n                n_faces_x + np.ravel_multi_index(index, dims=faces_y_shape, order=\"F\")\n                for index in faces_y_indices\n            ]\n            # Get indices of faces_z\n            faces_z_indices = [\n                [\n                    self.index_unraveled[0],\n                    self.index_unraveled[1],\n                    self.index_unraveled[2] + delta,\n                ]\n                for delta in (0, 1)\n            ]\n            faces_z_indices = [\n                n_faces_x\n                + n_faces_y\n                + np.ravel_multi_index(index, dims=faces_z_shape, order=\"F\")\n                for index in faces_z_indices\n            ]\n            faces_indices = faces_x_indices + faces_y_indices + faces_z_indices\n        return faces_indices\n\n    def get_neighbors(self, mesh):\n        \"\"\"\n        Return the neighboring cells in the mesh.\n\n        Parameters\n        ----------\n        mesh : TensorMesh\n            TensorMesh where the current cell lives.\n\n        Returns\n        -------\n        list of TensorCell\n        \"\"\"\n        return [mesh[index] for index in self.neighbors]\n"
  },
  {
    "path": "discretize/tensor_mesh.py",
    "content": "\"\"\"Module housing the TensorMesh implementation.\"\"\"\n\nimport itertools\nimport numpy as np\n\nfrom discretize.base import BaseRectangularMesh, BaseTensorMesh\nfrom discretize.operators import DiffOperators, InnerProducts\nfrom discretize.mixins import InterfaceMixins, TensorMeshIO\nfrom discretize.utils import mkvc, as_array_n_by_dim\nfrom discretize.utils.code_utils import deprecate_property\n\nfrom .tensor_cell import TensorCell\n\n\nclass TensorMesh(\n    DiffOperators,\n    InnerProducts,\n    BaseTensorMesh,\n    BaseRectangularMesh,\n    TensorMeshIO,\n    InterfaceMixins,\n):\n    \"\"\"\n    Tensor mesh class.\n\n    Tensor meshes are numerical grids whose cell centers, nodes, faces, edges, widths,\n    volumes, etc... can be directly expressed as tensor products. The axes defining\n    coordinates of the mesh are orthogonal. And cell properties along one axis do\n    not vary with respect to the position along any other axis.\n\n    Parameters\n    ----------\n    h : (dim) iterable of int, numpy.ndarray, or tuple\n        Defines the cell widths along each axis. The length of the iterable object is\n        equal to the dimension of the mesh (1, 2 or 3). For a 3D mesh, the list would\n        have the form *[hx, hy, hz]* .\n\n        Along each axis, the user has 3 choices for defining the cells widths:\n\n        - :class:`int` -> A unit interval is equally discretized into `N` cells.\n        - :class:`numpy.ndarray` -> The widths are explicity given for each cell\n        - the widths are defined as a :class:`list` of :class:`tuple` of the form *(dh, nc, [npad])*\n          where *dh* is the cell width, *nc* is the number of cells, and *npad* (optional)\n          is a padding factor denoting exponential increase/decrease in the cell width\n          for each cell; e.g. *[(2., 10, -1.3), (2., 50), (2., 10, 1.3)]*\n\n    origin : (dim) iterable, default: 0\n        Define the origin or 'anchor point' of the mesh; i.e. the bottom-left-frontmost\n        corner. By default, the mesh is anchored such that its origin is at *[0, 0, 0]* .\n\n        For each dimension (x, y or z), The user may set the origin 2 ways:\n\n        - a ``scalar`` which explicitly defines origin along that dimension.\n        - **{'0', 'C', 'N'}** a :class:`str` specifying whether the zero coordinate along\n          each axis is the first node location ('0'), in the center ('C') or the last\n          node location ('N') (see Examples).\n\n    See Also\n    --------\n    utils.unpack_widths :\n        The function used to expand a tuple to generate widths.\n\n    Examples\n    --------\n    An example of a 2D tensor mesh is shown below. Here we use a list of tuple to\n    define the discretization along the x-axis and a numpy array to define the\n    discretization along the y-axis. We also use a string argument to center the\n    x-axis about x = 0 and set the top of the mesh to y = 0.\n\n    >>> from discretize import TensorMesh\n    >>> import matplotlib.pyplot as plt\n\n    >>> ncx = 10      # number of core mesh cells in x\n    >>> dx = 5        # base cell width x\n    >>> npad_x = 3    # number of padding cells in x\n    >>> exp_x = 1.25  # expansion rate of padding cells in x\n    >>> ncy = 24      # total number of mesh cells in y\n    >>> dy = 5        # base cell width y\n\n    >>> hx = [(dx, npad_x, -exp_x), (dx, ncx), (dx, npad_x, exp_x)]\n    >>> hy = dy * np.ones(ncy)\n    >>> mesh = TensorMesh([hx, hy], origin='CN')\n\n    >>> fig = plt.figure(figsize=(5,5))\n    >>> ax = fig.add_subplot(111)\n    >>> mesh.plot_grid(ax=ax)\n    >>> plt.show()\n    \"\"\"\n\n    _meshType = \"TENSOR\"\n    _aliases = {\n        **DiffOperators._aliases,\n        **BaseRectangularMesh._aliases,\n        **BaseTensorMesh._aliases,\n    }\n\n    def __repr__(self):\n        \"\"\"Plain text representation.\"\"\"\n        fmt = \"\\n  {}: {:,} cells\\n\\n\".format(type(self).__name__, self.nC)\n        fmt += 22 * \" \" + \"MESH EXTENT\" + 13 * \" \" + \"CELL WIDTH      FACTOR\\n\"\n        fmt += \"  dir    nC        min           max         min       max \"\n        fmt += \"     max\\n  ---   ---  \" + 27 * \"-\" + \"  \" + 18 * \"-\" + \"  ------\\n\"\n\n        # Get attributes and put into table.\n        attrs = self._repr_attributes()\n        for i in range(self.dim):\n            name = attrs[\"names\"][i]\n            iattr = attrs[name]\n            fmt += \"   {}\".format(name)\n            fmt += \" {:6}\".format(iattr[\"nC\"])\n            for p in [\"min\", \"max\"]:\n                fmt += \" {:13,.2f}\".format(iattr[p])\n            for p in [\"h_min\", \"h_max\"]:\n                fmt += \" {:9,.2f}\".format(iattr[p])\n            fmt += \"{:8,.2f}\".format(iattr[\"max_fact\"])\n            fmt += \"\\n\"  # End row\n\n        fmt += \"\\n\"\n        return fmt\n\n    def _repr_html_(self):\n        \"\"\"HTML representation.\"\"\"\n        style = \" style='padding: 5px 20px 5px 20px;'\"\n\n        fmt = \"<table>\\n\"\n        fmt += \"  <tr>\\n\"\n        fmt += \"    <td style='font-weight: bold; font-size: 1.2em; text-align\"\n        fmt += \": center;' colspan='3'>{}</td>\\n\".format(type(self).__name__)\n        fmt += \"    <td style='font-size: 1.2em; text-align: center;'\"\n        fmt += \"colspan='4'>{:,} cells</td>\\n\".format(self.nC)\n        fmt += \"  </tr>\\n\"\n\n        fmt += \"  <tr>\\n\"\n        fmt += \"    <th></th>\\n\"\n        fmt += \"    <th></th>\\n\"\n        fmt += \"    <th colspan='2'\" + style + \">MESH EXTENT</th>\\n\"\n        fmt += \"    <th colspan='2'\" + style + \">CELL WIDTH</th>\\n\"\n        fmt += \"    <th\" + style + \">FACTOR</th>\\n\"\n        fmt += \"  </tr>\\n\"\n\n        fmt += \"  <tr>\\n\"\n        fmt += \"    <th\" + style + \">dir</th>\\n\"\n        fmt += \"    <th\" + style + \">nC</th>\\n\"\n        fmt += \"    <th\" + style + \">min</th>\\n\"\n        fmt += \"    <th\" + style + \">max</th>\\n\"\n        fmt += \"    <th\" + style + \">min</th>\\n\"\n        fmt += \"    <th\" + style + \">max</th>\\n\"\n        fmt += \"    <th\" + style + \">max</th>\\n\"\n        fmt += \"  </tr>\\n\"\n\n        # Get attributes and put into table.\n        attrs = self._repr_attributes()\n        for i in range(self.dim):\n            name = attrs[\"names\"][i]\n            iattr = attrs[name]\n            fmt += \"  <tr>\\n\"  # Start row\n            fmt += \"    <td\" + style + \">{}</td>\\n\".format(name)\n            fmt += \"    <td\" + style + \">{}</td>\\n\".format(iattr[\"nC\"])\n            for p in [\"min\", \"max\", \"h_min\", \"h_max\", \"max_fact\"]:\n                fmt += \"    <td\" + style + \">{:,.2f}</td>\\n\".format(iattr[p])\n            fmt += \"  </tr>\\n\"  # End row\n\n        fmt += \"</table>\\n\"\n        return fmt\n\n    def __iter__(self):\n        \"\"\"Iterate over the cells.\"\"\"\n        iterator = (self[i] for i in range(len(self)))\n        return iterator\n\n    def __getitem__(self, indices):\n        \"\"\"\n        Return the boundaries of a single cell of the mesh.\n\n        Parameters\n        ----------\n        indices : int, slice, or tuple of int and slices\n            Indices of a cell in the mesh.\n            It can be a single integer or a single slice (for ravelled\n            indices), or a tuple combining integers and slices for each\n            direction.\n\n        Returns\n        -------\n        TensorCell or list of TensorCell\n        \"\"\"\n        # Handle non tuple indices\n        if isinstance(indices, slice):\n            cells = [self[i] for i in _slice_to_index(indices, len(self))]\n            return cells\n        if np.issubdtype(type(indices), np.integer):\n            indices = self._sanitize_indices(indices)\n            indices = np.unravel_index(indices, self.shape_cells, order=\"F\")\n        # Handle tuple indices\n        if not isinstance(indices, tuple):\n            raise ValueError(\n                f\"Invalid indices '{indices}'. \"\n                \"It should be an int, a slice or a tuple of int and slices.\"\n            )\n        if len(indices) != self.dim:\n            raise ValueError(\n                f\"Invalid number of indices '{len(indices)}'. \"\n                f\"It should match the number of dimensions of the mesh ({self.dim}).\"\n            )\n        # Int indices only\n        all_indices_are_ints = all(np.issubdtype(type(i), np.integer) for i in indices)\n        if all_indices_are_ints:\n            indices = self._sanitize_indices(indices)\n            return self._get_cell(indices)\n        # Slice and int indices\n        indices_per_dim = [\n            (\n                _slice_to_index(index, self.shape_cells[dim])\n                if isinstance(index, slice)\n                else [self._sanitize_indices(index, dim=dim)]\n            )\n            for dim, index in enumerate(indices)\n        ]\n        # Combine the indices_per_dim using itertools.product.\n        # Because we want to follow a FORTRAN order, we need to reverse the\n        # order of the indices_per_dim and the indices.\n        indices = (i[::-1] for i in itertools.product(*indices_per_dim[::-1]))\n        cells = [self._get_cell(i) for i in indices]\n        if not cells:\n            return None\n        return cells\n\n    def _sanitize_indices(self, indices, dim=None):\n        \"\"\"\n        Sanitize integer indices for cell in the mesh.\n\n        Convert negative indices into their corresponding positive values\n        within the mesh. It works with a tuple of indices or with\n        single int (ravelled indices).\n\n        Parameters\n        ----------\n        indices : int or tuple of int\n            Indices of a single mesh cell. It can contain negative indices.\n        dim : int or None\n            Corresponding dimension of ``indices``, if it's a single int. If\n            None and ``indices`` is an int, then ``indices`` will be assumed to\n            be a ravelled index. If ``indices`` is a tuple, ``dim`` is ignored.\n\n        Returns\n        -------\n        int or tuple of int\n        \"\"\"\n        if isinstance(indices, tuple):\n            indices = tuple(\n                index if index >= 0 else index + self.shape_cells[i]\n                for i, index in enumerate(indices)\n            )\n        elif indices < 0:\n            if dim is None:\n                indices = indices + self.n_cells\n            else:\n                indices = indices + self.shape_cells[dim]\n        return indices\n\n    def _get_cell(self, indices):\n        \"\"\"Return a single cell in the mesh.\n\n        Parameters\n        ----------\n        indices : tuple of int\n            Tuple containing the indices of the cell. Must have the same number\n            of elements as the mesh dimensions.\n\n        Returns\n        -------\n        TensorCell\n        \"\"\"\n        assert all(index >= 0 for index in indices)\n        if self.dim == 1:\n            (i,) = indices\n            x1, x2 = self.nodes_x[i], self.nodes_x[i + 1]\n            origin = np.array([x1])\n            h = np.array([x2 - x1])\n        if self.dim == 2:\n            i, j = indices\n            x1, x2 = self.nodes_x[i], self.nodes_x[i + 1]\n            y1, y2 = self.nodes_y[j], self.nodes_y[j + 1]\n            origin = np.array([x1, y1])\n            h = np.array([x2 - x1, y2 - y1])\n        if self.dim == 3:\n            i, j, k = indices\n            x1, x2 = self.nodes_x[i], self.nodes_x[i + 1]\n            y1, y2 = self.nodes_y[j], self.nodes_y[j + 1]\n            z1, z2 = self.nodes_z[k], self.nodes_z[k + 1]\n            origin = np.array([x1, y1, z1])\n            h = np.array([x2 - x1, y2 - y1, z2 - z1])\n        return TensorCell(h, origin, indices, self.shape_cells)\n\n    # --------------- Geometries ---------------------\n    @property\n    def cell_volumes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_cell_volumes\", None) is None:\n            vh = self.h\n            # Compute cell volumes\n            if self.dim == 1:\n                self._cell_volumes = mkvc(vh[0])\n            elif self.dim == 2:\n                # Cell sizes in each direction\n                self._cell_volumes = mkvc(np.outer(vh[0], vh[1]))\n            elif self.dim == 3:\n                # Cell sizes in each direction\n                self._cell_volumes = mkvc(np.outer(mkvc(np.outer(vh[0], vh[1])), vh[2]))\n        return self._cell_volumes\n\n    @property\n    def face_x_areas(self):\n        \"\"\"Return the areas of the x-faces.\n\n        Calling this property will compute and return the areas of faces\n        whose normal vector is along the x-axis.\n\n        Returns\n        -------\n        (n_faces_x) numpy.ndarray\n            The quantity returned depends on the dimensions of the mesh:\n\n            - *1D:* Numpy array of ones whose length is equal to the number of nodes\n            - *2D:* Areas of x-faces (equivalent to the lengths of y-edges)\n            - *3D:* Areas of x-faces\n        \"\"\"\n        if getattr(self, \"_face_x_areas\", None) is None:\n            # Ensure that we are working with column vectors\n            vh = self.h\n            # The number of cell centers in each direction\n            n = self.vnC\n            # Compute areas of cell faces\n            if self.dim == 1:\n                areaFx = np.ones(n[0] + 1)\n            elif self.dim == 2:\n                areaFx = np.outer(np.ones(n[0] + 1), vh[1])\n            elif self.dim == 3:\n                areaFx = np.outer(np.ones(n[0] + 1), mkvc(np.outer(vh[1], vh[2])))\n            self._face_x_areas = mkvc(areaFx)\n        return self._face_x_areas\n\n    @property\n    def face_y_areas(self):\n        \"\"\"Return the areas of the y-faces.\n\n        Calling this property will compute and return the areas of faces\n        whose normal vector is along the y-axis. Note that only 2D and 3D\n        tensor meshes have z-faces.\n\n        Returns\n        -------\n        (n_faces_y) numpy.ndarray\n            The quantity returned depends on the dimensions of the mesh:\n\n            - *1D:* N/A since 1D meshes do not have y-faces\n            - *2D:* Areas of y-faces (equivalent to the lengths of x-edges)\n            - *3D:* Areas of y-faces\n        \"\"\"\n        if getattr(self, \"_face_y_areas\", None) is None:\n            # Ensure that we are working with column vectors\n            vh = self.h\n            # The number of cell centers in each direction\n            n = self.vnC\n            # Compute areas of cell faces\n            if self.dim == 1:\n                raise Exception(\"1D meshes do not have y-Faces\")\n            elif self.dim == 2:\n                areaFy = np.outer(vh[0], np.ones(n[1] + 1))\n            elif self.dim == 3:\n                areaFy = np.outer(vh[0], mkvc(np.outer(np.ones(n[1] + 1), vh[2])))\n            self._face_y_areas = mkvc(areaFy)\n        return self._face_y_areas\n\n    @property\n    def face_z_areas(self):\n        \"\"\"Return the areas of the z-faces.\n\n        Calling this property will compute and return the areas of faces\n        whose normal vector is along the z-axis. Note that only 3D tensor\n        meshes will have z-faces.\n\n        Returns\n        -------\n        (n_faces_z) numpy.ndarray\n            The quantity returned depends on the dimensions of the mesh:\n\n            - *1D:* N/A since 1D meshes do not have z-faces\n            - *2D:* N/A since 2D meshes do not have z-faces\n            - *3D:* Areas of z-faces\n        \"\"\"\n        if getattr(self, \"_face_z_areas\", None) is None:\n            # Ensure that we are working with column vectors\n            vh = self.h\n            # The number of cell centers in each direction\n            n = self.vnC\n            # Compute areas of cell faces\n            if self.dim == 1 or self.dim == 2:\n                raise Exception(\"{}D meshes do not have z-Faces\".format(self.dim))\n            elif self.dim == 3:\n                areaFz = np.outer(vh[0], mkvc(np.outer(vh[1], np.ones(n[2] + 1))))\n            self._face_z_areas = mkvc(areaFz)\n        return self._face_z_areas\n\n    @property\n    def face_areas(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 1:\n            return self.face_x_areas\n        elif self.dim == 2:\n            return np.r_[self.face_x_areas, self.face_y_areas]\n        elif self.dim == 3:\n            return np.r_[self.face_x_areas, self.face_y_areas, self.face_z_areas]\n\n    @property\n    def edge_x_lengths(self):\n        \"\"\"Return the x-edge lengths.\n\n        Calling this property will compute and return the lengths of edges\n        parallel to the x-axis.\n\n        Returns\n        -------\n        (n_edges_x) numpy.ndarray\n            X-edge lengths\n        \"\"\"\n        if getattr(self, \"_edge_x_lengths\", None) is None:\n            # Ensure that we are working with column vectors\n            vh = self.h\n            # The number of cell centers in each direction\n            n = self.vnC\n            # Compute edge lengths\n            if self.dim == 1:\n                edgeEx = vh[0]\n            elif self.dim == 2:\n                edgeEx = np.outer(vh[0], np.ones(n[1] + 1))\n            elif self.dim == 3:\n                edgeEx = np.outer(\n                    vh[0], mkvc(np.outer(np.ones(n[1] + 1), np.ones(n[2] + 1)))\n                )\n            self._edge_x_lengths = mkvc(edgeEx)\n        return self._edge_x_lengths\n\n    @property\n    def edge_y_lengths(self):\n        \"\"\"Return the y-edge lengths.\n\n        Calling this property will compute and return the lengths of edges\n        parallel to the y-axis.\n\n        Returns\n        -------\n        (n_edges_y) numpy.ndarray\n            The quantity returned depends on the dimensions of the mesh:\n\n            - *1D:* N/A since 1D meshes do not have y-edges\n            - *2D:* Returns y-edge lengths\n            - *3D:* Returns y-edge lengths\n        \"\"\"\n        if getattr(self, \"_edge_y_lengths\", None) is None:\n            # Ensure that we are working with column vectors\n            vh = self.h\n            # The number of cell centers in each direction\n            n = self.vnC\n            # Compute edge lengths\n            if self.dim == 1:\n                raise Exception(\"1D meshes do not have y-edges\")\n            elif self.dim == 2:\n                edgeEy = np.outer(np.ones(n[0] + 1), vh[1])\n            elif self.dim == 3:\n                edgeEy = np.outer(\n                    np.ones(n[0] + 1), mkvc(np.outer(vh[1], np.ones(n[2] + 1)))\n                )\n            self._edge_y_lengths = mkvc(edgeEy)\n        return self._edge_y_lengths\n\n    @property\n    def edge_z_lengths(self):\n        \"\"\"Return the z-edge lengths.\n\n        Calling this property will compute and return the lengths of edges\n        parallel to the z-axis.\n\n        Returns\n        -------\n        (n_edges_z) numpy.ndarray\n            The quantity returned depends on the dimensions of the mesh:\n\n            - *1D:* N/A since 1D meshes do not have z-edges\n            - *2D:* N/A since 2D meshes do not have z-edges\n            - *3D:* Returns z-edge lengths\n        \"\"\"\n        if getattr(self, \"_edge_z_lengths\", None) is None:\n            # Ensure that we are working with column vectors\n            vh = self.h\n            # The number of cell centers in each direction\n            n = self.vnC\n            # Compute edge lengths\n            if self.dim == 1 or self.dim == 2:\n                raise Exception(\"{}D meshes do not have y-edges\".format(self.dim))\n            elif self.dim == 3:\n                edgeEz = np.outer(\n                    np.ones(n[0] + 1), mkvc(np.outer(np.ones(n[1] + 1), vh[2]))\n                )\n            self._edge_z_lengths = mkvc(edgeEz)\n        return self._edge_z_lengths\n\n    @property\n    def edge_lengths(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 1:\n            return self.edge_x_lengths\n        elif self.dim == 2:\n            return np.r_[self.edge_x_lengths, self.edge_y_lengths]\n        elif self.dim == 3:\n            return np.r_[self.edge_x_lengths, self.edge_y_lengths, self.edge_z_lengths]\n        return self._edge\n\n    @property\n    def face_boundary_indices(self):\n        \"\"\"Return the indices of the x, (y and z) boundary faces.\n\n        For x, (y and z) faces, this property returns the indices of the faces\n        on the boundaries. That is, the property returns the indices of the x-faces\n        that lie on the x-boundary; likewise for y and z. Note that each\n        Cartesian direction will have both a lower and upper boundary,\n        and the property will return the indices corresponding to the lower\n        and upper boundaries separately.\n\n        E.g. for a 2D domain, there are 2 x-boundaries and 2 y-boundaries (4 in total).\n        In this case, the return is a list of length 4 organized\n        [ind_Bx1, ind_Bx2, ind_By1, ind_By2]::\n\n                       By2\n                + ------------- +\n                |               |\n                |               |\n            Bx1 |               | Bx2\n                |               |\n                |               |\n                + ------------- +\n                       By1\n\n\n        Returns\n        -------\n        (dim * 2) list of numpy.ndarray of bool\n            The length of list returned depends on the dimension of the mesh.\n            And the length of each array containing the indices depends on the\n            number of faces in each direction. For 1D, 2D and 3D\n            tensor meshes, the returns take the following form:\n\n            - *1D:* returns [ind_Bx1, ind_Bx2]\n            - *2D:* returns [ind_Bx1, ind_Bx2, ind_By1, ind_By2]\n            - *3D:* returns [ind_Bx1, ind_Bx2, ind_By1, ind_By2, ind_Bz1, ind_Bz2]\n\n        Examples\n        --------\n        Here, we construct a 4 by 3 cell 2D tensor mesh and return the indices\n        of the x and y-boundary faces. In this case there are 3 x-faces on each\n        x-boundary, and there are 4 y-faces on each y-boundary.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> hx = [1, 1, 1, 1]\n        >>> hy = [2, 2, 2]\n        >>> mesh = TensorMesh([hx, hy])\n        >>> ind_Bx1, ind_Bx2, ind_By1, ind_By2 = mesh.face_boundary_indices\n\n        >>> ax = plt.subplot(111)\n        >>> mesh.plot_grid(ax=ax)\n        >>> ax.scatter(*mesh.faces_x[ind_Bx1].T)\n        >>> plt.show()\n        \"\"\"\n        if self.dim == 1:\n            indxd = self.gridFx == min(self.gridFx)\n            indxu = self.gridFx == max(self.gridFx)\n            return indxd, indxu\n        elif self.dim == 2:\n            indxd = self.gridFx[:, 0] == min(self.gridFx[:, 0])\n            indxu = self.gridFx[:, 0] == max(self.gridFx[:, 0])\n            indyd = self.gridFy[:, 1] == min(self.gridFy[:, 1])\n            indyu = self.gridFy[:, 1] == max(self.gridFy[:, 1])\n            return indxd, indxu, indyd, indyu\n        elif self.dim == 3:\n            indxd = self.gridFx[:, 0] == min(self.gridFx[:, 0])\n            indxu = self.gridFx[:, 0] == max(self.gridFx[:, 0])\n            indyd = self.gridFy[:, 1] == min(self.gridFy[:, 1])\n            indyu = self.gridFy[:, 1] == max(self.gridFy[:, 1])\n            indzd = self.gridFz[:, 2] == min(self.gridFz[:, 2])\n            indzu = self.gridFz[:, 2] == max(self.gridFz[:, 2])\n            return indxd, indxu, indyd, indyu, indzd, indzu\n\n    @property\n    def cell_bounds(self):\n        \"\"\"The bounds of each cell.\n\n        Return a 2D array with the coordinates that define the bounds of each\n        cell in the mesh. Each row of the array contains the bounds for\n        a particular cell in the following order: ``x1``, ``x2``, ``y1``,\n        ``y2``, ``z1``, ``z2``, where ``x1 < x2``, ``y1 < y2`` and ``z1 < z2``.\n        \"\"\"\n        nodes = self.nodes.reshape((*self.shape_nodes, -1), order=\"F\")\n\n        min_nodes = nodes[(slice(-1),) * self.dim]\n        min_nodes = min_nodes.reshape((self.n_cells, -1), order=\"F\")\n        max_nodes = nodes[(slice(1, None),) * self.dim]\n        max_nodes = max_nodes.reshape((self.n_cells, -1), order=\"F\")\n\n        cell_bounds = np.stack((min_nodes, max_nodes), axis=-1)\n        cell_bounds = cell_bounds.reshape((self.n_cells, -1))\n        return cell_bounds\n\n    @property\n    def cell_nodes(self):\n        \"\"\"The index of all nodes for each cell.\n\n        The nodes for each cell are listed following an \"F\" order: the first\n        coordinate (``x``) changes faster than the second one (``y``). If the\n        mesh is 3D, the second coordinate (``y``) changes faster than the third\n        one (``z``).\n\n        Returns\n        -------\n        numpy.ndarray of int\n            Index array of shape (n_cells, 4) if 2D, or (n_cells, 8) if 3D\n\n        Notes\n        -----\n        For a 2D mesh, the nodes indices for a single cell are returned in the\n        following order:\n\n        .. code::\n\n            2 -- 3\n            |    |\n            0 -- 1\n\n        For a 3D mesh, the nodes indices for a single cell are returned in the\n        following order:\n\n        .. code::\n\n              6-----7\n             /|    /|\n            4-----5 |\n            | |   | |\n            | 2---|-3\n            |/    |/\n            0-----1\n\n        \"\"\"\n        order = \"F\"\n        nodes_indices = np.arange(self.n_nodes).reshape(self.shape_nodes, order=order)\n        if self.dim == 1:\n            cell_nodes = [\n                nodes_indices[:-1].reshape(-1, order=order),\n                nodes_indices[1:].reshape(-1, order=order),\n            ]\n        elif self.dim == 2:\n            cell_nodes = [\n                nodes_indices[:-1, :-1].reshape(-1, order=order),\n                nodes_indices[1:, :-1].reshape(-1, order=order),\n                nodes_indices[:-1, 1:].reshape(-1, order=order),\n                nodes_indices[1:, 1:].reshape(-1, order=order),\n            ]\n        else:\n            cell_nodes = [\n                nodes_indices[:-1, :-1, :-1].reshape(-1, order=order),\n                nodes_indices[1:, :-1, :-1].reshape(-1, order=order),\n                nodes_indices[:-1, 1:, :-1].reshape(-1, order=order),\n                nodes_indices[1:, 1:, :-1].reshape(-1, order=order),\n                nodes_indices[:-1, :-1, 1:].reshape(-1, order=order),\n                nodes_indices[1:, :-1, 1:].reshape(-1, order=order),\n                nodes_indices[:-1, 1:, 1:].reshape(-1, order=order),\n                nodes_indices[1:, 1:, 1:].reshape(-1, order=order),\n            ]\n        cell_nodes = np.stack(cell_nodes, axis=-1)\n        return cell_nodes\n\n    @property\n    def cell_boundary_indices(self):\n        \"\"\"Return the indices of the x, (y and z) boundary cells.\n\n        This property returns the indices of the cells on the x, (y and z)\n        boundaries, respectively. Note that each axis direction will\n        have both a lower and upper boundary. The property will\n        return the indices corresponding to the lower and upper\n        boundaries separately.\n\n        E.g. for a 2D domain, there are 2 x-boundaries and 2 y-boundaries (4 in total).\n        In this case, the return is a list of length 4 organized\n        [ind_Bx1, ind_Bx2, ind_By1, ind_By2]::\n\n                       By2\n                + ------------- +\n                |               |\n                |               |\n            Bx1 |               | Bx2\n                |               |\n                |               |\n                + ------------- +\n                       By1\n\n\n        Returns\n        -------\n        (2 * dim) list of numpy.ndarray of bool\n            The length of list returned depends on the dimension of the mesh (= 2 x dim).\n            And the length of each array containing the indices is equal to\n            the number of cells in the mesh. For 1D, 2D and 3D\n            tensor meshes, the returns take the following form:\n\n            - *1D:* returns [ind_Bx1, ind_Bx2]\n            - *2D:* returns [ind_Bx1, ind_Bx2, ind_By1, ind_By2]\n            - *3D:* returns [ind_Bx1, ind_Bx2, ind_By1, ind_By2, ind_Bz1, ind_Bz2]\n\n        Examples\n        --------\n        Here, we construct a 4 by 3 cell 2D tensor mesh and return the indices\n        of the x and y-boundary cells. In this case there are 3 cells touching\n        each x-boundary, and there are 4 cells touching each y-boundary.\n\n        >>> from discretize import TensorMesh\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n\n        >>> hx = [1, 1, 1, 1]\n        >>> hy = [2, 2, 2]\n        >>> mesh = TensorMesh([hx, hy])\n        >>> ind_Bx1, ind_Bx2, ind_By1, ind_By2 = mesh.cell_boundary_indices\n\n        >>> ax = plt.subplot(111)\n        >>> mesh.plot_grid(ax=ax)\n        >>> ax.scatter(*mesh.cell_centers[ind_Bx1].T)\n        >>> plt.show()\n        \"\"\"\n        if self.dim == 1:\n            indxd = self.gridCC == min(self.gridCC)\n            indxu = self.gridCC == max(self.gridCC)\n            return indxd, indxu\n        elif self.dim == 2:\n            indxd = self.gridCC[:, 0] == min(self.gridCC[:, 0])\n            indxu = self.gridCC[:, 0] == max(self.gridCC[:, 0])\n            indyd = self.gridCC[:, 1] == min(self.gridCC[:, 1])\n            indyu = self.gridCC[:, 1] == max(self.gridCC[:, 1])\n            return indxd, indxu, indyd, indyu\n        elif self.dim == 3:\n            indxd = self.gridCC[:, 0] == min(self.gridCC[:, 0])\n            indxu = self.gridCC[:, 0] == max(self.gridCC[:, 0])\n            indyd = self.gridCC[:, 1] == min(self.gridCC[:, 1])\n            indyu = self.gridCC[:, 1] == max(self.gridCC[:, 1])\n            indzd = self.gridCC[:, 2] == min(self.gridCC[:, 2])\n            indzu = self.gridCC[:, 2] == max(self.gridCC[:, 2])\n            return indxd, indxu, indyd, indyu, indzd, indzu\n\n    def point2index(self, locs):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n\n        locs = as_array_n_by_dim(locs, self.dim)\n        # in each dimension do a sorted search within the nodes\n        # arrays to find the containing cell in that dimension\n        cell_bounds = [\n            self.nodes_x,\n        ]\n        if self.dim > 1:\n            cell_bounds.append(self.nodes_y)\n        if self.dim == 3:\n            cell_bounds.append(self.nodes_z)\n\n        # subtract 1 here because given the nodes [0, 1], the point 0.5 would be inserted\n        # at index 1 to maintain the sorted list, but that corresponds to cell 0.\n        # clipping here ensures that anything outside the mesh will return the nearest cell.\n        multi_inds = tuple(\n            np.clip(np.searchsorted(n, p) - 1, 0, len(n) - 2)\n            for n, p in zip(cell_bounds, locs.T)\n        )\n        # and of course, we are fortran ordered in a tensor mesh.\n        if self.dim == 1:\n            return multi_inds[0]\n        else:\n            return np.ravel_multi_index(multi_inds, self.shape_cells, order=\"F\")\n\n    def _repr_attributes(self):\n        \"\"\"Represent attributes of the mesh.\"\"\"\n        attrs = {}\n        attrs[\"names\"] = [\"x\", \"y\", \"z\"][: self.dim]\n\n        # Loop over dimensions.\n        for i in range(self.dim):\n            name = attrs[\"names\"][i]  # Name of this dimension\n            attrs[name] = {}\n\n            # Get min/max node.\n            n_vector = getattr(self, \"nodes_\" + name)\n            attrs[name][\"min\"] = np.nanmin(n_vector)\n            attrs[name][\"max\"] = np.nanmax(n_vector)\n\n            # Get min/max cell width.\n            h_vector = self.h[i]\n            attrs[name][\"h_min\"] = np.nanmin(h_vector)\n            attrs[name][\"h_max\"] = np.nanmax(h_vector)\n\n            # Get max stretching factor.\n            if len(h_vector) < 2:\n                attrs[name][\"max_fact\"] = 1.0\n            else:\n                attrs[name][\"max_fact\"] = np.nanmax(\n                    np.r_[h_vector[:-1] / h_vector[1:], h_vector[1:] / h_vector[:-1]]\n                )\n\n            # Add number of cells.\n            attrs[name][\"nC\"] = self.shape_cells[i]\n\n        return attrs\n\n    # DEPRECATIONS\n    areaFx = deprecate_property(\n        \"face_x_areas\", \"areaFx\", removal_version=\"1.0.0\", error=True\n    )\n    areaFy = deprecate_property(\n        \"face_y_areas\", \"areaFy\", removal_version=\"1.0.0\", error=True\n    )\n    areaFz = deprecate_property(\n        \"face_z_areas\", \"areaFz\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEx = deprecate_property(\n        \"edge_x_lengths\", \"edgeEx\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEy = deprecate_property(\n        \"edge_y_lengths\", \"edgeEy\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEz = deprecate_property(\n        \"edge_z_lengths\", \"edgeEz\", removal_version=\"1.0.0\", error=True\n    )\n    faceBoundaryInd = deprecate_property(\n        \"face_boundary_indices\",\n        \"faceBoundaryInd\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    cellBoundaryInd = deprecate_property(\n        \"cell_boundary_indices\",\n        \"cellBoundaryInd\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n\n\ndef _slice_to_index(index_slice, end):\n    \"\"\"Generate indices from a slice.\n\n    Parameters\n    ----------\n    index_slice : slice\n        Slice for cell indices along a single dimension\n    end : int\n        End of the slice. Will use this value as the stop in case the\n        `index_slice.stop` is None.\n\n    Returns\n    -------\n    Generator\n    \"\"\"\n    if (start := index_slice.start) is None:\n        start = 0\n    if (stop := index_slice.stop) is None:\n        stop = end\n    if (step := index_slice.step) is None:\n        step = 1\n    if start < 0:\n        start += end\n    if stop < 0:\n        stop += end\n    if step < 0:\n        return reversed(range(start, stop, abs(step)))\n    return range(start, stop, step)\n"
  },
  {
    "path": "discretize/tests.py",
    "content": "\"\"\"\n===========================================\nTesting Utilities (:mod:`discretize.tests`)\n===========================================\n.. currentmodule:: discretize.tests\n\nThis module contains utilities for convergence testing.\n\nClasses\n-------\n.. autosummary::\n  :toctree: generated/\n\n  OrderTest\n\nFunctions\n---------\n.. autosummary::\n  :toctree: generated/\n\n  check_derivative\n  rosenbrock\n  get_quadratic\n  setup_mesh\n  assert_isadjoint\n\"\"\"  # NOQA D205\n\nimport warnings\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom discretize.utils import mkvc, example_curvilinear_grid, requires\nfrom discretize.tensor_mesh import TensorMesh\nfrom discretize.curvilinear_mesh import CurvilinearMesh\nfrom discretize.cylindrical_mesh import CylindricalMesh\nfrom discretize.utils.code_utils import deprecate_function\n\nfrom . import TreeMesh as Tree\n\nimport unittest\nimport inspect\n\ntry:\n    import getpass\n\n    name = getpass.getuser()[0].upper() + getpass.getuser()[1:]\nexcept Exception:\n    name = \"You\"\n\nhappiness = [\n    \"The test be workin!\",\n    \"You get a gold star!\",\n    \"Yay passed!\",\n    \"Happy little convergence test!\",\n    \"That was easy!\",\n    \"Testing is important.\",\n    \"You are awesome.\",\n    \"Go Test Go!\",\n    \"Once upon a time, a happy little test passed.\",\n    \"And then everyone was happy.\",\n    \"Not just a pretty face \" + name,\n    \"You deserve a pat on the back!\",\n    \"Well done \" + name + \"!\",\n    \"Awesome, \" + name + \", just awesome.\",\n]\nsadness = [\n    \"No gold star for you.\",\n    \"Try again soon.\",\n    \"Thankfully,  persistence is a great substitute for talent.\",\n    \"It might be easier to call this a feature...\",\n    \"Coffee break?\",\n    \"Boooooooo  :(\",\n    \"Testing is important. Do it again.\",\n    \"Did you put your clever trousers on today?\",\n    \"Just think about a dancing dinosaur and life will get better!\",\n    \"You had so much promise \" + name + \", oh well...\",\n    name.upper() + \" ERROR!\",\n    \"Get on it \" + name + \"!\",\n    \"You break it, you fix it.\",\n]\n\n_happiness_rng = np.random.default_rng()\n\n\ndef _warn_random_test():\n    stack = inspect.stack()\n    in_pytest = any(x[0].f_globals[\"__name__\"].startswith(\"_pytest.\") for x in stack)\n    in_nosetest = any(x[0].f_globals[\"__name__\"].startswith(\"nose.\") for x in stack)\n\n    if in_pytest or in_nosetest:\n        test = \"pytest\" if in_pytest else \"nosetest\"\n        warnings.warn(\n            f\"You are running a {test} without setting a random seed, the results might not \"\n            \"be repeatable. For repeatable tests please pass an argument to `random seed` \"\n            \"that is not `None`.\",\n            UserWarning,\n            stacklevel=3,\n        )\n    return in_pytest or in_nosetest\n\n\ndef setup_mesh(mesh_type, nC, nDim, random_seed=None):\n    \"\"\"Generate arbitrary mesh for testing.\n\n    For the mesh type, number of cells along each axis and dimension specified,\n    **setup_mesh** will construct a random mesh that can be used for testing.\n    By design, the domain width is 1 along each axis direction.\n\n    Parameters\n    ----------\n    mesh_type : str\n        Defines the mesh type. Must be one of **{'uniformTensorMesh',\n        'randomTensorMesh', 'uniformCylindricalMesh', 'randomCylindricalMesh',\n        'uniformTree', 'randomTree', 'uniformCurv', 'rotateCurv', 'sphereCurv'}**\n    nC : int\n        Number of cells along each axis. If *mesh_type* is 'Tree', then *nC* defines the\n        number of base mesh cells and must be a power of 2.\n    nDim : int\n        The dimension of the mesh. Must be 1, 2 or 3.\n    random_seed : numpy.random.Generator, int, optional\n        If ``random`` is in `mesh_type`, this is the random number generator to use for\n        creating that random mesh. If an integer or None it is used to seed a new\n        `numpy.random.default_rng`.\n\n    Returns\n    -------\n    discretize.base.BaseMesh\n        A discretize mesh of class specified by the input argument *mesh_type*\n    \"\"\"\n    if \"random\" in mesh_type:\n        if random_seed is None:\n            _warn_random_test()\n        rng = np.random.default_rng(random_seed)\n    if \"TensorMesh\" in mesh_type:\n        if \"uniform\" in mesh_type:\n            h = [nC, nC, nC]\n        elif \"random\" in mesh_type:\n            h1 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h2 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h3 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h = [hi / np.sum(hi) for hi in [h1, h2, h3]]  # normalize\n        else:\n            raise Exception(\"Unexpected mesh_type\")\n\n        mesh = TensorMesh(h[:nDim])\n        max_h = max([np.max(hi) for hi in mesh.h])\n\n    elif \"CylindricalMesh\" in mesh_type or \"CylMesh\" in mesh_type:\n        if \"uniform\" in mesh_type:\n            if \"symmetric\" in mesh_type:\n                h = [nC, 1, nC]\n            else:\n                h = [nC, nC, nC]\n        elif \"random\" in mesh_type:\n            h1 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            if \"symmetric\" in mesh_type:\n                h2 = [\n                    2 * np.pi,\n                ]\n            else:\n                h2 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h3 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h = [hi / np.sum(hi) for hi in [h1, h2, h3]]  # normalize\n            h[1] = h[1] * 2 * np.pi\n        else:\n            raise Exception(\"Unexpected mesh_type\")\n\n        if nDim == 2:\n            mesh = CylindricalMesh([h[0], h[1]])\n            if \"symmetric\" in mesh_type:\n                max_h = np.max(mesh.h[0])\n            else:\n                max_h = max([np.max(hi) for hi in mesh.h])\n        elif nDim == 3:\n            mesh = CylindricalMesh(h)\n            if \"symmetric\" in mesh_type:\n                max_h = max([np.max(hi) for hi in [mesh.h[0], mesh.h[2]]])\n            else:\n                max_h = max([np.max(hi) for hi in mesh.h])\n\n    elif \"Curv\" in mesh_type:\n        if \"uniform\" in mesh_type:\n            kwrd = \"rect\"\n        elif \"rotate\" in mesh_type:\n            kwrd = \"rotate\"\n        elif \"sphere\" in mesh_type:\n            kwrd = \"sphere\"\n        else:\n            raise Exception(\"Unexpected mesh_type\")\n        if nDim == 1:\n            raise Exception(\"Lom not supported for 1D\")\n        elif nDim == 2:\n            X, Y = example_curvilinear_grid([nC, nC], kwrd)\n            mesh = CurvilinearMesh([X, Y])\n        elif nDim == 3:\n            X, Y, Z = example_curvilinear_grid([nC, nC, nC], kwrd)\n            mesh = CurvilinearMesh([X, Y, Z])\n        max_h = 1.0 / nC\n\n    elif \"Tree\" in mesh_type:\n        if Tree is None:\n            raise Exception(\"Tree Mesh not installed. Run 'python setup.py install'\")\n        nC *= 2\n        if \"uniform\" in mesh_type or \"notatree\" in mesh_type:\n            h = [nC, nC, nC]\n        elif \"random\" in mesh_type:\n            h1 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h2 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h3 = rng.random(nC) * nC * 0.5 + nC * 0.5\n            h = [hi / np.sum(hi) for hi in [h1, h2, h3]]  # normalize\n        else:\n            raise Exception(\"Unexpected mesh_type\")\n\n        levels = int(np.log(nC) / np.log(2))\n        mesh = Tree(h[:nDim], levels=levels)\n\n        def function(cell):\n            if \"notatree\" in mesh_type:\n                return levels - 1\n            r = cell.center - 0.5\n            dist = np.sqrt(r.dot(r))\n            if dist < 0.2:\n                return levels\n            return levels - 1\n\n        mesh.refine(function)\n        # mesh.number()\n        # mesh.plot_grid(show_it=True)\n        max_h = max([np.max(hi) for hi in mesh.h])\n    return mesh, max_h\n\n\nclass OrderTest(unittest.TestCase):\n    r\"\"\"Base class for testing convergence of discrete operators with respect to cell size.\n\n    ``OrderTest`` is a base class for testing the order of convergence of discrete\n    operators with respect to cell size. ``OrderTest`` is inherited by the test\n    class for the given operator. Within the test class, the user sets the parameters\n    for the convergence testing and defines a method :py:attr:`~OrderTest.getError`\n    which defines the error as a norm of the residual (see example).\n\n    OrderTest inherits from :class:`unittest.TestCase`.\n\n    Attributes\n    ----------\n    name : str\n        Name the convergence test\n    meshTypes : list of str\n        List denoting the mesh types on which the convergence will be tested.\n        List entries must be of the list {'uniformTensorMesh', 'randomTensorMesh',\n        'uniformCylindricalMesh', 'randomCylindricalMesh', 'uniformTree', 'randomTree',\n        'uniformCurv', 'rotateCurv', 'sphereCurv'}\n    expectedOrders : float or list of float (default = 2.0)\n        Defines the expect orders of convergence for all meshes defined in *meshTypes*.\n        If list, must have same length as argument *meshTypes*.\n    tolerance : float or list of float (default = 0.85)\n        Defines tolerance for numerical approximate of convergence order.\n        If list, must have same length as argument *meshTypes*.\n    meshSizes : list of int\n        From coarsest to finest, defines the number of mesh cells in each axis direction\n        for the meshes used in the convergence test; e.g. [4, 8, 16, 32]\n    meshDimension : int\n        Mesh dimension. Must be 1, 2 or 3\n    random_seed : numpy.random.Generator, int, optional\n        If ``random`` is in `mesh_type`, this is the random number generator\n        used generate the random meshes, if an ``int`` or ``None``, it used to seed\n        a new `numpy.random.default_rng`.\n\n    Notes\n    -----\n    Consider an operator :math:`A(f)` that acts on a test function :math:`f`. And let\n    :math:`A_h (f)` be the discrete approximation to the original operator constructed\n    on a mesh will cell size :math:`h`. ``OrderTest`` assesses the convergence of\n\n    .. math::\n        error(h) = \\| A_h(f) - A(f) \\|\n\n    as :math:`h \\rightarrow 0`. Note that you can provide any norm to quantify the error.\n    The convergence test is passed when the numerically estimated rate of convergence is within\n    a specified tolerance of the expected convergence rate supplied by the user.\n\n    Examples\n    --------\n    Here, we utilize the ``OrderTest`` class to validate the rate of convergence for\n    the :py:attr:`~discretize.differential_operators.face_divergence`. Our convergence\n    test is done for a uniform 2D tensor mesh. Under the test class *TestDIV2D*, we\n    define the static parameters for the order test. We then define a method *getError*\n    for this class which returns the norm of some residual. With these two pieces\n    defined, we can call the order test as shown below.\n\n    >>> from discretize.tests import OrderTest\n    >>> import unittest\n    >>> import numpy as np\n\n    >>> class TestDIV2D(OrderTest):\n    ...     # Static properties for OrderTest\n    ...     name = \"Face Divergence 2D\"\n    ...     meshTypes = [\"uniformTensorMesh\"]\n    ...     meshDimension = 2\n    ...     expectedOrders = 2.0\n    ...     tolerance = 0.85\n    ...     meshSizes = [8, 16, 32, 64]\n    ...     def getError(self):\n    ...         # Test function\n    ...         fx = lambda x, y: np.sin(2 * np.pi * x)\n    ...         fy = lambda x, y: np.sin(2 * np.pi * y)\n    ...         # Analytic solution for operator acting on test function\n    ...         sol = lambda x, y: 2 * np.pi * (np.cos(2 * np.pi * x) + np.cos(2 * np.pi * y))\n    ...         # Evaluate test function on faces\n    ...         f = np.r_[\n    ...             fx(self.M.faces_x[:, 0], self.M.faces_x[:, 1]),\n    ...             fy(self.M.faces_y[:, 0], self.M.faces_y[:, 1])\n    ...         ]\n    ...         # Analytic solution at cell centers\n    ...         div_f = sol(self.M.cell_centers[:, 0], self.M.cell_centers[:, 1])\n    ...         # Numerical approximation of divergence at cell centers\n    ...         div_f_num = self.M.face_divergence * f\n    ...         # Define the error function as a norm\n    ...         err = np.linalg.norm((div_f_num - div_f), np.inf)\n    ...         return err\n    ...     def test_order(self):\n    ...         self.orderTest()\n    \"\"\"\n\n    name = \"Order Test\"\n    expectedOrders = (\n        2.0  # This can be a list of orders, must be the same length as meshTypes\n    )\n    tolerance = 0.85  # This can also be a list, must be the same length as meshTypes\n    meshSizes = [4, 8, 16, 32]\n    meshTypes = [\"uniformTensorMesh\"]\n    _meshType = meshTypes[0]\n    meshDimension = 3\n    random_seed = None\n\n    def setupMesh(self, nC):\n        \"\"\"Generate mesh and set as current mesh for testing.\n\n        Parameters\n        ----------\n        nC : int\n            Number of cells along each axis.\n\n        Returns\n        -------\n        Float\n            Maximum cell width for the mesh\n        \"\"\"\n        mesh, max_h = setup_mesh(\n            self._meshType, nC, self.meshDimension, random_seed=self.random_seed\n        )\n        self.M = mesh\n        return max_h\n\n    def getError(self):\n        r\"\"\"Compute error defined as a norm of the residual.\n\n        This method is overwritten within the test class of a particular operator.\n        Within the method, we define a test function :math:`f`, the analytic solution\n        of an operator :math:`A(f)` acting on the test function, and the numerical\n        approximation obtained by applying the discretized operator :math:`A_h (f)`.\n        **getError** is defined to return the norm of the residual as shown below:\n\n        .. math::\n            error(h) = \\| A_h(f) - A(f) \\|\n\n        \"\"\"\n        return 1.0\n\n    def orderTest(self, random_seed=None):\n        \"\"\"Perform an order test.\n\n        For number of cells specified in meshSizes setup mesh, call getError\n        and prints mesh size, error, ratio between current and previous error,\n        and estimated order of convergence.\n        \"\"\"\n        __tracebackhide__ = True\n        if not isinstance(self.meshTypes, list):\n            raise TypeError(\"meshTypes must be a list\")\n        if type(self.tolerance) is not list:\n            self.tolerance = np.ones(len(self.meshTypes)) * self.tolerance\n\n        # if we just provide one expected order, repeat it for each mesh type\n        if isinstance(self.expectedOrders, (float, int)):\n            self.expectedOrders = [self.expectedOrders for i in self.meshTypes]\n        try:\n            self.expectedOrders = list(self.expectedOrders)\n        except TypeError:\n            raise TypeError(\"expectedOrders must be array like\")\n        if len(self.expectedOrders) != len(self.meshTypes):\n            raise ValueError(\n                \"expectedOrders must have the same length as the meshTypes\"\n            )\n\n        if random_seed is not None:\n            self.random_seed = random_seed\n\n        def test_func(n_cells):\n            max_h = self.setupMesh(n_cells)\n            err = self.getError()\n            return err, max_h\n\n        for mesh_type, order, tolerance in zip(\n            self.meshTypes, self.tolerance, self.expectedOrders\n        ):\n            self._meshType = mesh_type\n            assert_expected_order(\n                test_func,\n                self.meshSizes,\n                expected_order=order,\n                rtol=np.abs(1 - tolerance),\n                test_type=\"mean_at_least\",\n            )\n\n\ndef assert_expected_order(\n    func, n_cells, expected_order=2.0, rtol=0.15, test_type=\"mean\"\n):\n    \"\"\"Perform an order test.\n\n    For number of cells specified in `mesh_sizes` call `func`\n    and prints mesh size, error, ratio between current and previous error,\n    and estimated order of convergence.\n\n    Parameters\n    ----------\n    func : callable\n        Function which should accept an integer representing the number of\n        discretizations on the domain and return a tuple of the error and\n        the discretization widths.\n    n_cells : array_like of int\n        List of number of discretizations to pass to func.\n    expected_order : float, optional\n        The expected order of accuracy for you test\n    rtol : float, optional\n        The relative tolerance of the order test.\n    test_type : {'mean', 'min', 'last', 'all', 'mean_at_least'}\n        Which property of the list of calculated orders to test.\n\n    Returns\n    -------\n    numpy.ndarray\n        The calculated order values on success\n\n    Raises\n    ------\n    AssertionError\n\n    Notes\n    -----\n    For the different ``test_type`` arguments, different properties of the\n    order is tested:\n\n        - `mean`: the mean value of all calculated orders is tested for\n          approximate equality with the expected order.\n        - `min`: The minimimum value of calculated orders is tested for\n          approximate equality with the expected order.\n        - `last`: The last calculated order is tested for approximate equality\n          with the expected order.\n        - `all`: All calculated orders are tested for approximate equality with\n          the expected order.\n        - `mean_at_least`: The mean is tested to be at least approximately the\n          expected order. This is the default test for the previous ``OrderTest``\n          class in older versions of `discretize`.\n\n    Examples\n    --------\n    Testing the convergence order of an central difference operator\n\n    >>> from discretize.tests import assert_expected_order\n    >>> func = lambda y: np.cos(y)\n    >>> func_deriv = lambda y: -np.sin(y)\n\n    Define the function that returns the error and cell width for\n    a given number of discretizations.\n    >>> def deriv_error(n):\n    ...     # grid points\n    ...     nodes = np.linspace(0, 1, n+1)\n    ...     cc = 0.5 * (nodes[1:] + nodes[:-1])\n    ...     dh = nodes[1]-nodes[0]\n    ...     # evaluate the function on nodes\n    ...     node_eval = func(nodes)\n    ...     # calculate the numerical derivative\n    ...     num_deriv = (node_eval[1:] - node_eval[:-1]) / dh\n    ...     # calculate the true derivative\n    ...     true_deriv = func_deriv(cc)\n    ...     # compare the L-inf norm of the error vector\n    ...     err = np.linalg.norm(num_deriv - true_deriv, ord=np.inf)\n    ...     return err, dh\n\n    Then run the expected order test.\n    >>> assert_expected_order(deriv_error, [10, 20, 30, 40, 50])\n    \"\"\"\n    __tracebackhide__ = True\n    n_cells = np.asarray(n_cells, dtype=int)\n    if test_type not in [\"mean\", \"min\", \"last\", \"all\", \"mean_at_least\"]:\n        raise ValueError\n    orders = []\n    # Do first values:\n    nc = n_cells[0]\n    err_last, h_last = func(nc)\n\n    print(\"_______________________________________________________\")\n    print(\"  nc  |    h    |    error    | e(i-1)/e(i) |  order   \")\n    print(\"~~~~~~|~~~~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~\")\n    print(f\"{nc:^6d}|{h_last:^9.2e}|{err_last:^13.3e}|             |\")\n\n    for nc in n_cells[1:]:\n        err, h = func(nc)\n        order = np.log(err / err_last) / np.log(h / h_last)\n        print(f\"{nc:^6d}|{h:^9.2e}|{err:^13.3e}|{err_last / err:^13.4f}|{order:^10.4f}\")\n        err_last = err\n        h_last = h\n        orders.append(order)\n\n    print(\"-------------------------------------------------------\")\n\n    try:\n        if test_type == \"mean\":\n            np.testing.assert_allclose(np.mean(orders), expected_order, rtol=rtol)\n        elif test_type == \"mean_at_least\":\n            test = np.mean(orders) > expected_order * (1 - rtol)\n            if not test:\n                raise AssertionError(\n                    f\"\\nOrder mean {np.mean(orders)} is not greater than the expected order \"\n                    f\"{expected_order} within the tolerance {rtol}.\"\n                )\n        elif test_type == \"min\":\n            np.testing.assert_allclose(np.min(orders), expected_order, rtol=rtol)\n        elif test_type == \"last\":\n            np.testing.assert_allclose(orders[-1], expected_order, rtol=rtol)\n        elif test_type == \"all\":\n            np.testing.assert_allclose(orders, expected_order, rtol=rtol)\n        print(_happiness_rng.choice(happiness))\n    except AssertionError as err:\n        print(_happiness_rng.choice(sadness))\n        raise err\n\n    return orders\n\n\ndef rosenbrock(x, return_g=True, return_H=True):\n    \"\"\"Evaluate the Rosenbrock function.\n\n    This is mostly used for testing Gauss-Newton schemes\n\n    Parameters\n    ----------\n    x : numpy.ndarray\n        The (x0, x1) location for the Rosenbrock test\n    return_g : bool, optional\n        If *True*, return the gradient at *x*\n    return_H : bool, optional\n        If *True*, return the approximate Hessian at *x*\n\n    Returns\n    -------\n    tuple\n        Rosenbrock function evaluated at (x0, x1), the gradient at (x0, x1) if\n        *return_g = True* and the Hessian at (x0, x1) if *return_H = True*\n    \"\"\"\n    f = 100 * (x[1] - x[0] ** 2) ** 2 + (1 - x[0]) ** 2\n    g = np.array(\n        [2 * (200 * x[0] ** 3 - 200 * x[0] * x[1] + x[0] - 1), 200 * (x[1] - x[0] ** 2)]\n    )\n    H = sp.csr_matrix(\n        np.array(\n            [[-400 * x[1] + 1200 * x[0] ** 2 + 2, -400 * x[0]], [-400 * x[0], 200]]\n        )\n    )\n\n    out = (f,)\n    if return_g:\n        out += (g,)\n    if return_H:\n        out += (H,)\n    return out if len(out) > 1 else out[0]\n\n\ndef check_derivative(\n    fctn,\n    x0,\n    num=7,\n    plotIt=False,\n    dx=None,\n    expectedOrder=2,\n    tolerance=0.85,\n    eps=1e-10,\n    ax=None,\n    random_seed=None,\n):\n    \"\"\"Perform a basic derivative check.\n\n    Compares error decay of 0th and 1st order Taylor approximation at point\n    x0 for a randomized search direction.\n\n    Parameters\n    ----------\n    fctn : callable\n        The function to test.\n    x0 : numpy.ndarray\n        Point at which to check derivative\n    num : int, optional\n        number of times to reduce step length to evaluate derivative\n    plotIt : bool, optional\n        If *True*, plot the convergence of the approximation of the derivative\n    dx : numpy.ndarray, optional\n        Step direction. By default, this parameter is set to *None* and a random\n        step direction is chosen using `rng`.\n    expectedOrder : int, optional\n        The expected order of convergence for the numerical derivative\n    tolerance : float, optional\n        The tolerance on the expected order\n    eps : float, optional\n        A threshold value for approximately equal to zero\n    ax : matplotlib.pyplot.Axes, optional\n        An axis object for the convergence plot if *plotIt = True*.\n        Otherwise, the function will create a new axis.\n    random_seed : numpy.random.Generator, int, optional\n        If `dx` is ``None``, this is the random number generator to use for\n        generating a step direction. If an integer or None, it is used to seed\n        a new `numpy.random.default_rng`.\n\n    Returns\n    -------\n    bool\n        Whether you passed the test.\n\n    Examples\n    --------\n    >>> from discretize import tests, utils\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> rng = np.random.default_rng(786412)\n\n    >>> def simplePass(x):\n    ...     return np.sin(x), utils.sdiag(np.cos(x))\n    >>> passed = tests.check_derivative(simplePass, rng.standard_normal(5), random_seed=rng)\n    ==================== check_derivative ====================\n    iter    h         |ft-f0|   |ft-f0-h*J0*dx|  Order\n    ---------------------------------------------------------\n     0   1.00e-01    1.690e-01     8.400e-03      nan\n     1   1.00e-02    1.636e-02     8.703e-05      1.985\n     2   1.00e-03    1.630e-03     8.732e-07      1.999\n     3   1.00e-04    1.629e-04     8.735e-09      2.000\n     4   1.00e-05    1.629e-05     8.736e-11      2.000\n     5   1.00e-06    1.629e-06     8.736e-13      2.000\n     6   1.00e-07    1.629e-07     8.822e-15      1.996\n    ========================= PASS! =========================\n    Once upon a time, a happy little test passed.\n    \"\"\"\n    __tracebackhide__ = True\n    # matplotlib is a soft dependencies for discretize,\n    # lazy-loaded to decrease load time of discretize.\n\n    try:\n        import matplotlib\n        import matplotlib.pyplot as plt\n    except ImportError:\n        matplotlib = False\n\n    print(\"{0!s} check_derivative {1!s}\".format(\"=\" * 20, \"=\" * 20))\n    print(\n        \"iter    h         |ft-f0|   |ft-f0-h*J0*dx|  Order\\n{0!s}\".format((\"-\" * 57))\n    )\n\n    f0, J0 = fctn(x0)\n\n    x0 = mkvc(x0)\n\n    if dx is None:\n        if random_seed is None:\n            _warn_random_test()\n        rng = np.random.default_rng(random_seed)\n        dx = rng.standard_normal(len(x0))\n\n    h = np.logspace(-1, -num, num)\n    E0 = np.ones(h.shape)\n    E1 = np.ones(h.shape)\n\n    def l2norm(x):\n        # because np.norm breaks if they are scalars?\n        return np.sqrt(np.real(np.vdot(x, x)))\n\n    for i in range(num):\n        # Evaluate at test point\n        ft, Jt = fctn(x0 + h[i] * dx)\n        # 0th order Taylor\n        E0[i] = l2norm(ft - f0)\n        # 1st order Taylor\n        if inspect.isfunction(J0):\n            E1[i] = l2norm(ft - f0 - h[i] * J0(dx))\n        else:\n            # We assume it is a numpy.ndarray\n            E1[i] = l2norm(ft - f0 - h[i] * J0.dot(dx))\n\n        order0 = np.log10(E0[:-1] / E0[1:])\n        order1 = np.log10(E1[:-1] / E1[1:])\n        print(\n            \" {0:d}   {1:1.2e}    {2:1.3e}     {3:1.3e}      {4:1.3f}\".format(\n                i, h[i], E0[i], E1[i], np.nan if i == 0 else order1[i - 1]\n            )\n        )\n\n    @requires({\"matplotlib\": matplotlib})\n    def _plot_it(axes, passed):\n        if axes is None:\n            axes = plt.subplot(111)\n        axes.loglog(h, E0, \"b\")\n        axes.loglog(h, E1, \"g--\")\n        axes.set_title(\n            \"Check Derivative - {0!s}\".format((\"PASSED :)\" if passed else \"FAILED :(\"))\n        )\n        axes.set_xlabel(\"h\")\n        axes.set_ylabel(\"Error\")\n        leg = axes.legend(\n            [r\"$\\mathcal{O}(h)$\", r\"$\\mathcal{O}(h^2)$\"],\n            loc=\"best\",\n            title=r\"$f(x + h\\Delta x) - f(x) - h g(x) \\Delta x - \\mathcal{O}(h^2) = 0$\",\n            frameon=False,\n        )\n        plt.setp(leg.get_title(), fontsize=15)\n        plt.show()\n\n    # Ensure we are about precision\n    order0 = order0[E0[1:] > eps]\n    order1 = order1[E1[1:] > eps]\n\n    # belowTol = order1.size == 0 and order0.size >= 0\n    # # Make sure we get the correct order\n    # correctOrder = order1.size > 0 and np.mean(order1) > tolerance * expectedOrder\n    #\n    # passTest = belowTol or correctOrder\n    try:\n        if order1.size == 0:\n            # This should happen if all of the 1st order taylor approximation errors\n            # were below epsilon, common if the original function was linear.\n            # Thus it has no higher order derivatives.\n            pass\n        else:\n            order_mean = np.mean(order1)\n            expected = tolerance * expectedOrder\n            test = order_mean > expected\n            if not test:\n                raise AssertionError(\n                    f\"\\n Order mean {order_mean} is not greater than\"\n                    f\" {expected} = tolerance: {tolerance} \"\n                    f\"* expected order: {expectedOrder}.\"\n                )\n        print(\"{0!s} PASS! {1!s}\".format(\"=\" * 25, \"=\" * 25))\n        print(_happiness_rng.choice(happiness) + \"\\n\")\n        if plotIt:\n            _plot_it(ax, True)\n    except AssertionError as err:\n        print(\n            \"{0!s}\\n{1!s} FAIL! {2!s}\\n{3!s}\".format(\n                \"*\" * 57, \"<\" * 25, \">\" * 25, \"*\" * 57\n            )\n        )\n        print(_happiness_rng.choice(sadness) + \"\\n\")\n        if plotIt:\n            _plot_it(ax, False)\n        raise err\n\n    return True\n\n\ndef get_quadratic(A, b, c=0):\n    r\"\"\"Return a function that evaluates the given quadratic.\n\n    Given **A**, **b** and *c*, this returns a function that evaluates\n    the quadratic for a vector **x**. Where :math:`\\mathbf{A} \\in \\mathbb{R}^{NxN}`,\n    :math:`\\mathbf{b} \\in \\mathbb{R}^N` and :math:`c` is a constant,\n    this function evaluates the following quadratic:\n\n    .. math::\n\n        Q( \\mathbf{x} ) = \\frac{1}{2} \\mathbf{x^T A x + b^T x} + c\n\n    for a vector :math:`\\mathbf{x}`. It also optionally returns the gradient of the\n    above equation, and its Hessian.\n\n    Parameters\n    ----------\n    A : (N, N) numpy.ndarray\n        A square matrix\n    b : (N) numpy.ndarray\n        A vector\n    c : float\n        A constant\n\n    Returns\n    -------\n    function :\n        The callable function that returns the quadratic evaluation, and optionally its\n        gradient, and Hessian.\n    \"\"\"\n\n    def Quadratic(x, return_g=True, return_H=True):\n        f = 0.5 * x.dot(A.dot(x)) + b.dot(x) + c\n        out = (f,)\n        if return_g:\n            g = A.dot(x) + b\n            out += (g,)\n        if return_H:\n            H = A\n            out += (H,)\n        return out if len(out) > 1 else out[0]\n\n    return Quadratic\n\n\ndef assert_isadjoint(\n    forward,\n    adjoint,\n    shape_u,\n    shape_v,\n    complex_u=False,\n    complex_v=False,\n    clinear=True,\n    rtol=1e-6,\n    atol=0.0,\n    assert_error=True,\n    random_seed=None,\n):\n    r\"\"\"Do a dot product test for the forward operator and its adjoint operator.\n\n    Dot product test to verify the correctness of the adjoint operator\n    :math:`F^H` of the forward operator :math:`F`.\n\n    .. math::\n\n        \\mathbf{v}^H ( \\mathbf{F} \\mathbf{u} ) =\n        ( \\mathbf{F}^H \\mathbf{v} )^H \\mathbf{u}\n\n\n    Parameters\n    ----------\n    forward : callable\n        Forward operator.\n\n    adjoint : callable\n        Adjoint operator.\n\n    shape_u : int, tuple of int\n        Shape of vector ``u`` passed in to ``forward``; it is accordingly the\n        expected shape of the vector returned from the ``adjoint``.\n\n    shape_v : int, tuple of int\n        Shape of vector ``v`` passed in to ``adjoint``; it is accordingly the\n        expected shape of the vector returned from the ``forward``.\n\n    complex_u : bool, default: False\n        If True, vector ``u`` passed to ``forward`` is a complex vector;\n        accordingly the ``adjoint`` is expected to return a complex vector.\n\n    complex_v : bool, default: False\n        If True, vector ``v`` passed to ``adjoint`` is a complex vector;\n        accordingly the ``forward`` is expected to return a complex vector.\n\n    clinear : bool, default: True\n        If operator is complex-linear (True) or real-linear (False).\n\n    rtol : float, default: 1e-6\n        Relative tolerance.\n\n    atol : float, default: 0.0\n        Absolute tolerance.\n\n    assert_error : bool, default: True\n        By default this test is an assertion (silent if passed, raising an\n        assertion error if failed). If set to False, the result of the test is\n        returned as boolean and a message is printed.\n\n    random_seed : numpy.random.Generator, int, optional\n        The random number generator to use for the adjoint test. If an integer or None\n        it is used to seed a new `numpy.random.default_rng`.\n\n    Returns\n    -------\n    passed : bool, optional\n        Result of the dot product test; only returned if ``assert_error`` is False.\n\n    Raises\n    ------\n    AssertionError\n        If the dot product test fails (only if assert_error=True).\n\n    \"\"\"\n    __tracebackhide__ = True\n\n    if random_seed is None:\n        _warn_random_test()\n    rng = np.random.default_rng(random_seed)\n\n    def random(size, iscomplex):\n        \"\"\"Create random data of size and dtype of <size>.\"\"\"\n        out = rng.standard_normal(size)\n        if iscomplex:\n            out = out + 1j * rng.standard_normal(size)\n        return out\n\n    # Create random vectors u and v.\n    u = random(np.prod(shape_u), complex_u).reshape(shape_u)\n    v = random(np.prod(shape_v), complex_v).reshape(shape_v)\n\n    # Carry out dot product test.\n    fwd_u = forward(u)\n    adj_v = adjoint(v)\n    if clinear:\n        lhs = np.vdot(v, fwd_u)  # lhs := v^H * (fwd * u)\n        rhs = np.vdot(adj_v, u)  # rhs := (adj * v)^H * u\n    else:\n        lhs = np.vdot(v.real, fwd_u.real) + np.vdot(v.imag, fwd_u.imag)\n        rhs = np.vdot(adj_v.real, u.real) + np.vdot(adj_v.imag, u.imag)\n\n    # Check if they are the same.\n    if assert_error:\n        np.testing.assert_allclose(\n            rhs, lhs, rtol=rtol, atol=atol, err_msg=\"Adjoint test failed\"\n        )\n\n    else:\n        passed = np.allclose(rhs, lhs, rtol=rtol, atol=atol)\n\n        print(\n            f\"Adjoint test {'PASSED' if passed else 'FAILED'} ::  \"\n            f\"{abs(rhs-lhs):.3e} < {atol+rtol*abs(lhs):.3e}  :: \"\n            f\"|rhs-lhs| < atol + rtol|lhs|\"\n        )\n\n        return passed\n\n\ndef assert_cell_intersects_geometric(\n    cell, points, edges=None, faces=None, as_refine=False\n):\n    \"\"\"Assert if a cell intersects a convex polygon.\n\n    Parameters\n    ----------\n    cell : tree_mesh.TreeCell\n        Must have cell.origin and cell.h properties\n    points : (*, dim) array_like\n        The points of the geometric object.\n    edges : (*, 2) array_like of int, optional\n        The 2 indices into points defining each edge\n    faces : (*, 3) array_like of int, optional\n        The 3 indices into points which lie on each face. These are used\n        to define the face normals from the three points as\n        ``norm = cross(p1 - p0, p2 - p0)``.\n    as_refine : bool, or int\n        If ``True`` (or a nonzero integer), this function will not assert and instead\n        return either 0, -1, or the integer making it suitable (but slow) for\n        refining a TreeMesh.\n\n    Returns\n    -------\n    int\n\n    Raises\n    ------\n    AssertionError\n    \"\"\"\n    __tracebackhide__ = True\n\n    x0 = cell.origin\n    xF = x0 + cell.h\n\n    points = np.atleast_2d(points)\n    if edges is not None:\n        edges = np.atleast_2d(edges)\n        if edges.shape[-1] != 2:\n            raise ValueError(\"Last dimension of edges must be 2.\")\n    if faces is not None:\n        faces = np.atleast_2d(faces)\n        if faces.shape[-1] != 3:\n            raise ValueError(\"Last dimension of faces must be 3.\")\n\n    do_asserts = not as_refine\n    level = -1\n    if as_refine and not isinstance(as_refine, bool):\n        level = int(as_refine)\n\n    dim = points.shape[-1]\n    # first the bounding box tests (associated with the 3 face normals of the cell\n    mins = points.min(axis=0)\n    for i_d in range(dim):\n        if do_asserts:\n            assert mins[i_d] <= xF[i_d]\n        else:\n            if mins[i_d] > xF[i_d]:\n                return 0\n\n    maxs = points.max(axis=0)\n    for i_d in range(dim):\n        if do_asserts:\n            assert maxs[i_d] >= x0[i_d]\n        else:\n            if maxs[i_d] < x0[i_d]:\n                return 0\n\n    # create array of all the box points\n    if edges is not None or faces is not None:\n        box_points = np.meshgrid(*list(zip(x0, xF)))\n        box_points = np.stack(box_points, axis=-1).reshape(-1, dim)\n\n        def project_min_max(points, axis):\n            ps = points @ axis\n            return ps.min(), ps.max()\n\n        if edges is not None and dim > 1:\n            box_dirs = np.eye(dim)\n            edge_dirs = points[edges[:, 1]] - points[edges[:, 0]]\n            # perform the edge-edge intersection tests\n            # these project all points onto the axis formed by the cross\n            # product of the geometric edges and the bounding box's edges/faces normals\n            for i in range(edges.shape[0]):\n                for j in range(dim):\n                    if dim == 3:\n                        axis = np.cross(edge_dirs[i], box_dirs[j])\n                    else:\n                        axis = [-edge_dirs[i, 1], edge_dirs[i, 0]]\n                    bmin, bmax = project_min_max(box_points, axis)\n                    gmin, gmax = project_min_max(points, axis)\n                    if do_asserts:\n                        assert bmax >= gmin and bmin <= gmax\n                    else:\n                        if bmax < gmin or bmin > gmax:\n                            return 0\n\n        if faces is not None and dim > 2:\n            face_normals = np.cross(\n                points[faces[:, 1]] - points[faces[:, 0]],\n                points[faces[:, 2]] - points[faces[:, 0]],\n            )\n            for i in range(faces.shape[0]):\n                bmin, bmax = project_min_max(box_points, face_normals[i])\n                gmin, gmax = project_min_max(points, face_normals[i])\n                if do_asserts:\n                    assert bmax >= gmin and bmin <= gmax\n                else:\n                    if bmax < gmin or bmin > gmax:\n                        return 0\n    if not do_asserts:\n        return level\n\n\n# DEPRECATIONS\nsetupMesh = deprecate_function(\n    setup_mesh, \"setupMesh\", removal_version=\"1.0.0\", error=True\n)\nRosenbrock = deprecate_function(\n    rosenbrock, \"Rosenbrock\", removal_version=\"1.0.0\", error=True\n)\ncheckDerivative = deprecate_function(\n    check_derivative, \"checkDerivative\", removal_version=\"1.0.0\", error=True\n)\ngetQuadratic = deprecate_function(\n    get_quadratic, \"getQuadratic\", removal_version=\"1.0.0\", error=True\n)\n"
  },
  {
    "path": "discretize/tree_mesh.py",
    "content": "\"\"\"Module containing the TreeMesh implementation.\"\"\"\n\nimport warnings\n\n#      ___          ___       ___          ___          ___          ___\n#     /\\  \\        /\\  \\     /\\  \\        /\\  \\        /\\  \\        /\\  \\\n#    /::\\  \\      /::\\  \\    \\:\\  \\      /::\\  \\      /::\\  \\      /::\\  \\\n#   /:/\\:\\  \\    /:/\\:\\  \\    \\:\\  \\    /:/\\:\\  \\    /:/\\:\\  \\    /:/\\:\\  \\\n#  /:/  \\:\\  \\  /:/  \\:\\  \\   /::\\  \\  /::\\~\\:\\  \\  /::\\~\\:\\  \\  /::\\~\\:\\  \\\n# /:/__/ \\:\\__\\/:/__/ \\:\\__\\ /:/\\:\\__\\/:/\\:\\ \\:\\__\\/:/\\:\\ \\:\\__\\/:/\\:\\ \\:\\__\\\n# \\:\\  \\ /:/  /\\:\\  \\  \\/__//:/  \\/__/\\/_|::\\/:/  /\\:\\~\\:\\ \\/__/\\:\\~\\:\\ \\/__/\n#  \\:\\  /:/  /  \\:\\  \\     /:/  /        |:|::/  /  \\:\\ \\:\\__\\   \\:\\ \\:\\__\\\n#   \\:\\/:/  /    \\:\\  \\    \\/__/         |:|\\/__/    \\:\\ \\/__/    \\:\\ \\/__/\n#    \\::/  /      \\:\\__\\                 |:|  |       \\:\\__\\       \\:\\__\\\n#     \\/__/        \\/__/                  \\|__|        \\/__/        \\/__/\n#\n#\n#\n#                      .----------------.----------------.\n#                     /|               /|               /|\n#                    / |              / |              / |\n#                   /  |      6      /  |     7       /  |\n#                  /   |            /   |            /   |\n#                 .----------------.----+-----------.    |\n#                /|    . ---------/|----.----------/|----.\n#               / |   /|         / |   /|         / |   /|\n#              /  |  / |  4     /  |  / |   5    /  |  / |\n#             /   | /  |       /   | /  |       /   | /  |\n#            . -------------- .----------------.    |/   |\n#            |    . ---+------|----.----+------|----.    |\n#            |   /|    .______|___/|____.______|___/|____.\n#            |  / |   /    2  |  / |   /     3 |  / |   /\n#            | /  |  /        | /  |  /        | /  |  /\n#            . ---+---------- . ---+---------- .    | /\n#            |    |/          |    |/          |    |/             z\n#            |    . ----------|----.-----------|----.              ^   y\n#            |   /      0     |   /       1    |   /               |  /\n#            |  /             |  /             |  /                | /\n#            | /              | /              | /                 o----> x\n#            . -------------- . -------------- .\n#\n#\n# Face Refinement:\n#\n#      2_______________3                    _______________\n#      |               |                   |       |       |\n#   ^  |               |                   |   2   |   3   |\n#   |  |               |                   |       |       |\n#   |  |       x       |        --->       |-------+-------|\n#   t1 |               |                   |       |       |\n#      |               |                   |   0   |   1   |\n#      |_______________|                   |_______|_______|\n#      0      t0-->    1\n#\n#\n# Face and Edge naming conventions:\n#\n#                      fZp\n#                       |\n#                 6 ------eX3------ 7\n#                /|     |         / |\n#               /eZ2    .        / eZ3\n#             eY2 |        fYp eY3  |\n#             /   |            / fXp|\n#            4 ------eX2----- 5     |\n#            |fXm 2 -----eX1--|---- 3          z\n#           eZ0  /            |  eY1           ^   y\n#            | eY0   .  fYm  eZ1 /             |  /\n#            | /     |        | /              | /\n#            0 ------eX0------1                o----> x\n#                    |\n#                   fZm\n#\n#\n#            fX                                  fY\n#      2___________3                       2___________3\n#      |     e1    |                       |     e1    |\n#      |           |                       |           |\n#   e0 |     x     | e2      z          e0 |     x     | e2      z\n#      |           |         ^             |           |         ^\n#      |___________|         |___> y       |___________|         |___> x\n#      0    e3     1                       0    e3     1\n#           fZ\n#      2___________3\n#      |     e1    |\n#      |           |\n#   e0 |     x     | e2      y\n#      |           |         ^\n#      |___________|         |___> x\n#      0    e3     1\n\nfrom discretize.base import BaseTensorMesh\nfrom discretize.operators import InnerProducts, DiffOperators\nfrom discretize.mixins import InterfaceMixins, TreeMeshIO\nfrom discretize._extensions.tree_ext import (  # noqa: F401\n    _TreeMesh,\n    TreeCell,\n    TreeMeshNotFinalizedError,\n)\nimport numpy as np\nimport scipy.sparse as sp\nfrom discretize.utils.code_utils import deprecate_property\nfrom scipy.spatial import Delaunay\n\n\nclass TreeMesh(\n    _TreeMesh,\n    InnerProducts,\n    DiffOperators,\n    BaseTensorMesh,\n    TreeMeshIO,\n    InterfaceMixins,\n):\n    \"\"\"Class for QuadTree (2D) and OcTree (3D) meshes.\n\n    Tree meshes are numerical grids where the dimensions of each cell are powers of 2\n    larger than some base cell dimension. Unlike the :class:`~discretize.TensorMesh`\n    class, gridded locations and numerical operators for instances of ``TreeMesh``\n    cannot be simply constructed using tensor products. Furthermore, each cell\n    is an instance of :class:`~discretize.tree_mesh.TreeCell` .\n\n    Each cell of a `TreeMesh` has a certain level associated with it which describes its\n    height in the structure of the tree. The tree mesh is refined by specifying what\n    level you want in a given location. They start at level 0, defining the largest\n    possible cell on the mesh, and go to a level of `max_level` describing the\n    finest possible cell on the mesh. TreeMesh` contains several refinement functions\n    used to design the mesh, starting with a general purpose refinement function, along\n    with several faster specialized refinement functions based on fundamental geometric\n    entities:\n\n    - `refine` -> The general purpose refinement function supporting arbitrary defined functions\n    - `insert_cells`\n    - `refine_ball`\n    - `refine_line`\n    - `refine_triangle`\n    - `refine_box`\n    - `refine_tetrahedron`\n    - `refine_vertical_trianglular_prism`\n    - `refine_bounding_box`\n    - `refine_points`\n    - `refine_surface`\n\n    Like array indexing in python, you can also supply negative indices as a level\n    arguments to these functions to index levels in a reveresed order (i.e. -1 is\n    equivalent to `max_level`).\n\n    Parameters\n    ----------\n    h : (dim) iterable of int, numpy.ndarray, or tuple\n        Defines the cell widths of the *underlying tensor mesh* along each axis. The\n        length of the iterable object is equal to the dimension of the mesh (2 or 3).\n        For a 3D mesh, the list would have the form *[hx, hy, hz]*. The number of cells\n        along each axis **must be a power of 2** .\n\n        Along each axis, the user has 3 choices for defining the cells widths for the\n        underlying tensor mesh:\n\n        - :class:`int` -> A unit interval is equally discretized into `N` cells.\n        - :class:`numpy.ndarray` -> The widths are explicity given for each cell\n        - the widths are defined as a :class:`list` of :class:`tuple` of the form *(dh, nc, [npad])*\n          where *dh* is the cell width, *nc* is the number of cells, and *npad* (optional)\n          is a padding factor denoting exponential increase/decrease in the cell width\n          for each cell; e.g. *[(2., 10, -1.3), (2., 50), (2., 10, 1.3)]*\n\n    origin : (dim) iterable, default: 0\n        Define the origin or 'anchor point' of the mesh; i.e. the bottom-left-frontmost\n        corner. By default, the mesh is anchored such that its origin is at [0, 0, 0].\n\n        For each dimension (x, y or z), The user may set the origin 2 ways:\n\n        - a ``scalar`` which explicitly defines origin along that dimension.\n        - **{'0', 'C', 'N'}** a :class:`str` specifying whether the zero coordinate along\n          each axis is the first node location ('0'), in the center ('C') or the last\n          node location ('N') (see Examples).\n\n    diagonal_balance : bool, optional\n        Whether to balance cells along the diagonal of the tree during construction.\n        This will affect all calls to refine the tree.\n\n    Examples\n    --------\n    Here we generate a basic 2D tree mesh.\n\n    >>> from discretize import TreeMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n\n    Define base mesh (domain and finest discretization),\n\n    >>> dh = 5    # minimum cell width (base mesh cell width)\n    >>> nbc = 64  # number of base mesh cells\n    >>> h = dh * np.ones(nbc)\n    >>> mesh = TreeMesh([h, h])\n\n    Define corner points for a rectangular box, and subdived the mesh within the box\n    to the maximum refinement level.\n\n    >>> x0s = [120.0, 80.0]\n    >>> x1s = [240.0, 160.0]\n    >>> levels = [mesh.max_level]\n    >>> mesh.refine_box(x0s, x1s, levels)\n\n    >>> mesh.plot_grid()\n    >>> plt.show()\n    \"\"\"\n\n    _meshType = \"TREE\"\n    _aliases = {\n        **BaseTensorMesh._aliases,\n        **DiffOperators._aliases,\n        **{\n            \"ntN\": \"n_total_nodes\",\n            \"ntEx\": \"n_total_edges_x\",\n            \"ntEy\": \"n_total_edges_y\",\n            \"ntEz\": \"n_total_edges_z\",\n            \"ntE\": \"n_total_edges\",\n            \"ntFx\": \"n_total_faces_x\",\n            \"ntFy\": \"n_total_faces_y\",\n            \"ntFz\": \"n_total_faces_z\",\n            \"ntF\": \"n_total_faces\",\n            \"nhN\": \"n_hanging_nodes\",\n            \"nhEx\": \"n_hanging_edges_x\",\n            \"nhEy\": \"n_hanging_edges_y\",\n            \"nhEz\": \"n_hanging_edges_z\",\n            \"nhE\": \"n_hanging_edges\",\n            \"nhFx\": \"n_hanging_faces_x\",\n            \"nhFy\": \"n_hanging_faces_y\",\n            \"nhFz\": \"n_hanging_faces_z\",\n            \"nhF\": \"n_hanging_faces\",\n            \"gridhN\": \"hanging_nodes\",\n            \"gridhFx\": \"hanging_faces_x\",\n            \"gridhFy\": \"hanging_faces_y\",\n            \"gridhFz\": \"hanging_faces_z\",\n            \"gridhEx\": \"hanging_edges_x\",\n            \"gridhEy\": \"hanging_edges_y\",\n            \"gridhEz\": \"hanging_edges_z\",\n        },\n    }\n    _items = {\"h\", \"origin\", \"cell_state\"}\n\n    # inheriting stuff from BaseTensorMesh that isn't defined in _QuadTree\n    def __init__(self, h=None, origin=None, diagonal_balance=None, **kwargs):\n        if \"x0\" in kwargs:\n            origin = kwargs.pop(\"x0\")\n\n        if diagonal_balance is None:\n            diagonal_balance = False\n            warnings.warn(\n                \"In discretize v1.0 the TreeMesh will change the default value of \"\n                \"diagonal_balance to True, which will likely slightly change meshes \"\n                \"you have previously created. \"\n                \"If you need to keep the current behavior, explicitly set \"\n                \"diagonal_balance=False.\",\n                FutureWarning,\n                stacklevel=2,\n            )\n        super().__init__(h=h, origin=origin, diagonal_balance=diagonal_balance)\n\n        cell_state = kwargs.pop(\"cell_state\", None)\n        cell_indexes = kwargs.pop(\"cell_indexes\", None)\n        cell_levels = kwargs.pop(\"cell_levels\", None)\n        if cell_state is None:\n            if cell_indexes is not None and cell_levels is not None:\n                cell_state = {}\n                cell_state[\"indexes\"] = cell_indexes\n                cell_state[\"levels\"] = cell_levels\n        if cell_state is not None:\n            indexes = cell_state[\"indexes\"]\n            levels = cell_state[\"levels\"]\n            self.__setstate__((indexes, levels))\n\n    def __repr__(self):\n        \"\"\"Plain text representation.\"\"\"\n        mesh_name = \"{0!s}TreeMesh\".format((\"Oc\" if self.dim == 3 else \"Quad\"))\n\n        top = \"\\n\" + mesh_name + \": {0:2.2f}% filled\\n\\n\".format(self.fill * 100)\n\n        # Number of cells per level\n        level_count = self._count_cells_per_index()\n        non_zero_levels = np.nonzero(level_count)[0]\n        cell_display = [\"Level : Number of cells\"]\n        cell_display.append(\"-----------------------\")\n        for level in non_zero_levels:\n            cell_display.append(\"{:^5} : {:^15}\".format(level, level_count[level]))\n        cell_display.append(\"-----------------------\")\n        cell_display.append(\"Total : {:^15}\".format(self.nC))\n\n        extent_display = [\"            Mesh Extent       \"]\n        extent_display.append(\"        min     ,     max     \")\n        extent_display.append(\"   ---------------------------\")\n        dim_label = {0: \"x\", 1: \"y\", 2: \"z\"}\n        for dim in range(self.dim):\n            n_vector = getattr(self, \"nodes_\" + dim_label[dim])\n            extent_display.append(\n                \"{}: {:^13},{:^13}\".format(dim_label[dim], n_vector[0], n_vector[-1])\n            )\n\n        # Return partial information if mesh is not finalized\n        if not self.finalized:\n            top = f\"\\n {mesh_name} (non finalized)\\n\\n\"\n            return top + \"\\n\".join(extent_display)\n\n        for i, line in enumerate(extent_display):\n            if i == len(cell_display):\n                cell_display.append(\" \" * (len(cell_display[0]) - 3 - len(line)))\n            cell_display[i] += 3 * \" \" + line\n\n        h_display = [\"     Cell Widths    \"]\n        h_display.append(\"    min   ,   max   \")\n        h_display.append(\"-\" * (len(h_display[0])))\n        h_gridded = self.h_gridded\n        mins = np.min(h_gridded, axis=0)\n        maxs = np.max(h_gridded, axis=0)\n        for dim in range(self.dim):\n            h_display.append(\"{:^10}, {:^10}\".format(mins[dim], maxs[dim]))\n\n        for i, line in enumerate(h_display):\n            if i == len(cell_display):\n                cell_display.append(\" \" * len(cell_display[0]))\n            cell_display[i] += 3 * \" \" + line\n\n        return top + \"\\n\".join(cell_display)\n\n    def _repr_html_(self):\n        \"\"\"HTML representation.\"\"\"\n        mesh_name = \"{0!s}TreeMesh\".format((\"Oc\" if self.dim == 3 else \"Quad\"))\n        style = \" style='padding: 5px 20px 5px 20px;'\"\n        dim_label = {0: \"x\", 1: \"y\", 2: \"z\"}\n\n        if not self.finalized:\n            style_bold = '\"font-weight: bold; font-size: 1.2em; text-align: center;\"'\n            style_regular = '\"font-size: 1.2em; text-align: center;\"'\n            output = [\n                \"<table>\",  # need to close this tag\n                \"<tr>\",\n                f\"<td style={style_bold}>{mesh_name}</td>\",\n                f\"<td style={style_regular} colspan='2'>(non finalized)</td>\",\n                \"</tr>\",\n                \"<table>\",  # need to close this tag\n                \"<tr>\",\n                \"<th></th>\",\n                f'<th {style} colspan=\"2\">Mesh extent</th>',\n                \"</tr>\",\n                \"<tr>\",\n                \"<th></th>\",\n                f\"<th {style}>min</th>\",\n                f\"<th {style}>max</th>\",\n                \"</tr>\",\n            ]\n            for dim in range(self.dim):\n                n_vector = getattr(self, \"nodes_\" + dim_label[dim])\n                output += [\n                    \"<tr>\",\n                    f\"<td {style}>{dim_label[dim]}</td>\",\n                    f\"<td {style}>{n_vector[0]}</td>\",\n                    f\"<td {style}>{n_vector[-1]}</td>\",\n                    \"</tr>\",\n                ]\n            output += [\"</table>\", \"</table>\"]\n            return \"\\n\".join(output)\n\n        level_count = self._count_cells_per_index()\n        non_zero_levels = np.nonzero(level_count)[0]\n        h_gridded = self.h_gridded\n        mins = np.min(h_gridded, axis=0)\n        maxs = np.max(h_gridded, axis=0)\n\n        # Cell level table:\n        cel_tbl = \"<table>\\n\"\n        cel_tbl += \"<tr>\\n\"\n        cel_tbl += \"<th\" + style + \">Level</th>\\n\"\n        cel_tbl += \"<th\" + style + \">Number of cells</th>\\n\"\n        cel_tbl += \"</tr>\\n\"\n        for level in non_zero_levels:\n            cel_tbl += \"<tr>\\n\"\n            cel_tbl += \"<td\" + style + \">{}</td>\\n\".format(level)\n            cel_tbl += \"<td\" + style + \">{}</td>\\n\".format(level_count[level])\n            cel_tbl += \"</tr>\\n\"\n        cel_tbl += \"<tr>\\n\"\n        cel_tbl += (\n            \"<td style='font-weight: bold; padding: 5px 20px 5px 20px;'> Total </td>\\n\"\n        )\n        cel_tbl += \"<td\" + style + \"> {} </td>\\n\".format(self.nC)\n        cel_tbl += \"</tr>\\n\"\n        cel_tbl += \"</table>\\n\"\n\n        det_tbl = \"<table>\\n\"\n        det_tbl += \"<tr>\\n\"\n        det_tbl += \"<th></th>\\n\"\n        det_tbl += \"<th\" + style + \" colspan='2'>Mesh extent</th>\\n\"\n        det_tbl += \"<th\" + style + \" colspan='2'>Cell widths</th>\\n\"\n        det_tbl += \"</tr>\\n\"\n\n        det_tbl += \"<tr>\\n\"\n        det_tbl += \"<th></th>\\n\"\n        det_tbl += \"<th\" + style + \">min</th>\\n\"\n        det_tbl += \"<th\" + style + \">max</th>\\n\"\n        det_tbl += \"<th\" + style + \">min</th>\\n\"\n        det_tbl += \"<th\" + style + \">max</th>\\n\"\n        det_tbl += \"</tr>\\n\"\n        for dim in range(self.dim):\n            n_vector = getattr(self, \"nodes_\" + dim_label[dim])\n            det_tbl += \"<tr>\\n\"\n            det_tbl += \"<td\" + style + \">{}</td>\\n\".format(dim_label[dim])\n            det_tbl += \"<td\" + style + \">{}</td>\\n\".format(n_vector[0])\n            det_tbl += \"<td\" + style + \">{}</td>\\n\".format(n_vector[-1])\n            det_tbl += \"<td\" + style + \">{}</td>\\n\".format(mins[dim])\n            det_tbl += \"<td\" + style + \">{}</td>\\n\".format(maxs[dim])\n            det_tbl += \"</tr>\\n\"\n        det_tbl += \"</table>\\n\"\n\n        full_tbl = \"<table>\\n\"\n        full_tbl += \"<tr>\\n\"\n        full_tbl += \"<td style='font-weight: bold; font-size: 1.2em; text-align: center;'>{}</td>\\n\".format(\n            mesh_name\n        )\n        full_tbl += \"<td style='font-size: 1.2em; text-align: center;' colspan='2'>{0:2.2f}% filled</td>\\n\".format(\n            100 * self.fill\n        )\n        full_tbl += \"</tr>\\n\"\n        full_tbl += \"<tr>\\n\"\n\n        full_tbl += \"<td>\\n\"\n        full_tbl += cel_tbl\n        full_tbl += \"</td>\\n\"\n\n        full_tbl += \"<td>\\n\"\n        full_tbl += det_tbl\n        full_tbl += \"</td>\\n\"\n\n        full_tbl += \"</tr>\\n\"\n        full_tbl += \"</table>\\n\"\n\n        return full_tbl\n\n    @BaseTensorMesh.origin.setter\n    def origin(self, value):  # NOQA D102\n        # first use the BaseTensorMesh to set the origin to handle \"0, C, N\"\n        BaseTensorMesh.origin.fset(self, value)\n        # then update the TreeMesh with the hidden value\n        self._set_origin(self._origin)\n\n    def refine_bounding_box(\n        self,\n        points,\n        level=-1,\n        padding_cells_by_level=None,\n        finalize=True,\n        diagonal_balance=None,\n    ):\n        \"\"\"Refine within a bounding box based on the maximum and minimum extent of scattered points.\n\n        This function refines the tree mesh based on the bounding box defined by the\n        maximum and minimum extent of the scattered input `points`. It will refine\n        the tree mesh for each level given.\n\n        It also optionally pads the bounding box at each level based on the number of\n        padding cells at each dimension.\n\n        Parameters\n        ----------\n        points : (N, dim) array_like\n            The bounding box will be the maximum and minimum extent of these points\n        level : int, optional\n            The level of the treemesh to refine the bounding box to.\n            Negative values index tree levels backwards, (e.g. `-1` is `max_level`).\n        padding_cells_by_level : None, int, (n_level) array_like or (n_level, dim) array_like, optional\n            The number of cells to pad the bounding box at each level of refinement.\n            If a single number, each level below ``level`` will be padded with those\n            number of cells. If array_like, `n_level`s below ``level`` will be padded,\n            where if a 1D array, each dimension will be padded with the same number of\n            cells, or 2D array supports variable padding along each dimension. None\n            implies no padding.\n        finalize : bool, optional\n            Whether to finalize the mesh after the call.\n        diagonal_balance : None or bool, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        See Also\n        --------\n        refine_box\n\n        Examples\n        --------\n        Given a set of points, we want to refine the tree mesh with the bounding box\n        that surrounds those points. The arbitrary points we use for this example are\n        uniformly scattered between [3/8, 5/8] in the first and second dimension.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> mesh = discretize.TreeMesh([32, 32])\n        >>> rng = np.random.default_rng(852)\n        >>> points = rng.random((20, 2)) * 0.25 + 3/8\n\n        Now we want to refine to the maximum level, with no padding the in `x`\n        direction and `2` cells in `y`. At the second highest level we want 2 padding\n        cells in each direction beyond that.\n\n        >>> padding = [[0, 2], [2, 2]]\n        >>> mesh.refine_bounding_box(points, -1, padding)\n\n        For demonstration we, overlay the bounding box to show the effects of padding.\n\n        >>> ax = mesh.plot_grid()\n        >>> rect = patches.Rectangle([3/8, 3/8], 1/4, 1/4, facecolor='none', edgecolor='r', linewidth=3)\n        >>> ax.add_patch(rect)\n        >>> plt.show()\n        \"\"\"\n        bsw = np.min(np.atleast_2d(points), axis=0)\n        tnw = np.max(np.atleast_2d(points), axis=0)\n        if level < 0:\n            level = (self.max_level + 1) - (abs(level) % (self.max_level + 1))\n        if level > self.max_level:\n            raise IndexError(f\"Level beyond max octree level, {self.max_level}\")\n\n        # pad based on the number of cells at each level\n        if padding_cells_by_level is None:\n            padding_cells_by_level = np.array([0])\n        padding_cells_by_level = np.asarray(padding_cells_by_level)\n        if padding_cells_by_level.ndim == 0 and level > 1:\n            padding_cells_by_level = np.full(level - 1, padding_cells_by_level)\n        padding_cells_by_level = np.atleast_1d(padding_cells_by_level)\n        if (\n            padding_cells_by_level.ndim == 2\n            and padding_cells_by_level.shape[1] != self.dim\n        ):\n            raise ValueError(\"incorrect dimension for padding_cells_by_level.\")\n\n        h_min = np.r_[[h.min() for h in self.h]]\n        x0 = []\n        xF = []\n        ls = []\n        # The zip will short circuit on the shorter array.\n        for lv, n_pad in zip(np.arange(level, 1, -1), padding_cells_by_level):\n            padding_at_level = n_pad * h_min * 2 ** (self.max_level - lv)\n            bsw = bsw - padding_at_level\n            tnw = tnw + padding_at_level\n            x0.append(bsw)\n            xF.append(tnw)\n            ls.append(lv)\n\n        self.refine_box(\n            x0, xF, ls, finalize=finalize, diagonal_balance=diagonal_balance\n        )\n\n    def refine_points(\n        self,\n        points,\n        level=-1,\n        padding_cells_by_level=None,\n        finalize=True,\n        diagonal_balance=None,\n    ):\n        \"\"\"Refine the mesh at given points to the prescribed level.\n\n        This function refines the tree mesh around the `points`. It will refine\n        the tree mesh for each level given. It also optionally radially pads around each\n        point at each level with the number of padding cells given.\n\n        Parameters\n        ----------\n        points : (N, dim) array_like\n            The points to be refined around.\n        level : int, optional\n            The level of the tree mesh to refine to. Negative values index tree levels\n            backwards, (e.g. `-1` is `max_level`).\n        padding_cells_by_level : None, int, (n_level) array_like, optional\n            The number of cells to pad the bounding box at each level of refinement.\n            If a single number, each level below ``level`` will be padded with those\n            number of cells. If array_like, `n_level`s below ``level`` will be padded.\n            None implies no padding.\n        finalize : bool, optional\n            Whether to finalize the mesh after the call.\n        diagonal_balance : None or bool, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        See Also\n        --------\n        refine_ball, insert_cells\n\n        Examples\n        --------\n        Given a set of points, we want to refine the tree mesh around these points to\n        a prescribed level with a certain amount of padding.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> mesh = discretize.TreeMesh([32, 32])\n\n        Now we want to refine to the maximum level with 1 cell padding around the point,\n        and then refine at the second highest level with at least 3 cells beyond that.\n\n        >>> points = np.array([[0.1, 0.3], [0.6, 0.8]])\n        >>> padding = [1, 3]\n        >>> mesh.refine_points(points, -1, padding)\n\n        >>> ax = mesh.plot_grid()\n        >>> ax.scatter(*points.T, color='C1')\n        >>> plt.show()\n        \"\"\"\n        if level < 0:\n            level = (self.max_level + 1) - (abs(level) % (self.max_level + 1))\n        if level > self.max_level:\n            raise IndexError(f\"Level beyond max octree level, {self.max_level}\")\n\n        # pad based on the number of cells at each level\n        if padding_cells_by_level is None:\n            padding_cells_by_level = np.array([0])\n        padding_cells_by_level = np.asarray(padding_cells_by_level)\n        if padding_cells_by_level.ndim == 0 and level > 1:\n            padding_cells_by_level = np.full(level - 1, padding_cells_by_level)\n        padding_cells_by_level = np.atleast_1d(padding_cells_by_level)\n\n        h_min = np.max([h.min() for h in self.h])\n        radius_at_level = 0.0\n        for lv, n_pad in zip(np.arange(level, 1, -1), padding_cells_by_level):\n            radius_at_level += n_pad * h_min * 2 ** (self.max_level - lv)\n            if radius_at_level == 0:\n                self.insert_cells(\n                    points, lv, finalize=False, diagonal_balance=diagonal_balance\n                )\n            else:\n                self.refine_ball(\n                    points,\n                    radius_at_level,\n                    lv,\n                    finalize=False,\n                    diagonal_balance=diagonal_balance,\n                )\n\n        if finalize:\n            self.finalize()\n\n    def refine_surface(\n        self,\n        xyz,\n        level=-1,\n        padding_cells_by_level=None,\n        pad_up=False,\n        pad_down=True,\n        finalize=True,\n        diagonal_balance=None,\n    ):\n        \"\"\"Refine along a surface triangulated from xyz to the prescribed level.\n\n        This function refines the mesh based on a triangulated surface from the input\n        points. Every cell that intersects the surface will be refined to the given\n        level.\n\n        It also optionally pads the surface at each level based on the number of\n        padding cells at each dimension. It does so by stretching the bounding box of\n        the surface in each dimension.\n\n        Parameters\n        ----------\n        xyz : (N, dim) array_like or tuple\n            The points defining the surface. Will be triangulated along the horizontal\n            dimensions. You are able to supply your own triangulation in 3D by passing\n            a tuple of (`xyz`, `triangle_indices`).\n        level : int, optional\n            The level of the tree mesh to refine to. Negative values index tree levels\n            backwards, (e.g. `-1` is `max_level`).\n        padding_cells_by_level : None, int, (n_level) array_like or (n_level, dim) array_like, optional\n            The number of cells to pad the bounding box at each level of refinement.\n            If a single number, each level below ``level`` will be padded with those\n            number of cells. If array_like, `n_level`s below ``level`` will be padded,\n            where if a 1D array, each dimension will be padded with the same number of\n            cells, or 2D array supports variable padding along each dimension. None\n            implies no padding.\n        pad_up : bool, optional\n            Whether to pad above the surface.\n        pad_down : bool, optional\n            Whether to pad below the surface.\n        finalize : bool, optional\n            Whether to finalize the mesh after the call.\n        diagonal_balance : None or bool, optional\n            Whether to balance cells diagonally in the refinement, `None` implies using\n            the same setting used to instantiate the TreeMesh`.\n\n        See Also\n        --------\n        refine_triangle, refine_vertical_trianglular_prism\n\n        Examples\n        --------\n        In 2D we define the surface as a line segment, which we would like to refine\n        along.\n\n        >>> import discretize\n        >>> import matplotlib.pyplot as plt\n        >>> import matplotlib.patches as patches\n        >>> mesh = discretize.TreeMesh([32, 32])\n\n        This surface is a simple sine curve, which we would like to pad with at least\n        2 cells vertically below at the maximum level, and 3 cells below that at the\n        second highest level.\n\n        >>> x = np.linspace(0.2, 0.8, 51)\n        >>> z = 0.25*np.sin(2*np.pi*x)+0.5\n        >>> xz = np.c_[x, z]\n        >>> mesh.refine_surface(xz, -1, [[0, 2], [0, 3]])\n\n        >>> ax = mesh.plot_grid()\n        >>> ax.plot(x, z, color='C1')\n        >>> plt.show()\n\n        In 3D we define a grid of surface locations with there corresponding elevations.\n        In this example we pad 2 cells at the finest level below the surface, and 3\n        cells down at the next level.\n\n        >>> mesh = discretize.TreeMesh([32, 32, 32])\n        >>> x, y = np.mgrid[0.2:0.8:21j, 0.2:0.8:21j]\n        >>> z = 0.125*np.sin(2*np.pi*x) + 0.5 + 0.125 * np.cos(2 * np.pi * y)\n        >>> points = np.stack([x, y, z], axis=-1).reshape(-1, 3)\n        >>> mesh.refine_surface(points, -1, [[0, 0, 2], [0, 0, 3]])\n\n        >>> v = mesh.cell_levels_by_index(np.arange(mesh.n_cells))\n        >>> fig, axs = plt.subplots(1, 3, figsize=(12,4))\n        >>> mesh.plot_slice(v, ax=axs[0], normal='x', grid=True, clim=[2, 5])\n        >>> mesh.plot_slice(v, ax=axs[1], normal='y', grid=True, clim=[2, 5])\n        >>> mesh.plot_slice(v, ax=axs[2], normal='z', grid=True, clim=[2, 5])\n        >>> plt.show()\n        \"\"\"\n        if level < 0:\n            level = (self.max_level + 1) - (abs(level) % (self.max_level + 1))\n        if level > self.max_level:\n            raise IndexError(f\"Level beyond max octree level, {self.max_level}\")\n\n        # pad based on the number of cells at each level\n        if padding_cells_by_level is None:\n            padding_cells_by_level = np.array([0])\n        padding_cells_by_level = np.asarray(padding_cells_by_level)\n        if padding_cells_by_level.ndim == 0 and level > 1:\n            padding_cells_by_level = np.full(level - 1, padding_cells_by_level)\n        padding_cells_by_level = np.atleast_1d(padding_cells_by_level)\n        if (\n            padding_cells_by_level.ndim == 2\n            and padding_cells_by_level.shape[1] != self.dim\n        ):\n            raise ValueError(\"incorrect dimension for padding_cells_by_level.\")\n\n        if self.dim == 2:\n            xyz = np.asarray(xyz)\n            sorter = np.argsort(xyz[:, 0])\n            xyz = xyz[sorter]\n            n_ps = len(xyz)\n            inds = np.arange(n_ps)\n            simps1 = np.c_[inds[:-1], inds[1:], inds[:-1]] + [0, 0, n_ps]\n            simps2 = np.c_[inds[:-1], inds[1:], inds[1:]] + [n_ps, n_ps, 0]\n            simps = np.r_[simps1, simps2]\n        else:\n            if isinstance(xyz, tuple):\n                xyz, simps = xyz\n                xyz = np.asarray(xyz)\n                simps = np.asarray(simps)\n            else:\n                xyz = np.asarray(xyz)\n                triang = Delaunay(xyz[:, :2])\n                simps = triang.simplices\n            n_ps = len(xyz)\n\n        # calculate bounding box for padding\n        bb_min = np.min(xyz, axis=0)[:-1]\n        bb_max = np.max(xyz, axis=0)[:-1]\n        half_width = (bb_max - bb_min) / 2\n        center = (bb_max + bb_min) / 2\n        points = np.empty((n_ps, self.dim))\n        points[:, -1] = xyz[:, -1]\n\n        h_min = np.r_[[h.min() for h in self.h]]\n        pad = 0.0\n        for lv, n_pad in zip(np.arange(level, 1, -1), padding_cells_by_level):\n            pad += n_pad * h_min * 2 ** (self.max_level - lv)\n            h = 0\n            if pad_up:\n                h += pad[-1]\n            if pad_down:\n                h += pad[-1]\n                points[:, -1] = xyz[:, -1] - pad[-1]\n            horizontal_expansion = (half_width + pad[:-1]) / half_width\n            points[:, :-1] = horizontal_expansion * (xyz[:, :-1] - center) + center\n            if self.dim == 2:\n                triangles = np.r_[points, points + [0, h]][simps]\n                self.refine_triangle(\n                    triangles, lv, finalize=False, diagonal_balance=diagonal_balance\n                )\n            else:\n                triangles = points[simps]\n                self.refine_vertical_trianglular_prism(\n                    triangles, h, lv, finalize=False, diagonal_balance=diagonal_balance\n                )\n\n        if finalize:\n            self.finalize()\n\n    @property\n    def total_nodes(self):\n        \"\"\"Gridded hanging and non-hanging nodes locations.\n\n        This property returns a numpy array of shape\n        ``(n_total_nodes, dim)`` containing gridded locations for\n        all hanging and non-hanging nodes in the mesh.\n\n        Returns\n        -------\n        (n_total_nodes, dim) numpy.ndarray of float\n            Gridded hanging and non-hanging node locations\n        \"\"\"\n        self._error_if_not_finalized(\"total_nodes\")\n        return np.vstack((self.nodes, self.hanging_nodes))\n\n    @property\n    def vntF(self):\n        \"\"\"Vector number of total faces along each axis.\n\n        This property returns the total number of hanging and\n        non-hanging faces along each axis direction. The returned\n        quantity is a list of integers of the form [nFx,nFy,nFz].\n\n        Returns\n        -------\n        list of int\n            Vector number of total faces along each axis\n        \"\"\"\n        return [self.ntFx, self.ntFy] + ([] if self.dim == 2 else [self.ntFz])\n\n    @property\n    def vntE(self):\n        \"\"\"Vector number of total edges along each axis.\n\n        This property returns the total number of hanging and\n        non-hanging edges along each axis direction. The returned\n        quantity is a list of integers of the form [nEx,nEy,nEz].\n\n        Returns\n        -------\n        list of int\n            Vector number of total edges along each axis\n        \"\"\"\n        return [self.ntEx, self.ntEy] + ([] if self.dim == 2 else [self.ntEz])\n\n    @property\n    def stencil_cell_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_stencil_cell_gradient\", None) is None:\n            self._stencil_cell_gradient = sp.vstack(\n                [self.stencil_cell_gradient_x, self.stencil_cell_gradient_y]\n            )\n            if self.dim == 3:\n                self._stencil_cell_gradient = sp.vstack(\n                    [self._stencil_cell_gradient, self.stencil_cell_gradient_z]\n                )\n\n        return self._stencil_cell_gradient\n\n    @property\n    def cell_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DifferentialOperators\n        if getattr(self, \"_cell_gradient\", None) is None:\n            i_s = self.face_boundary_indices\n\n            ix = np.ones(self.nFx)\n            ix[i_s[0]] = 0.0\n            ix[i_s[1]] = 0.0\n            Pafx = sp.diags(ix)\n\n            iy = np.ones(self.nFy)\n            iy[i_s[2]] = 0.0\n            iy[i_s[3]] = 0.0\n            Pafy = sp.diags(iy)\n\n            MfI = self.get_face_inner_product(invert_matrix=True)\n\n            if self.dim == 2:\n                Pi = sp.block_diag([Pafx, Pafy])\n\n            elif self.dim == 3:\n                iz = np.ones(self.nFz)\n                iz[i_s[4]] = 0.0\n                iz[i_s[5]] = 0.0\n                Pafz = sp.diags(iz)\n                Pi = sp.block_diag([Pafx, Pafy, Pafz])\n\n            self._cell_gradient = (\n                -Pi * MfI * self.face_divergence.T * sp.diags(self.cell_volumes)\n            )\n\n        return self._cell_gradient\n\n    @property\n    def cell_gradient_x(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DifferentialOperators\n        if getattr(self, \"_cell_gradient_x\", None) is None:\n            nFx = self.nFx\n            i_s = self.face_boundary_indices\n\n            ix = np.ones(self.nFx)\n            ix[i_s[0]] = 0.0\n            ix[i_s[1]] = 0.0\n            Pafx = sp.diags(ix)\n\n            MfI = self.get_face_inner_product(invert_matrix=True)\n            MfIx = sp.diags(MfI.diagonal()[:nFx])\n\n            self._cell_gradient_x = (\n                -Pafx * MfIx * self.face_x_divergence.T * sp.diags(self.cell_volumes)\n            )\n\n        return self._cell_gradient_x\n\n    @property\n    def cell_gradient_y(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DifferentialOperators\n        if getattr(self, \"_cell_gradient_y\", None) is None:\n            nFx = self.nFx\n            nFy = self.nFy\n            i_s = self.face_boundary_indices\n\n            iy = np.ones(self.nFy)\n            iy[i_s[2]] = 0.0\n            iy[i_s[3]] = 0.0\n            Pafy = sp.diags(iy)\n\n            MfI = self.get_face_inner_product(invert_matrix=True)\n            MfIy = sp.diags(MfI.diagonal()[nFx : nFx + nFy])\n\n            self._cell_gradient_y = (\n                -Pafy * MfIy * self.face_y_divergence.T * sp.diags(self.cell_volumes)\n            )\n\n        return self._cell_gradient_y\n\n    @property\n    def cell_gradient_z(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DifferentialOperators\n        if self.dim == 2:\n            raise TypeError(\"z derivative not defined in 2D\")\n        if getattr(self, \"_cell_gradient_z\", None) is None:\n            nFx = self.nFx\n            nFy = self.nFy\n            i_s = self.face_boundary_indices\n\n            iz = np.ones(self.nFz)\n            iz[i_s[4]] = 0.0\n            iz[i_s[5]] = 0.0\n            Pafz = sp.diags(iz)\n\n            MfI = self.get_face_inner_product(invert_matrix=True)\n            MfIz = sp.diags(MfI.diagonal()[nFx + nFy :])\n\n            self._cell_gradient_z = (\n                -Pafz * MfIz * self.face_z_divergence.T * sp.diags(self.cell_volumes)\n            )\n\n        return self._cell_gradient_z\n\n    @property\n    def face_x_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DifferentialOperators\n        if getattr(self, \"_face_x_divergence\", None) is None:\n            self._face_x_divergence = self.face_divergence[:, : self.nFx]\n        return self._face_x_divergence\n\n    @property\n    def face_y_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DifferentialOperators\n        if getattr(self, \"_face_y_divergence\", None) is None:\n            self._face_y_divergence = self.face_divergence[\n                :, self.nFx : self.nFx + self.nFy\n            ]\n        return self._face_y_divergence\n\n    @property\n    def face_z_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.operators.DifferentialOperators\n        if getattr(self, \"_face_z_divergence\", None) is None:\n            self._face_z_divergence = self.face_divergence[:, self.nFx + self.nFy :]\n        return self._face_z_divergence\n\n    def point2index(self, locs):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.get_containing_cells(locs)\n\n    def cell_levels_by_index(self, indices):\n        \"\"\"Fast function to return a list of levels for the given cell indices.\n\n        Parameters\n        ----------\n        indices: (N) array_like\n            Cell indexes to query\n\n        Returns\n        -------\n        (N) numpy.ndarray of int\n            Levels for the cells.\n        \"\"\"\n        return self._cell_levels_by_indexes(indices)\n\n    def get_interpolation_matrix(  # NOQA D102\n        self, locs, location_type=\"cell_centers\", zeros_outside=False, **kwargs\n    ):\n        # Documentation inherited from discretize.base.BaseMesh\n        if \"locType\" in kwargs:\n            raise TypeError(\n                \"The locType keyword argument has been removed, please use location_type. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n        if \"zerosOutside\" in kwargs:\n            raise TypeError(\n                \"The zerosOutside keyword argument has been removed, please use zeros_outside. \"\n                \"This will be removed in discretize 1.0.0\"\n            )\n        location_type = self._parse_location_type(location_type)\n        if self.dim == 2 and \"z\" in location_type:\n            raise NotImplementedError(\"Unable to interpolate from Z edges/faces in 2D\")\n\n        locs = self._require_ndarray_with_dim(\"locs\", locs, ndim=2, dtype=np.float64)\n        if location_type == \"nodes\":\n            Av = self._getNodeIntMat(locs, zeros_outside)\n        elif location_type in [\"edges_x\", \"edges_y\", \"edges_z\"]:\n            Av = self._getEdgeIntMat(locs, zeros_outside, location_type[-1])\n        elif location_type in [\"faces_x\", \"faces_y\", \"faces_z\"]:\n            Av = self._getFaceIntMat(locs, zeros_outside, location_type[-1])\n        elif location_type in [\"cell_centers\"]:\n            Av = self._getCellIntMat(locs, zeros_outside)\n        else:\n            raise ValueError(\n                \"Location must be a grid location, not {}\".format(location_type)\n            )\n        return Av\n\n    @property\n    def permute_cells(self):\n        \"\"\"Permutation matrix re-ordering of cells sorted by x, then y, then z.\n\n        Returns\n        -------\n        (n_cells, n_cells) scipy.sparse.csr_matrix\n        \"\"\"\n        # TODO: cache these?\n        P = np.lexsort(self.gridCC.T)  # sort by x, then y, then z\n        return sp.identity(self.nC).tocsr()[P]\n\n    @property\n    def permute_faces(self):\n        \"\"\"Permutation matrix re-ordering of faces sorted by x, then y, then z.\n\n        Returns\n        -------\n        (n_faces, n_faces) scipy.sparse.csr_matrix\n        \"\"\"\n        # TODO: cache these?\n        Px = np.lexsort(self.gridFx.T)\n        Py = np.lexsort(self.gridFy.T) + self.nFx\n        if self.dim == 2:\n            P = np.r_[Px, Py]\n        else:\n            Pz = np.lexsort(self.gridFz.T) + (self.nFx + self.nFy)\n            P = np.r_[Px, Py, Pz]\n        return sp.identity(self.nF).tocsr()[P]\n\n    @property\n    def permute_edges(self):\n        \"\"\"Permutation matrix re-ordering of edges sorted by x, then y, then z.\n\n        Returns\n        -------\n        (n_edges, n_edges) scipy.sparse.csr_matrix\n        \"\"\"\n        # TODO: cache these?\n        Px = np.lexsort(self.gridEx.T)\n        Py = np.lexsort(self.gridEy.T) + self.nEx\n        if self.dim == 2:\n            P = np.r_[Px, Py]\n        if self.dim == 3:\n            Pz = np.lexsort(self.gridEz.T) + (self.nEx + self.nEy)\n            P = np.r_[Px, Py, Pz]\n        return sp.identity(self.nE).tocsr()[P]\n\n    @property\n    def cell_state(self):\n        \"\"\"The current state of the cells on the mesh.\n\n        This represents the x, y, z indices of the cells in the base tensor mesh, as\n        well as their levels. It can be used to reconstruct the mesh.\n\n        Returns\n        -------\n        dict\n            dictionary with two entries:\n\n            - ``\"indexes\"``: the indexes of the cells\n            - ``\"levels\"``: the levels of the cells\n        \"\"\"\n        indexes, levels = self.__getstate__()\n        return {\"indexes\": indexes.tolist(), \"levels\": levels.tolist()}\n\n    def validate(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.finalized\n\n    def equals(self, other):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        try:\n            if self.finalized and other.finalized:\n                return super().equals(other)\n        except AttributeError:\n            pass\n        return False\n\n    def __reduce__(self):\n        \"\"\"Return the necessary items to reconstruct this object's state.\"\"\"\n        return TreeMesh, (self.h, self.origin, False), self.__getstate__()\n\n    cellGrad = deprecate_property(\n        \"cell_gradient\", \"cellGrad\", removal_version=\"1.0.0\", error=True\n    )\n    cellGradx = deprecate_property(\n        \"cell_gradient_x\", \"cellGradx\", removal_version=\"1.0.0\", error=True\n    )\n    cellGrady = deprecate_property(\n        \"cell_gradient_y\", \"cellGrady\", removal_version=\"1.0.0\", error=True\n    )\n    cellGradz = deprecate_property(\n        \"cell_gradient_z\", \"cellGradz\", removal_version=\"1.0.0\", error=True\n    )\n    cellGradStencil = deprecate_property(\n        \"cell_gradient_stencil\",\n        \"cellGradStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    faceDivx = deprecate_property(\n        \"face_x_divergence\", \"faceDivx\", removal_version=\"1.0.0\", error=True\n    )\n    faceDivy = deprecate_property(\n        \"face_y_divergence\", \"faceDivy\", removal_version=\"1.0.0\", error=True\n    )\n    faceDivz = deprecate_property(\n        \"face_z_divergence\", \"faceDivz\", removal_version=\"1.0.0\", error=True\n    )\n    maxLevel = deprecate_property(\n        \"max_used_level\", \"maxLevel\", removal_version=\"1.0.0\", error=True\n    )\n    areaFx = deprecate_property(\n        \"face_x_areas\", \"areaFx\", removal_version=\"1.0.0\", error=True\n    )\n    areaFy = deprecate_property(\n        \"face_y_areas\", \"areaFy\", removal_version=\"1.0.0\", error=True\n    )\n    areaFz = deprecate_property(\n        \"face_z_areas\", \"areaFz\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEx = deprecate_property(\n        \"edge_x_lengths\", \"edgeEx\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEy = deprecate_property(\n        \"edge_y_lengths\", \"edgeEy\", removal_version=\"1.0.0\", error=True\n    )\n    edgeEz = deprecate_property(\n        \"edge_z_lengths\", \"edgeEz\", removal_version=\"1.0.0\", error=True\n    )\n    permuteCC = deprecate_property(\n        \"permute_cells\", \"permuteCC\", removal_version=\"1.0.0\", error=True\n    )\n    permuteF = deprecate_property(\n        \"permute_faces\", \"permuteF\", removal_version=\"1.0.0\", error=True\n    )\n    permuteE = deprecate_property(\n        \"permute_edges\", \"permuteE\", removal_version=\"1.0.0\", error=True\n    )\n    faceBoundaryInd = deprecate_property(\n        \"face_boundary_indices\",\n        \"faceBoundaryInd\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    cellBoundaryInd = deprecate_property(\n        \"cell_boundary_indices\",\n        \"cellBoundaryInd\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _aveCC2FxStencil = deprecate_property(\n        \"average_cell_to_total_face_x\",\n        \"_aveCC2FxStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _aveCC2FyStencil = deprecate_property(\n        \"average_cell_to_total_face_y\",\n        \"_aveCC2FyStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _aveCC2FzStencil = deprecate_property(\n        \"average_cell_to_total_face_z\",\n        \"_aveCC2FzStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _cellGradStencil = deprecate_property(\n        \"stencil_cell_gradient\",\n        \"_cellGradStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _cellGradxStencil = deprecate_property(\n        \"stencil_cell_gradient_x\",\n        \"_cellGradxStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _cellGradyStencil = deprecate_property(\n        \"stencil_cell_gradient_y\",\n        \"_cellGradyStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n    _cellGradzStencil = deprecate_property(\n        \"stencil_cell_gradient_z\",\n        \"_cellGradzStencil\",\n        removal_version=\"1.0.0\",\n        error=True,\n    )\n"
  },
  {
    "path": "discretize/unstructured_mesh.py",
    "content": "\"\"\"Module containing unstructured meshes for discretize.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as sp\nfrom scipy.spatial import KDTree\nfrom discretize.utils import Identity, invert_blocks, spzeros, cross2d\nfrom discretize.base import BaseMesh\nfrom discretize._extensions.simplex_helpers import (\n    _build_faces_edges,\n    _build_adjacency,\n    _directed_search,\n    _interp_cc,\n)\nfrom discretize.mixins import InterfaceMixins, SimplexMeshIO\n\n\nclass SimplexMesh(BaseMesh, SimplexMeshIO, InterfaceMixins):\n    \"\"\"Class for traingular (2D) and tetrahedral (3D) meshes.\n\n    Simplex is the abstract term for triangular like elements in an arbitrary dimension.\n    Simplex meshes are subdivided into trianglular (in 2D) or tetrahedral (in 3D)\n    elements. They are capable of representing abstract geometric surfaces, with widely\n    variable element sizes.\n\n    Parameters\n    ----------\n    nodes : (n_nodes, dim) array_like of float\n        Defines every node of the mesh.\n\n    simplices : (n_cells, dim+1) array_like of int\n        This array defines the connectivity of nodes to form cells. Each element\n        indexes into the `nodes` array. Each row defines which nodes make a given cell.\n        This array is sorted along each row and then stored on the mesh.\n\n    Notes\n    -----\n    Only rudimentary checking of the input nodes and simplices is performed, only\n    checking for degenerate simplices who have zero volume. There are no checks for\n    overlapping cells, or for the quality of the mesh.\n\n    Examples\n    --------\n    Here we generate a basic 2D triangular mesh, by triangulating a rectangular domain.\n\n    >>> from discretize import SimplexMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> import matplotlib.tri as tri\n\n    First we define the nodes of our mesh\n\n    >>> X, Y = np.mgrid[-20:20:21j, -10:10:11j]\n    >>> nodes = np.c_[X.reshape(-1), Y.reshape(-1)]\n\n    Then we triangulate the nodes, here we use matplotlib, but you could also use\n    scipy's Delaunay, or any other triangular mesh generator. Essentailly we are\n    creating every node in the mesh, and a list of triangles/tetrahedrals defining which\n    nodes make of each cell.\n\n    >>> triang = tri.Triangulation(nodes[:, 0], nodes[:, 1])\n    >>> simplices = triang.triangles\n\n    Finally we can assemble them into a SimplexMesh\n\n    >>> mesh = SimplexMesh(nodes, simplices)\n    >>> mesh.plot_grid()\n    >>> plt.show()\n    \"\"\"\n\n    _meshType = \"simplex\"\n    _items = {\"nodes\", \"simplices\"}\n\n    def __init__(self, nodes, simplices):\n        # grab copies of the nodes and simplices for protection\n        nodes = np.asarray(nodes)\n        simplices = np.asarray(simplices)\n        dim = nodes.shape[1]\n        if dim not in [3, 2]:\n            raise ValueError(\n                f\"Mesh must be either 2 or 3 dimensions, nodes has {dim} dimension.\"\n            )\n        if simplices.shape[1] != dim + 1:\n            raise ValueError(\n                \"simplices second dimension is not compatible with the mesh dimension. \"\n                f\"Saw {simplices.shape[1]}, and expected {dim + 1}.\"\n            )\n\n        self._nodes = nodes.copy()\n        self._nodes.setflags(write=\"false\")\n        self._simplices = simplices.copy()\n        # sort the simplices by node index to simplify further functions...\n        self._simplices.sort(axis=1)\n        self._simplices.setflags(write=\"false\")\n\n        if self.cell_volumes.min() == 0.0:\n            raise ValueError(\"Triangulation contains degenerate simplices\")\n\n        self._number()\n\n    def _number(self):\n        items = _build_faces_edges(self.simplices)\n        self._simplex_faces = np.array(items[0])\n        self._faces = np.array(items[1])\n        self._simplex_edges = np.array(items[2])\n        self._edges = np.array(items[3])\n        self._n_faces = self._faces.shape[0]\n        self._n_edges = self._edges.shape[0]\n        if self.dim == 3:\n            self._face_edges = np.array(items[4])\n\n    @property\n    def simplices(self):\n        \"\"\"The node indices for all simplexes of the mesh.\n\n        This array defines the connectivity of the mesh. For each simplex this array is\n        sorted by the node index.\n\n        Returns\n        -------\n        (n_cells, dim + 1) numpy.ndarray of int\n        \"\"\"\n        return self._simplices\n\n    @property\n    def neighbors(self):\n        \"\"\"\n        The adjacancy graph of the mesh.\n\n        This array contains the adjacent cell index for each cell of the mesh. For each\n        cell the i'th neighbor is opposite the i'th node, across the i'th face. If a\n        cell has no neighbor in a particular direction, then it is listed as having -1.\n        This also implies that this is a boundary face.\n\n        Returns\n        -------\n        (n_cells, dim + 1) numpy.ndarray of int\n        \"\"\"\n        if getattr(self, \"_neighbors\", None) is None:\n            self._neighbors = np.array(\n                _build_adjacency(self._simplex_faces, self.n_faces)\n            )\n        return self._neighbors\n\n    @property\n    def transform_and_shift(self):\n        \"\"\"\n        The barycentric transformation matrix and shift.\n\n        Returns\n        -------\n        transform : (n_cells, dim, dim) numpy.ndarray\n        shift : (n_cells) numpy.ndarray\n        \"\"\"\n        if getattr(self, \"_transform\", None) is None:\n            # compute the barycentric transforms\n            points = self.nodes\n            simplices = self.simplices\n\n            shift = points[self.simplices[:, -1]]\n\n            T = (points[simplices[:, :-1]] - shift[:, None, :]).transpose((0, 2, 1))\n\n            self._transform = invert_blocks(T)\n            self._shift = shift\n        return self._transform, self._shift\n\n    @property\n    def dim(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.nodes.shape[-1]\n\n    @property\n    def n_nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self._nodes.shape[0]\n\n    @property\n    def nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self._nodes\n\n    @property\n    def n_cells(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.simplices.shape[0]\n\n    @property\n    def cell_centers(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_cell_centers\", None) is None:\n            self._cell_centers = np.mean(self.nodes[self.simplices], axis=1)\n        return self._cell_centers\n\n    @property\n    def cell_volumes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_cell_volumes\", None) is None:\n            simplex_nodes = self._nodes[self.simplices]\n            mats = np.pad(simplex_nodes, ((0, 0), (0, 0), (0, 1)), constant_values=1)\n            V1 = np.abs(np.linalg.det(mats))\n            V1 /= 6 if self.dim == 3 else 2\n            self._cell_volumes = V1\n        return self._cell_volumes\n\n    @property\n    def n_edges(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self._n_edges\n\n    @property\n    def edges(self):  # NOQA D102\n        if getattr(self, \"_edge_locs\", None) is None:\n            self._edge_locs = np.mean(self.nodes[self._edges], axis=1)\n        # Documentation inherited from discretize.base.BaseMesh\n        return self._edge_locs\n\n    @property\n    def edge_tangents(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_edge_tangents\", None) is None:\n            tangents = np.diff(self.nodes[self._edges], axis=1).squeeze()\n            tangents /= np.linalg.norm(tangents, axis=-1)[:, None]\n            self._edge_tangents = tangents\n        return self._edge_tangents\n\n    @property\n    def edge_lengths(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_edge_lengths\", None) is None:\n            self._edge_lengths = np.linalg.norm(\n                np.diff(self.nodes[self._edges], axis=1).squeeze(), axis=-1\n            )\n        return self._edge_lengths\n\n    @property\n    def n_faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self._n_faces\n\n    @property\n    def faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_face_locs\", None) is None:\n            self._face_locs = np.mean(self.nodes[self._faces], axis=1)\n        return self._face_locs\n\n    @property\n    def face_areas(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            return self.edge_lengths\n        else:\n            if getattr(self, \"_face_areas\", None) is None:\n                face_nodes = self._nodes[self._faces]\n                v01 = face_nodes[:, 1] - face_nodes[:, 0]\n                v02 = face_nodes[:, 2] - face_nodes[:, 0]\n                self._face_areas = np.linalg.norm(np.cross(v01, v02), axis=1) / 2\n            return self._face_areas\n\n    @property\n    def face_normals(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_face_normals\", None) is None:\n            if self.dim == 2:\n                # Take the normal as being the cross product of edge_tangents\n                # and a unit vector in a \"3rd\" dimension.\n                normal = np.c_[self.edge_tangents[:, 1], -self.edge_tangents[:, 0]]\n            else:\n                # define normal as |01 x 02|\n                # therefore clockwise path about the normal is 0->1->2->0\n                face_nodes = self._nodes[self._faces]\n                v01 = face_nodes[:, 1] - face_nodes[:, 0]\n                v02 = face_nodes[:, 2] - face_nodes[:, 0]\n                normal = np.cross(v01, v02)\n                normal /= np.linalg.norm(normal, axis=1)[:, None]\n            self._face_normals = normal\n        return self._face_normals\n\n    @property\n    def face_divergence(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_face_divergence\", None) is None:\n            areas = self.face_areas\n            normals = self.face_normals\n\n            # approx outward normals (to figure out if face normal is opposite the outward direction)\n            test = self.faces[self._simplex_faces] - self.cell_centers[:, None, :]\n            dirs = np.einsum(\"ijk,ijk->ij\", normals[self._simplex_faces], test)\n\n            Aijs = areas[self._simplex_faces] / self.cell_volumes[:, None]\n            Aijs[dirs < 0] *= -1\n\n            Aijs = Aijs.reshape(-1)\n            ind_ptr = (self.dim + 1) * np.arange(self.n_cells + 1)\n            col_inds = self._simplex_faces.reshape(-1)\n            self._face_divergence = sp.csr_matrix(\n                (Aijs, col_inds, ind_ptr), shape=(self.n_cells, self.n_faces)\n            )\n        return self._face_divergence\n\n    @property\n    def nodal_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_nodal_gradient\", None) is None:\n            ind_ptr = 2 * np.arange(self.n_edges + 1)\n            col_inds = self._edges.reshape(-1)\n            Aijs = ((1.0 / self.edge_lengths[:, None]) * [-1, 1]).reshape(-1)\n            self._nodal_gradient = sp.csr_matrix(\n                (Aijs, col_inds, ind_ptr), shape=(self.n_edges, self.n_nodes)\n            )\n        return self._nodal_gradient\n\n    @property\n    def edge_curl(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_edge_curl\", None) is None:\n            dim = self.dim\n            n_edges = self.n_edges\n            if dim == 2:\n                face_edges = self._simplex_edges\n                face_nodes = self.nodes[self.simplices]\n                face_areas = self.cell_volumes\n                n_faces = self.n_cells\n            else:\n                face_edges = self._face_edges\n                face_nodes = self.nodes[self._faces]\n                face_areas = self.face_areas\n                n_faces = self.n_faces\n\n            ind_ptr = 3 * np.arange(n_faces + 1)\n            col_inds = face_edges.reshape(-1)\n\n            # edge tangents point from lower node to higher node\n            # clockwise about the face normal on face is path from 0 -> 1 -> 2 -> 0\n            # so for each face can take the dot product of the path with the edge tangent\n\n            l01 = face_nodes[:, 1] - face_nodes[:, 0]  # path opposite node 2\n            l12 = face_nodes[:, 2] - face_nodes[:, 1]  # path opposite node 0\n            l20 = face_nodes[:, 0] - face_nodes[:, 2]  # path opposite node 1\n\n            face_path_tangents = self.edge_tangents[face_edges]\n\n            if dim == 2:\n                # need to do an adjustment in 2D for the places where the simplices\n                # are not oriented counter clockwise about the +z axis\n                # cp = np.cross(l01, -l20)\n                # cp is a bunch of 1s (where simplices are CCW) and -1s (where simplices are CW)\n                # (but we take the sign here to guard against numerical precision)\n                cp = np.sign(cross2d(l20, l01))\n                face_areas = face_areas * cp\n                # don't due *= here\n\n            Aijs = (\n                np.c_[\n                    np.einsum(\"ij,ij->i\", face_path_tangents[:, 0], l12),\n                    np.einsum(\"ij,ij->i\", face_path_tangents[:, 1], l20),\n                    np.einsum(\"ij,ij->i\", face_path_tangents[:, 2], l01),\n                ]\n                / face_areas[:, None]\n            ).reshape(-1)\n\n            self._edge_curl = sp.csr_matrix(\n                (Aijs, col_inds, ind_ptr), shape=(n_faces, n_edges)\n            )\n        return self._edge_curl\n\n    def __validate_model(self, model, invert_model=False):\n        n_cells = self.n_cells\n        dim = self.dim\n        # determines the tensor type of the model and reshapes it properly\n        model = np.atleast_1d(model)\n        n_aniso = ((dim + 1) * dim) // 2\n        if model.size == n_aniso * n_cells:\n            # model is fully anisotropic\n            # reshape it into a stack of dim x dim matrices\n            if model.ndim == 1:\n                model = model.reshape((-1, n_aniso), order=\"F\")\n            vals = model\n            if self.dim == 2:\n                model = np.stack(\n                    [[vals[:, 0], vals[:, 2]], [vals[:, 2], vals[:, 1]]]\n                ).transpose((2, 0, 1))\n            else:\n                model = np.stack(\n                    [\n                        [vals[:, 0], vals[:, 3], vals[:, 4]],\n                        [vals[:, 3], vals[:, 1], vals[:, 5]],\n                        [vals[:, 4], vals[:, 5], vals[:, 2]],\n                    ]\n                ).transpose((2, 0, 1))\n        elif model.size == dim * n_cells:\n            if model.ndim == 1:\n                model = model.reshape((n_cells, dim), order=\"F\")\n            model = model.reshape(-1)\n\n        if invert_model:\n            if model.ndim == 1:\n                model = 1.0 / model\n            else:\n                model = invert_blocks(model)\n        return model\n\n    def __get_inner_product_projection_matrices(\n        self, i_type, with_volume=True, return_pointers=True\n    ):\n        if getattr(self, \"_proj_stash\", None) is None:\n            self._proj_stash = {}\n        key = (i_type, with_volume)\n        if key not in self._proj_stash:\n            dim = self.dim\n            n_cells = self.n_cells\n            if i_type == \"F\":\n                vecs = self.face_normals\n                n_items = self.n_faces\n                simplex_items = self._simplex_faces\n                if dim == 2:\n                    node_items = np.array([[1, 2], [0, 2], [0, 1]])\n                else:\n                    node_items = np.array([[1, 2, 3], [0, 2, 3], [0, 1, 3], [0, 1, 2]])\n            elif i_type == \"E\":\n                vecs = self.edge_tangents\n                n_items = self.n_edges\n                simplex_items = self._simplex_edges\n                if dim == 2:\n                    node_items = np.array([[1, 2], [0, 2], [0, 1]])\n                elif dim == 3:\n                    node_items = np.array([[1, 2, 3], [0, 2, 4], [0, 1, 5], [3, 4, 5]])\n\n            Ps = []\n            # Precalc indptr and values for the projection matrix\n            P_indptr = np.arange(dim * n_cells + 1)\n            ones = np.ones(dim * n_cells)\n            if with_volume:\n                V = np.sqrt(self.cell_volumes / (dim + 1))\n\n            # precalculate indices for the block diagonal matrix\n            d = np.ones(dim, dtype=int)[:, None] * np.arange(dim)\n            t = np.arange(n_cells)\n            T_col_inds = (d + t[:, None, None] * dim).reshape(-1)\n            T_ind_ptr = dim * np.arange(dim * n_cells + 1)\n\n            for i in range(dim + 1):\n                # array which selects the items associated with node i of each simplex...\n                item_inds = np.take(simplex_items, node_items[i], axis=1)\n                P_col_inds = item_inds.reshape(-1)\n                P = sp.csr_matrix(\n                    (ones, P_col_inds, P_indptr), shape=(dim * n_cells, n_items)\n                )\n\n                item_vectors = vecs[item_inds]\n                trans_inv = invert_blocks(item_vectors)\n                if with_volume:\n                    trans_inv *= V[:, None, None]\n                T = sp.csr_matrix(\n                    (trans_inv.reshape(-1), T_col_inds, T_ind_ptr),\n                    shape=(dim * n_cells, dim * n_cells),\n                )\n                Ps.append(T @ P)\n\n            self._proj_stash[key] = (Ps, T_col_inds, T_ind_ptr)\n        Ps, T_col_inds, T_ind_ptr = self._proj_stash[key]\n        if return_pointers:\n            return Ps, (T_col_inds, T_ind_ptr)\n        else:\n            return Ps\n\n    def __get_inner_product(self, i_type, model, invert_model):\n        Ps, (T_col_inds, T_ind_ptr) = self.__get_inner_product_projection_matrices(\n            i_type\n        )\n        n_cells = self.n_cells\n        dim = self.dim\n        if model is None:\n            Mu = Identity()\n        else:\n            model = self.__validate_model(model, invert_model)\n            if model.size == 1:\n                Mu = sp.diags((model,), (0,), shape=(dim * n_cells, dim * n_cells))\n            elif model.size == n_cells:\n                Mu = sp.diags(np.repeat(model, dim))\n            elif model.size == dim * n_cells:\n                # diagonally anisotropic model\n                Mu = sp.diags(model)\n            elif model.size == (dim * dim) * n_cells:\n                Mu = sp.csr_matrix(\n                    (model.reshape(-1), T_col_inds, T_ind_ptr),\n                    shape=(dim * n_cells, dim * n_cells),\n                )\n            else:\n                raise ValueError(\"Unrecognized size of model vector\")\n\n        A = np.sum([P.T @ Mu @ P for P in Ps])\n        return A\n\n    def get_face_inner_product(  # NOQA D102\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        do_fast=True,\n    ):\n        # Documentation inherited from discretize.base.BaseMesh\n        if invert_matrix:\n            raise NotImplementedError(\n                \"The inverse of the inner product matrix with a tetrahedral mesh is not supported.\"\n            )\n        return self.__get_inner_product(\"F\", model, invert_model)\n\n    def get_edge_inner_product(  # NOQA D102\n        self,\n        model=None,\n        invert_model=False,\n        invert_matrix=False,\n        do_fast=True,\n    ):\n        # Documentation inherited from discretize.base.BaseMesh\n        if invert_matrix:\n            raise NotImplementedError(\n                \"The inverse of the inner product matrix with a tetrahedral mesh is not supported.\"\n            )\n        return self.__get_inner_product(\"E\", model, invert_model)\n\n    def __get_inner_product_deriv_func(self, i_type, model):\n        Ps, _ = self.__get_inner_product_projection_matrices(i_type)\n        dim = self.dim\n        n_cells = self.n_cells\n        if model.size == 1:\n            tensor_type = 0\n        elif model.size == n_cells:\n            tensor_type = 1\n            col_inds = np.repeat(np.arange(n_cells), dim)\n            ind_ptr = np.arange(n_cells * dim + 1)\n        elif model.size == dim * n_cells:\n            tensor_type = 2\n            col_inds = np.arange(dim * n_cells).reshape((n_cells, dim), order=\"F\")\n            col_inds = col_inds.reshape(-1)\n            ind_ptr = np.arange(n_cells * dim + 1)\n        elif model.size == (((dim + 1) * dim) // 2) * n_cells:\n            tensor_type = 3\n            # create a stencil that goes from the model vector ordering\n            # into the anisotropy tensor\n            if dim == 2:\n                stencil = np.array([[0, 2], [2, 1]])\n            elif dim == 3:\n                stencil = np.array([[0, 3, 4], [3, 1, 5], [4, 5, 2]])\n            col_inds = n_cells * stencil + np.arange(n_cells)[:, None, None]\n            col_inds = col_inds.reshape(-1)\n            ind_ptr = dim * np.arange(n_cells * dim + 1)\n        else:\n            raise ValueError(\"Unrecognized size of model vector\")\n        inv_items = model.size\n\n        if i_type == \"F\":\n            n_items = self.n_faces\n        elif i_type == \"E\":\n            n_items = self.n_edges\n\n        def func(v):\n            dMdm = spzeros(n_items, inv_items)\n            if tensor_type == 0:\n                for P in Ps:\n                    dMdm = dMdm + sp.csr_matrix(\n                        (P.T * (P * v), (range(n_items), np.zeros(n_items))),\n                        shape=(n_items, inv_items),\n                    )\n            elif tensor_type == 1:\n                for P in Ps:\n                    ys = P @ v\n                    dMdm = dMdm + P.T @ sp.csr_matrix(\n                        (ys, col_inds, ind_ptr), shape=(n_cells * dim, inv_items)\n                    )\n            elif tensor_type == 2:\n                for P in Ps:\n                    ys = P @ v\n                    dMdm = dMdm + P.T @ sp.csr_matrix(\n                        (ys, col_inds, ind_ptr), shape=(n_cells * dim, inv_items)\n                    )\n            elif tensor_type == 3:\n                for P in Ps:\n                    ys = P @ v\n                    ys = np.repeat(ys, dim).reshape((-1, dim, dim))\n                    ys = ys.transpose((0, 2, 1)).reshape(-1)\n                    dMdm = dMdm + P.T @ sp.csr_matrix(\n                        (ys, col_inds, ind_ptr), shape=(n_cells * dim, inv_items)\n                    )\n            return dMdm\n\n        return func\n\n    def get_face_inner_product_deriv(  # NOQA D102\n        self, model, do_fast=True, invert_model=False, invert_matrix=False\n    ):\n        # Documentation inherited from discretize.base.BaseMesh\n        if invert_model:\n            raise NotImplementedError(\n                \"Inverted model derivatives are not supported here\"\n            )\n        if invert_matrix:\n            raise NotImplementedError(\"Inverted matrix derivatives are not supported\")\n        return self.__get_inner_product_deriv_func(\"F\", model)\n\n    def get_edge_inner_product_deriv(  # NOQA D102\n        self, model, do_fast=True, invert_model=False, invert_matrix=False\n    ):\n        # Documentation inherited from discretize.base.BaseMesh\n        if invert_model:\n            raise NotImplementedError(\n                \"Inverted model derivatives are not supported here\"\n            )\n        if invert_matrix:\n            raise NotImplementedError(\"Inverted matrix derivatives are not supported\")\n        return self.__get_inner_product_deriv_func(\"E\", model)\n\n    def _get_edge_surf_int_proj_mats(self, only_boundary=False, with_area=True):\n        \"\"\"Return the projection operators for integrating edges on each face.\n\n        Parameters\n        ----------\n        only_boundary : bool, optional\n            Whether to only operate on the boundary faces or not.\n        with_area : bool, optional\n            Whether to include the face area.\n\n        Returns\n        -------\n        list of (3 * n_faces, n_edges) scipy.sparse.csr_matrix\n        \"\"\"\n        # edges associated with each face... can just get the indices of the curl...\n        face_edges = self._face_edges\n        face_areas = self.face_areas\n        if only_boundary:\n            bf_inds = self.boundary_face_list\n            face_edges = face_edges[bf_inds]\n            face_areas = face_areas[bf_inds]\n            face_normals = self.boundary_face_outward_normals\n        else:\n            face_normals = self.face_normals\n\n        # face_edges for these are:\n        edge_inds = [[1, 2], [0, 2], [0, 1]]\n\n        n_f = face_edges.shape[0]\n\n        ones = np.ones(n_f * 2)\n        P_indptr = np.arange(2 * n_f + 1)\n\n        d = np.ones(3, dtype=int)[:, None] * np.arange(2)\n        t = np.arange(n_f)\n        T_col_inds = (d + t[:, None, None] * 2).reshape(-1)\n        T_ind_ptr = 2 * np.arange(3 * n_f + 1)\n\n        Ps = []\n\n        # translate c to fortran ordering\n        C2F_col_inds = np.arange(n_f * 3).reshape((-1, 3), order=\"C\").reshape(-1)\n        C2F_row_inds = np.arange(n_f * 3).reshape((-1, 3), order=\"F\").reshape(-1)\n        C2F = sp.csr_matrix(\n            (np.ones(n_f * 3), (C2F_row_inds, C2F_col_inds)), shape=(n_f * 3, n_f * 3)\n        )\n\n        for i in range(3):\n            # matrix which selects the edges associate with each of the nodes of each boundary face\n            node_edges = face_edges[:, edge_inds[i]]\n            P = sp.csr_matrix(\n                (ones, node_edges.reshape(-1), P_indptr), shape=(2 * n_f, self.n_edges)\n            )\n\n            edge_dirs = self.edge_tangents[node_edges]\n            t_for = np.concatenate((edge_dirs, face_normals[:, None, :]), axis=1)\n            t_inv = invert_blocks(t_for)\n            t_inv = t_inv[:, :, :-1] / 3  # n_edges_per_thing\n\n            if with_area:\n                t_inv *= face_areas[:, None, None]\n\n            T = C2F @ sp.csr_matrix(\n                (t_inv.reshape(-1), T_col_inds, T_ind_ptr),\n                shape=(3 * n_f, 2 * n_f),\n            )\n            Ps.append((T @ P))\n        return Ps\n\n    @property\n    def cell_centers_tree(self):\n        \"\"\"A KDTree object built from the cell centers.\n\n        Returns\n        -------\n        scipy.spatial.KDTree\n        \"\"\"\n        if getattr(self, \"_cc_tree\", None) is None:\n            self._cc_tree = KDTree(self.cell_centers)\n        return self._cc_tree\n\n    def point2index(self, locs):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        tree = self.cell_centers_tree\n        # for each location, find the nearest cell center as an initial guess for\n        # the nearest simplex, then use a directed search to further refine\n        _, nearest_cc = tree.query(locs)\n        nodes = self.nodes\n        simplex_nodes = self.simplices\n        transform, shift = self.transform_and_shift\n        return _directed_search(\n            np.atleast_2d(locs),\n            np.atleast_1d(nearest_cc),\n            nodes,\n            simplex_nodes,\n            self.neighbors,\n            transform,\n            shift,\n            return_bary=False,\n        )\n\n    def get_interpolation_matrix(  # NOQA D102\n        self, loc, location_type=\"cell_centers\", zeros_outside=False, **kwargs\n    ):\n        # Documentation inherited from discretize.base.BaseMesh\n        location_type = self._parse_location_type(location_type)\n        tree = self.cell_centers_tree\n        # for each location, find the nearest cell center as an initial guess for\n        # the nearest simplex, then use a directed search to further refine\n        loc = np.atleast_2d(loc)\n        _, nearest_cc = tree.query(loc)\n        nodes = self.nodes\n        simplex_nodes = self.simplices\n        transform, shift = self.transform_and_shift\n\n        inds, barys = _directed_search(\n            loc,\n            nearest_cc,\n            nodes,\n            simplex_nodes,\n            self.neighbors,\n            transform,\n            shift,\n            zeros_outside=zeros_outside,\n            return_bary=True,\n        )\n\n        if zeros_outside:\n            barys[inds == -1] = 0.0\n\n        n_loc = len(loc)\n        location_type = self._parse_location_type(location_type)\n        if location_type == \"nodes\":\n            nodes_per_cell = self.dim + 1\n            ind_ptr = nodes_per_cell * np.arange(n_loc + 1)\n            col_inds = simplex_nodes[inds].reshape(-1)\n            Aij = barys.reshape(-1)\n            n_items = self.n_nodes\n        elif location_type == \"cell_centers\":\n            # detemine which node each point is closest to.\n            which_node = simplex_nodes[inds, np.argmax(barys, axis=-1)]\n            # this matrix can be used to lookup which cells a given node touch,\n            # which will also be the cells used to interpolate from.\n            mat = self.average_node_to_cell.T[which_node].tocsr()\n            # this will overwrite the \"mat\" matrices data to create the interpolation\n            _interp_cc(loc, self.cell_centers, mat.data, mat.indices, mat.indptr)\n            if zeros_outside:\n                e = np.ones(n_loc)\n                e[inds == -1] = 0.0\n                mat = sp.diags(e, format=\"csr\") @ mat\n            return mat\n        else:\n            component = location_type[-1]\n            if component == \"x\":\n                i_dir = 0\n            elif component == \"y\":\n                i_dir = 1\n            else:\n                i_dir = -1\n            if location_type[:-2] == \"edges\":\n                # grab the barycentric transforms associated with each simplex:\n                ts = transform[inds, :, i_dir]\n                ts = np.hstack((ts, -ts.sum(axis=1)[:, None]))\n\n                # edges_x, edges_y, edges_z\n                # grab edge lengths\n                lengths = self.edge_lengths\n\n                # use 1-form Whitney basis functions for (edges)\n                edges = self._simplex_edges[inds]\n\n                # (1, 2), (0, 2), (0, 1)\n                e0 = (barys[:, 1] * ts[:, 2] - barys[:, 2] * ts[:, 1]) * lengths[\n                    edges[:, 0]\n                ]\n                e1 = (barys[:, 0] * ts[:, 2] - barys[:, 2] * ts[:, 0]) * lengths[\n                    edges[:, 1]\n                ]\n                e2 = (barys[:, 0] * ts[:, 1] - barys[:, 1] * ts[:, 0]) * lengths[\n                    edges[:, 2]\n                ]\n                if self.dim == 3:\n                    # (0, 3), (1, 3), (2, 3)\n                    e3 = (barys[:, 0] * ts[:, 3] - barys[:, 3] * ts[:, 0]) * lengths[\n                        edges[:, 3]\n                    ]\n                    e4 = (barys[:, 1] * ts[:, 3] - barys[:, 3] * ts[:, 1]) * lengths[\n                        edges[:, 4]\n                    ]\n                    e5 = (barys[:, 2] * ts[:, 3] - barys[:, 3] * ts[:, 2]) * lengths[\n                        edges[:, 5]\n                    ]\n                    Aij = np.c_[e0, e1, e2, e3, e4, e5].reshape(-1)\n                    ind_ptr = 6 * np.arange(n_loc + 1)\n                else:\n                    Aij = np.c_[e0, e1, e2].reshape(-1)\n                    ind_ptr = 3 * np.arange(n_loc + 1)\n                col_inds = edges.reshape(-1)\n                n_items = self.n_edges\n            elif location_type[:-2] == \"faces\":\n                # grab the barycentric transforms associated with each simplex:\n                ts = transform[inds, :]\n                ts = np.hstack((ts, -ts.sum(axis=1)[:, None]))\n                # use  Whitney 2 - form basis functions for face vector interp\n                faces = self._simplex_faces[inds]\n                areas = self.face_areas\n\n                # [1, 2], [0, 2], [0, 1]\n                if self.dim == 2:\n                    # i j k\n                    # t0 t1 t2\n                    # 0 0 1\n                    # t1 * i - t0 * j\n\n                    f0 = (\n                        cross2d(barys[:, 1:], ts[:, 1:, 1 - i_dir]) * areas[faces[:, 0]]\n                    )\n                    f1 = (\n                        cross2d(barys[:, [0, 2]], ts[:, [0, 2], 1 - i_dir])\n                        * areas[faces[:, 1]]\n                    )\n                    f2 = (\n                        cross2d(barys[:, :-1], ts[:, :-1, 1 - i_dir])\n                        * areas[faces[:, 2]]\n                    )\n                    if i_dir == 1:\n                        f0 *= -1\n                        f1 *= -1\n                        f2 *= -1\n                    Aij = np.c_[f0, f1, f2].reshape(-1)\n                    ind_ptr = 3 * np.arange(n_loc + 1)\n                else:\n                    # [1, 2, 3], [0, 2, 3], [0, 1, 3], [0, 1, 2]\n                    # f123 = (L1 * G_2 x G_3 + L2 * G_3 x G_1 + L3 * G_1 x G_2)\n                    # f023 = (L0 * G_2 x G_3 + L2 * G_3 x G_0 + L3 * G_0 x G_2)\n                    # f013 = (L0 * G_1 x G_3 + L1 * G_3 x G_0 + L3 * G_0 x G_1)\n                    # f012 = (L0 * G_1 x G_2 + L1 * G_2 x G_0 + L2 * G_0 x G_1)\n                    f0 = (\n                        2\n                        * (\n                            barys[:, 1] * (np.cross(ts[:, 2], ts[:, 3])[:, i_dir])\n                            + barys[:, 2] * (np.cross(ts[:, 3], ts[:, 1])[:, i_dir])\n                            + barys[:, 3] * (np.cross(ts[:, 1], ts[:, 2])[:, i_dir])\n                        )\n                        * areas[faces[:, 0]]\n                    )\n                    f1 = (\n                        2\n                        * (\n                            barys[:, 0] * (np.cross(ts[:, 2], ts[:, 3])[:, i_dir])\n                            + barys[:, 2] * (np.cross(ts[:, 3], ts[:, 0])[:, i_dir])\n                            + barys[:, 3] * (np.cross(ts[:, 0], ts[:, 2])[:, i_dir])\n                        )\n                        * areas[faces[:, 1]]\n                    )\n                    f2 = (\n                        2\n                        * (\n                            barys[:, 0] * (np.cross(ts[:, 1], ts[:, 3])[:, i_dir])\n                            + barys[:, 1] * (np.cross(ts[:, 3], ts[:, 0])[:, i_dir])\n                            + barys[:, 3] * (np.cross(ts[:, 0], ts[:, 1])[:, i_dir])\n                        )\n                        * areas[faces[:, 2]]\n                    )\n                    f3 = (\n                        2\n                        * (\n                            barys[:, 0] * (np.cross(ts[:, 1], ts[:, 2])[:, i_dir])\n                            + barys[:, 1] * (np.cross(ts[:, 2], ts[:, 0])[:, i_dir])\n                            + barys[:, 2] * (np.cross(ts[:, 0], ts[:, 1])[:, i_dir])\n                        )\n                        * areas[faces[:, 3]]\n                    )\n                    Aij = np.c_[f0, f1, f2, f3].reshape(-1)\n                    ind_ptr = 4 * np.arange(n_loc + 1)\n                col_inds = faces.reshape(-1)\n                n_items = self.n_faces\n        return sp.csr_matrix((Aij, col_inds, ind_ptr), shape=(n_loc, n_items))\n\n    @property\n    def average_node_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_cell\", None) is None:\n            nodes_per_cell = self.dim + 1\n            n_cells = self.n_cells\n\n            ind_ptr = nodes_per_cell * np.arange(n_cells + 1)\n            col_inds = self.simplices.reshape(-1)\n            Aij = np.full(nodes_per_cell * n_cells, 1 / nodes_per_cell)\n            self._average_node_to_cell = sp.csr_matrix(\n                (Aij, col_inds, ind_ptr), shape=(n_cells, self.n_nodes)\n            )\n        return self._average_node_to_cell\n\n    @property\n    def average_node_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_face\", None) is None:\n            nodes_per_face = self.dim\n            n_faces = self.n_faces\n\n            ind_ptr = nodes_per_face * np.arange(n_faces + 1)\n            col_inds = self._faces.reshape(-1)\n            Aij = np.full(nodes_per_face * n_faces, 1 / nodes_per_face)\n            self._average_node_to_face = sp.csr_matrix(\n                (Aij, col_inds, ind_ptr), shape=(n_faces, self.n_nodes)\n            )\n        return self._average_node_to_face\n\n    @property\n    def average_node_to_edge(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_node_to_edge\", None) is None:\n            n_edges = self.n_edges\n\n            ind_ptr = 2 * np.arange(n_edges + 1)\n            col_inds = self._edges.reshape(-1)\n            Aij = np.full(2 * n_edges, 0.5)\n            self._average_node_to_edge = sp.csr_matrix(\n                (Aij, col_inds, ind_ptr), shape=(n_edges, self.n_nodes)\n            )\n        return self._average_node_to_edge\n\n    @property\n    def average_cell_to_node(self):\n        \"\"\"Averaging matrix from cell centers to nodes.\n\n        The averaging operation uses a volume weighting scheme.\n\n        Returns\n        -------\n        (n_nodes, n_cells) scipy.sparse.csr_matrix\n        \"\"\"\n        # this reproduces linear functions everywhere except on the boundary nodes\n        if getattr(self, \"_average_cell_to_node\", None) is None:\n            simps = self.simplices\n            cells = np.broadcast_to(\n                np.arange(self.n_cells)[:, None], simps.shape\n            ).reshape(-1)\n            weights = np.broadcast_to(self.cell_volumes[:, None], simps.shape).reshape(\n                -1\n            )\n            simps = simps.reshape(-1)\n\n            A = sp.csr_matrix(\n                (weights, (simps, cells)), shape=(self.n_nodes, self.n_cells)\n            )\n            norm = sp.diags(1.0 / np.asarray(A.sum(axis=1))[:, 0])\n            self._average_cell_to_node = norm @ A\n        return self._average_cell_to_node\n\n    @property\n    def average_cell_to_edge(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        # Simple averaging of all cells with a common edge\n        if getattr(self, \"_average_cell_to_edge\", None) is None:\n            simps = self._simplex_edges\n            cells = np.broadcast_to(\n                np.arange(self.n_cells)[:, None], simps.shape\n            ).reshape(-1)\n            weights = np.broadcast_to(self.cell_volumes[:, None], simps.shape).reshape(\n                -1\n            )\n            simps = simps.reshape(-1)\n\n            A = sp.csr_matrix(\n                (weights, (simps, cells)), shape=(self.n_edges, self.n_cells)\n            )\n            norm = sp.diags(1.0 / np.asarray(A.sum(axis=1))[:, 0])\n            self._average_cell_to_edge = norm @ A\n        return self._average_cell_to_edge\n\n    @property\n    def average_face_to_cell_vector(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_face_to_cell_vector\", None) is None:\n            dim = self.dim\n            n_cells = self.n_cells\n            n_faces = self.n_faces\n\n            nodes_per_cell = dim + 1\n\n            Av = sp.csr_matrix((dim * n_cells, n_faces))\n            Ps = self.__get_inner_product_projection_matrices(\n                \"F\", with_volume=False, return_pointers=False\n            )\n            for P in Ps:\n                Av = Av + 1 / (nodes_per_cell) * P\n            # Av needs to be re-ordered to comply with discretize standard\n            ind = np.arange(Av.shape[0]).reshape(n_cells, -1).flatten(order=\"F\")\n            P = sp.eye(Av.shape[0], format=\"csr\")[ind]\n            self._average_face_to_cell_vector = P @ Av\n\n        return self._average_face_to_cell_vector\n\n    @property\n    def average_edge_to_cell_vector(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_edge_to_cell_vector\", None) is None:\n            dim = self.dim\n            n_cells = self.n_cells\n            n_edges = self.n_edges\n            nodes_per_cell = dim + 1\n\n            Av = sp.csr_matrix((dim * n_cells, n_edges))\n            # Precalc indptr and values for the projection matrix\n            Ps = self.__get_inner_product_projection_matrices(\n                \"E\", with_volume=False, return_pointers=False\n            )\n            for P in Ps:\n                Av = Av + 1 / (nodes_per_cell) * P\n            # Av needs to be re-ordered to comply with discretize standard\n            ind = np.arange(Av.shape[0]).reshape(n_cells, -1).flatten(order=\"F\")\n            P = sp.eye(Av.shape[0], format=\"csr\")[ind]\n            self._average_edge_to_cell_vector = P @ Av\n        return self._average_edge_to_cell_vector\n\n    @property\n    def average_face_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_face_to_cell\", None) is None:\n            n_cells = self.n_cells\n            n_faces = self.n_faces\n            col_inds = self._simplex_faces\n            n_face_per_cell = col_inds.shape[1]\n            Aij = np.full((n_cells, n_face_per_cell), 1.0 / n_face_per_cell)\n            row_ptr = np.arange(n_cells + 1) * (n_face_per_cell)\n\n            self._average_face_to_cell = sp.csr_matrix(\n                (Aij.reshape(-1), col_inds.reshape(-1), row_ptr),\n                shape=(n_cells, n_faces),\n            )\n        return self._average_face_to_cell\n\n    @property\n    def average_edge_to_cell(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if getattr(self, \"_average_edge_to_cell\", None) is None:\n            n_cells = self.n_cells\n            n_edges = self.n_edges\n            col_inds = self._simplex_edges\n            n_edge_per_cell = col_inds.shape[1]\n            Aij = np.full((n_cells, n_edge_per_cell), 1.0 / (n_edge_per_cell))\n            row_ptr = np.arange(n_cells + 1) * (n_edge_per_cell)\n\n            self._average_edge_to_cell = sp.csr_matrix(\n                (Aij.reshape(-1), col_inds.reshape(-1), row_ptr),\n                shape=(n_cells, n_edges),\n            )\n        return self._average_edge_to_cell\n\n    @property\n    def average_edge_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            # in 2D edges are at the same location as faces\n            return sp.eye(self.n_edges, self.n_edges)\n\n        if getattr(self, \"_average_edge_to_face\", None) is None:\n            # in 3D there are three edges per face\n            n_faces = self.n_faces\n            n_edges = self.n_edges\n            face_edges = self._face_edges\n\n            ind_ptr = 3 * np.arange(n_faces + 1)\n            col_inds = face_edges.reshape(-1)\n            Aijs = np.full(3 * self.n_faces, 1 / 3)\n            self._average_edge_to_face = sp.csr_matrix(\n                (Aijs, col_inds, ind_ptr), shape=(n_faces, n_edges)\n            )\n        return self._average_edge_to_face\n\n    @property\n    def average_cell_to_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n\n        if getattr(self, \"_average_cell_to_face\", None) is None:\n            A = self.average_face_to_cell.T\n            row_sum = np.asarray(A.sum(axis=-1))[:, 0]\n            row_sum[row_sum == 0.0] = 1.0\n            self._average_cell_to_face = sp.diags(1.0 / row_sum) @ A\n        return self._average_cell_to_face\n\n    @property\n    def stencil_cell_gradient(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        # An operator that differences cells on each side of a face\n        # in the direction of the face normal\n        if getattr(self, \"_stencil_cell_gradient\", None) is None:\n            tests = self.cell_centers[:, None, :] - self.faces[self._simplex_faces]\n            Aij = np.sign(\n                np.einsum(\n                    \"ijk, ijk -> ij\", tests, self.face_normals[self._simplex_faces]\n                )\n            ).reshape(-1)\n            ind_ptr = 3 * np.arange(self.n_cells + 1)\n            col_inds = self._simplex_faces.reshape(-1)\n\n            self._stencil_cell_gradient = sp.csr_matrix(\n                (Aij, col_inds, ind_ptr), shape=(self.n_cells, self.n_faces)\n            ).T\n        return self._stencil_cell_gradient\n\n    @property\n    def boundary_face_list(self):\n        \"\"\"Boolean array of faces that lie on the boundary of the mesh.\n\n        Returns\n        -------\n        (n_faces) numpy.ndarray of bool\n        \"\"\"\n        if getattr(self, \"_boundary_face_list\", None) is None:\n            ind_dir = np.where(self.neighbors == -1)\n            self._is_boundary_face = self._simplex_faces[ind_dir]\n        return self._is_boundary_face\n\n    @property\n    def project_face_to_boundary_face(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return sp.eye(self.n_faces, format=\"csr\")[self.boundary_face_list]\n\n    @property\n    def project_edge_to_boundary_edge(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            return self.project_face_to_boundary_face\n        bound_edges = np.unique(self._face_edges[self.boundary_face_list])\n        return sp.eye(self.n_edges, format=\"csr\")[bound_edges]\n\n    @property\n    def project_node_to_boundary_node(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        bound_nodes = np.unique(self._faces[self.boundary_face_list])\n        return sp.eye(self.n_nodes, format=\"csr\")[bound_nodes]\n\n    @property\n    def boundary_nodes(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        bound_nodes = np.unique(self._faces[self.boundary_face_list])\n        return self.nodes[bound_nodes]\n\n    @property\n    def boundary_edges(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        if self.dim == 2:\n            return self.boundary_faces\n        bound_nodes = np.unique(self._face_edges[self.boundary_face_list])\n        return self.edges[bound_nodes]\n\n    @property\n    def boundary_faces(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        return self.faces[self.boundary_face_list]\n\n    @property\n    def boundary_face_outward_normals(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        bound_cells, which_face = np.where(self.neighbors == -1)\n        bound_faces = self._simplex_faces[(bound_cells, which_face)]\n        bound_face_normals = self.face_normals[bound_faces]\n\n        out_ish = self.faces[bound_faces] - self.cell_centers[bound_cells]\n        direc = np.sign(np.einsum(\"ij,ij->i\", bound_face_normals, out_ish))\n        boundary_face_outward_normals = direc[:, None] * bound_face_normals\n\n        return boundary_face_outward_normals\n\n    @property\n    def boundary_face_scalar_integral(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        P = self.project_face_to_boundary_face\n\n        w_h_dot_normal = np.sum(\n            (P @ self.face_normals) * self.boundary_face_outward_normals, axis=-1\n        )\n        A = sp.diags(self.face_areas) @ P.T @ sp.diags(w_h_dot_normal)\n        return A\n\n    @property\n    def boundary_node_vector_integral(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        Pn = self.project_node_to_boundary_node\n        Pf = self.project_face_to_boundary_face\n        n_boundary_nodes = Pn.shape[0]\n\n        dA = self.boundary_face_outward_normals * (Pf @ self.face_areas)[:, None]\n\n        Av = Pf @ self.average_node_to_face @ Pn.T\n\n        u_dot_ds = Av.T @ dA\n        diags = u_dot_ds.T\n        offsets = n_boundary_nodes * np.arange(self.dim)\n\n        return Pn.T @ sp.diags(\n            diags, offsets, shape=(n_boundary_nodes, self.dim * n_boundary_nodes)\n        )\n\n    @property\n    def boundary_edge_vector_integral(self):  # NOQA D102\n        # Documentation inherited from discretize.base.BaseMesh\n        boundary_faces = self.boundary_face_list\n        if self.dim == 2:\n            boundary_face_edges = boundary_faces\n            dA = (\n                self.boundary_face_outward_normals\n                * self.face_areas[boundary_faces][:, None]\n            )\n\n            # projection matrices\n            # for each edge on boundary faces\n            Pe = self.project_edge_to_boundary_edge\n            n_boundary_edges, n_edges = Pe.shape\n            index = boundary_face_edges\n            w_cross_n = cross2d(dA, self.edge_tangents[index])\n            M_be = (\n                sp.csr_matrix((w_cross_n, (index, index)), shape=(n_edges, n_edges))\n                @ Pe.T\n            )\n        else:\n            Ps = self._get_edge_surf_int_proj_mats(only_boundary=True, with_area=True)\n            # the cross product of a vector defined on each face with the face outward normal...\n            # so V cross n = -n cross V\n            cx = sp.diags(self.boundary_face_outward_normals[:, 0])\n            cy = sp.diags(self.boundary_face_outward_normals[:, 1])\n            cz = sp.diags(self.boundary_face_outward_normals[:, 2])\n            # the negative cross mat\n            cross_mat = sp.bmat([[None, cz, -cy], [-cz, None, cx], [cy, -cx, None]])\n\n            Pf = self.project_face_to_boundary_face\n            Pe = self.project_edge_to_boundary_edge\n            Av = (Pf @ self.average_edge_to_face) @ Pe.T\n            Av = cross_mat @ sp.block_diag((Av, Av, Av))\n\n            M_be = np.sum(Ps).T @ Av\n        return M_be\n\n    def __reduce__(self):\n        \"\"\"Return the class and attributes necessary to reconstruct the mesh.\"\"\"\n        return self.__class__, (\n            self.nodes,\n            self.simplices,\n        )\n"
  },
  {
    "path": "discretize/utils/__init__.py",
    "content": "\"\"\"\n========================================================\nUtility Classes and Functions (:mod:`discretize.utils`)\n========================================================\n.. currentmodule:: discretize.utils\n\nThe ``utils`` package contains utilities for helping with common operations involving\ndiscrete meshes\n\nUtility Classes\n===============\n.. autosummary::\n  :toctree: generated/\n\n  TensorType\n  Zero\n  Identity\n\nUtility Functions\n=================\n\nCode Utilities\n--------------\n.. autosummary::\n  :toctree: generated/\n\n  is_scalar\n  as_array_n_by_dim\n  requires\n\nCoordinate Transform Utilities\n------------------------------\n.. autosummary::\n  :toctree: generated/\n\n  rotate_points_from_normals\n  rotation_matrix_from_normals\n  cylindrical_to_cartesian\n  cartesian_to_cylindrical\n\nInterpolation Utilities\n-----------------------\n.. autosummary::\n  :toctree: generated/\n\n  interpolation_matrix\n  volume_average\n\nIO utilities\n------------\n.. autosummary::\n  :toctree: generated/\n\n  load_mesh\n  download\n\nMatrix Utilities\n----------------\n.. autosummary::\n  :toctree: generated/\n\n  mkvc\n  sdiag\n  sdinv\n  speye\n  kron3\n  spzeros\n  ddx\n  av\n  av_extrap\n  ndgrid\n  ind2sub\n  sub2ind\n  get_subarray\n  inverse_3x3_block_diagonal\n  inverse_2x2_block_diagonal\n  invert_blocks\n  make_property_tensor\n  inverse_property_tensor\n  cross2d\n\nMesh Utilities\n--------------\n.. autosummary::\n  :toctree: generated/\n\n  unpack_widths\n  closest_points_index\n  extract_core_mesh\n  random_model\n  refine_tree_xyz\n  active_from_xyz\n  mesh_builder_xyz\n\nUtilities for Curvilinear Meshes\n--------------------------------\n.. autosummary::\n  :toctree: generated/\n\n  example_curvilinear_grid\n  volume_tetrahedron\n  face_info\n  index_cube\n\"\"\"\n\nfrom discretize.utils.code_utils import is_scalar, as_array_n_by_dim, requires\nfrom discretize.utils.matrix_utils import (\n    mkvc,\n    sdiag,\n    sdinv,\n    speye,\n    kron3,\n    spzeros,\n    ddx,\n    av,\n    av_extrap,\n    ndgrid,\n    make_boundary_bool,\n    ind2sub,\n    sub2ind,\n    get_subarray,\n    inverse_3x3_block_diagonal,\n    inverse_2x2_block_diagonal,\n    invert_blocks,\n    TensorType,\n    make_property_tensor,\n    inverse_property_tensor,\n    Zero,\n    Identity,\n)\nfrom discretize.utils.mesh_utils import (\n    unpack_widths,\n    closest_points_index,\n    extract_core_mesh,\n    random_model,\n    refine_tree_xyz,\n    active_from_xyz,\n    mesh_builder_xyz,\n    example_simplex_mesh,\n)\nfrom discretize.utils.curvilinear_utils import (\n    example_curvilinear_grid,\n    volume_tetrahedron,\n    face_info,\n    index_cube,\n)\nfrom discretize.utils.interpolation_utils import interpolation_matrix, volume_average\nfrom discretize.utils.coordinate_utils import (\n    rotate_points_from_normals,\n    rotation_matrix_from_normals,\n    cyl2cart,\n    cart2cyl,\n    cylindrical_to_cartesian,\n    cartesian_to_cylindrical,\n    # rotate_vec_cyl2cart\n)\n\nfrom discretize.utils.io_utils import download, load_mesh\n\n# DEPRECATIONS\nfrom discretize.utils.code_utils import isScalar, asArray_N_x_Dim\nfrom discretize.utils.matrix_utils import (\n    sdInv,\n    getSubArray,\n    inv3X3BlockDiagonal,\n    inv2X2BlockDiagonal,\n    makePropertyTensor,\n    invPropertyTensor,\n    cross2d,\n)\nfrom discretize.utils.mesh_utils import (\n    meshTensor,\n    closestPoints,\n    ExtractCoreMesh,\n)\nfrom discretize.utils.curvilinear_utils import (\n    exampleLrmGrid,\n    volTetra,\n    indexCube,\n    faceInfo,\n)\nfrom discretize.utils.interpolation_utils import interpmat\nfrom discretize.utils.coordinate_utils import (\n    rotationMatrixFromNormals,\n    rotatePointsFromNormals,\n)\n"
  },
  {
    "path": "discretize/utils/code_utils.py",
    "content": "\"\"\"Utilities for common operations within code.\"\"\"\n\nimport numpy as np\nimport warnings\n\nSCALARTYPES = (complex, float, int, np.number)\n\n\ndef is_scalar(f):\n    \"\"\"Determine if the input argument is a scalar.\n\n    The function **is_scalar** returns *True* if the input is an integer,\n    float or complex number. The function returns *False* otherwise.\n\n    Parameters\n    ----------\n    f : object\n        Any input quantity\n\n    Returns\n    -------\n    bool\n        - *True* if the input argument is an integer, float or complex number\n        - *False* otherwise\n    \"\"\"\n    if isinstance(f, SCALARTYPES):\n        return True\n    elif (\n        isinstance(f, np.ndarray)\n        and f.size == 1\n        and isinstance(f.reshape(-1)[0], SCALARTYPES)\n    ):\n        return True\n    return False\n\n\ndef as_array_n_by_dim(pts, dim):\n    \"\"\"Coerce the given array to have *dim* columns.\n\n    The function **as_array_n_by_dim** will examine the *pts* array,\n    and coerce it to be at least  if the number of columns is equal to *dim*.\n\n    This is similar to the :func:`numpy.atleast_2d`, except that it ensures that then\n    input has *dim* columns, and it appends a :data:`numpy.newaxis` to 1D arrays\n    instead of prepending.\n\n    Parameters\n    ----------\n    pts : array_like\n        array to check.\n    dim : int\n        The number of columns which *pts* should have\n\n    Returns\n    -------\n    (n_pts, dim) numpy.ndarray\n        verified array\n    \"\"\"\n    pts = np.asarray(pts)\n\n    if dim > 1:\n        pts = np.atleast_2d(pts)\n    elif len(pts.shape) == 1:\n        pts = pts[:, np.newaxis]\n\n    if pts.shape[1] != dim:\n        raise ValueError(\n            \"pts must be a column vector of shape (nPts, {0:d}) not ({1:d}, {2:d})\".format(\n                *((dim,) + pts.shape)\n            )\n        )\n\n    return pts\n\n\ndef requires(modules):\n    \"\"\"Decorate a function with soft dependencies.\n\n    This function was inspired by the `requires` function of pysal,\n    which is released under the 'BSD 3-Clause \"New\" or \"Revised\" License'.\n\n    https://github.com/pysal/pysal/blob/master/pysal/lib/common.py\n\n    Parameters\n    ----------\n    modules : dict\n        Dictionary containing soft dependencies, e.g.,\n        {'matplotlib': matplotlib}.\n\n    Returns\n    -------\n    decorated_function : function\n        Original function if all soft dependencies are met, otherwise\n        it returns an empty function which prints why it is not running.\n\n    \"\"\"\n    # Check the required modules, add missing ones in the list `missing`.\n    missing = []\n    for key, item in modules.items():\n        if item is False:\n            missing.append(key)\n\n    def decorated_function(function):\n        \"\"\"Wrap function.\"\"\"\n        if not missing:\n            return function\n        else:\n\n            def passer(*args, **kwargs):\n                print((\"Missing dependencies: {d}.\".format(d=missing)))\n                print((\"Not running `{}`.\".format(function.__name__)))\n\n            return passer\n\n    return decorated_function\n\n\ndef deprecate_class(\n    removal_version=None, new_location=None, future_warn=False, error=False\n):\n    \"\"\"Decorate a class as deprecated.\n\n    Parameters\n    ----------\n    removal_version : str, optional\n        Which version the class will be removed in.\n    new_location : str, optional\n        A new package location for the class.\n    future_warn : bool, optional\n        Whether to issue a FutureWarning, or a DeprecationWarning.\n    error : bool, optional\n        Throw error if deprecated class no longer implemented\n    \"\"\"\n    if future_warn:\n        warn = FutureWarning\n    else:\n        warn = DeprecationWarning\n\n    def decorator(cls):\n        my_name = cls.__name__\n        parent_name = cls.__bases__[0].__name__\n        message = f\"{my_name} has been deprecated, please use {parent_name}.\"\n        if error:\n            message = f\"{my_name} has been removed, please use {parent_name}.\"\n        elif removal_version is not None:\n            message += (\n                f\" It will be removed in version {removal_version} of discretize.\"\n            )\n        else:\n            message += \" It will be removed in a future version of discretize.\"\n\n        # stash the original initialization of the class\n        cls._old__init__ = cls.__init__\n\n        def __init__(self, *args, **kwargs):\n            if error:\n                raise NotImplementedError(message)\n            else:\n                warnings.warn(message, warn, stacklevel=2)\n            self._old__init__(*args, **kwargs)\n\n        cls.__init__ = __init__\n        if new_location is not None:\n            parent_name = f\"{new_location}.{parent_name}\"\n        cls.__doc__ = f\"\"\" This class has been deprecated, see `{parent_name}` for documentation\"\"\"\n        return cls\n\n    return decorator\n\n\ndef deprecate_module(\n    old_name, new_name, removal_version=None, future_warn=False, error=False\n):\n    \"\"\"Deprecate a module.\n\n    Parameters\n    ----------\n    old_name : str\n        The old name of the module.\n    new_name : str\n        The new name of the module.\n    removal_version : str, optional\n        Which version the class will be removed in.\n    future_warn : bool, optional\n        Whether to issue a FutureWarning, or a DeprecationWarning.\n    error : bool, default: ``False``\n        Throw error if deprecated module no longer implemented\n    \"\"\"\n    if future_warn:\n        warn = FutureWarning\n    else:\n        warn = DeprecationWarning\n    message = f\"The {old_name} module has been deprecated, please use {new_name}.\"\n    if error:\n        message = f\"{old_name} has been removed, please use {new_name}.\"\n    elif removal_version is not None:\n        message += f\" It will be removed in version {removal_version} of discretize\"\n    else:\n        message += \" It will be removed in a future version of discretize.\"\n    message += \" Please update your code accordingly.\"\n    if error:\n        raise NotImplementedError(message)\n    else:\n        warnings.warn(message, warn, stacklevel=2)\n\n\ndef deprecate_property(\n    new_name, old_name, removal_version=None, future_warn=False, error=False\n):\n    \"\"\"Deprecate a class property.\n\n    Parameters\n    ----------\n    new_name : str\n        The new name of the property.\n    old_name : str\n        The old name of the property.\n    removal_version : str, optional\n        Which version the class will be removed in.\n    future_warn : bool, optional\n        Whether to issue a FutureWarning, or a DeprecationWarning.\n    error : bool, default: ``False``\n        Throw error if deprecated property no longer implemented\n\n    \"\"\"\n    if future_warn:\n        warn = FutureWarning\n    else:\n        warn = DeprecationWarning\n    if error:\n        tag = \"\"\n    elif removal_version is not None:\n        tag = f\" It will be removed in version {removal_version} of discretize.\"\n    else:\n        tag = \" It will be removed in a future version of discretize.\"\n\n    def get_dep(self):\n        class_name = type(self).__name__\n        message = (\n            f\"{class_name}.{old_name} has been deprecated, please use {class_name}.{new_name}.\"\n            + tag\n        )\n        if error:\n            raise NotImplementedError(message.replace(\"deprecated\", \"removed\"))\n        else:\n            warnings.warn(message, warn, stacklevel=2)\n        return getattr(self, new_name)\n\n    def set_dep(self, other):\n        class_name = type(self).__name__\n        message = (\n            f\"{class_name}.{old_name} has been deprecated, please use {class_name}.{new_name}.\"\n            + tag\n        )\n        if error:\n            raise NotImplementedError(message.replace(\"deprecated\", \"removed\"))\n        else:\n            warnings.warn(message, warn, stacklevel=2)\n        setattr(self, new_name, other)\n\n    doc = f\"\"\"\n    `{old_name}` has been deprecated. See `{new_name}` for documentation.\n\n    See Also\n    --------\n    {new_name}\n    \"\"\"\n\n    return property(get_dep, set_dep, None, doc)\n\n\ndef deprecate_method(\n    new_name, old_name, removal_version=None, future_warn=False, error=False\n):\n    \"\"\"Deprecate a class method.\n\n    Parameters\n    ----------\n    new_name : str\n        The new name of the method.\n    old_name : str\n        The old name of the method.\n    removal_version : str, optional\n        Which version the class will be removed in.\n    future_warn : bool, optional\n        Whether to issue a FutureWarning, or a DeprecationWarning.\n    error : bool, default: ``False``\n        Throw error if deprecated method no longer implemented\n    \"\"\"\n    if future_warn:\n        warn = FutureWarning\n    else:\n        warn = DeprecationWarning\n    if error:\n        tag = \"\"\n    elif removal_version is not None:\n        tag = f\" It will be removed in version {removal_version} of discretize.\"\n    else:\n        tag = \" It will be removed in a future version of discretize.\"\n\n    def new_method(self, *args, **kwargs):\n        class_name = type(self).__name__\n        message = (\n            f\"{class_name}.{old_name} has been deprecated, please use \"\n            f\"{class_name}.{new_name}.{tag}\"\n        )\n        if error:\n            raise NotImplementedError(message.replace(\"deprecated\", \"removed\"))\n        else:\n            warnings.warn(\n                message,\n                warn,\n                stacklevel=2,\n            )\n        return getattr(self, new_name)(*args, **kwargs)\n\n    doc = f\"\"\"\n    `{old_name}` has been deprecated. See `{new_name}` for documentation\n\n    See Also\n    --------\n    {new_name}\n    \"\"\"\n    if error:\n        doc = doc.replace(\"deprecated\", \"removed\")\n    new_method.__doc__ = doc\n    return new_method\n\n\ndef deprecate_function(\n    new_function, old_name, removal_version=None, future_warn=False, error=False\n):\n    \"\"\"Deprecate a function.\n\n    Parameters\n    ----------\n    new_function : callable\n        The new function.\n    old_name : str\n        The old name of the function.\n    removal_version : str, optional\n        Which version the class will be removed in.\n    future_warn : bool, optional\n        Whether to issue a FutureWarning, or a DeprecationWarning.\n    error : bool, default: ``False``\n        Throw error if deprecated function no longer implemented\n    \"\"\"\n    if future_warn:\n        warn = FutureWarning\n    else:\n        warn = DeprecationWarning\n    new_name = new_function.__name__\n    if error:\n        tag = \"\"\n    elif removal_version is not None:\n        tag = f\" It will be removed in version {removal_version} of discretize.\"\n    else:\n        tag = \" It will be removed in a future version of discretize.\"\n\n    message = f\"{old_name} has been deprecated, please use {new_name}.\" + tag\n\n    def dep_function(*args, **kwargs):\n        if error:\n            raise NotImplementedError(message.replace(\"deprecated\", \"removed\"))\n        else:\n            warnings.warn(\n                message,\n                warn,\n                stacklevel=2,\n            )\n            return new_function(*args, **kwargs)\n\n    doc = f\"\"\"\n    `{old_name}` has been deprecated. See `{new_name}` for documentation\n\n    See Also\n    --------\n    {new_name}\n    \"\"\"\n    if error:\n        doc = doc.replace(\"deprecated\", \"removed\")\n    dep_function.__doc__ = doc\n    return dep_function\n\n\n# DEPRECATIONS\nisScalar = deprecate_function(\n    is_scalar, \"isScalar\", removal_version=\"1.0.0\", error=True\n)\nasArray_N_x_Dim = deprecate_function(\n    as_array_n_by_dim, \"asArray_N_x_Dim\", removal_version=\"1.0.0\", error=True\n)\n"
  },
  {
    "path": "discretize/utils/codeutils.py",
    "content": "from discretize.utils.code_utils import *  # NOQA F401,F403\n\nraise ImportError(\n    \"Importing from discretize.codeutils is deprecated behavoir. Please import \"\n    \"from discretize.utils. This message will be removed in version 1.0.0 of discretize.\",\n)\n"
  },
  {
    "path": "discretize/utils/coordinate_utils.py",
    "content": "\"\"\"Simple utilities for coordinate transformations.\"\"\"\n\nimport numpy as np\nfrom discretize.utils.matrix_utils import mkvc\nfrom discretize.utils.code_utils import as_array_n_by_dim, deprecate_function\n\n\ndef cylindrical_to_cartesian(grid, vec=None):\n    r\"\"\"Transform from cylindrical to cartesian coordinates.\n\n    Transform a grid or a vector from cylindrical coordinates :math:`(r, \\theta, z)` to\n    Cartesian coordinates :math:`(x, y, z)`. :math:`\\theta` is given in radians.\n\n    Parameters\n    ----------\n    grid : (n, 3) array_like\n        Location points defined in cylindrical coordinates :math:`(r, \\theta, z)`.\n    vec : (n, 3) array_like, optional\n        Vector defined in cylindrical coordinates :math:`(r, \\theta, z)` at the\n        locations grid. Will also except a flattend array in column major order with the\n        same number of elements.\n\n    Returns\n    -------\n    (n, 3) numpy.ndarray\n        If `vec` is ``None``, this returns the transformed `grid` array, otherwise\n        this is the transformed `vec` array.\n\n    Examples\n    --------\n    Here, we convert a series of vectors in 3D space from cylindrical coordinates\n    to Cartesian coordinates.\n\n    >>> from discretize.utils import cylindrical_to_cartesian\n    >>> import numpy as np\n\n    Construct original set of vectors in cylindrical coordinates\n\n    >>> r = np.ones(9)\n    >>> phi = np.linspace(0, 2*np.pi, 9)\n    >>> z = np.linspace(-4., 4., 9)\n    >>> u = np.c_[r, phi, z]\n    >>> u\n    array([[ 1.        ,  0.        , -4.        ],\n           [ 1.        ,  0.78539816, -3.        ],\n           [ 1.        ,  1.57079633, -2.        ],\n           [ 1.        ,  2.35619449, -1.        ],\n           [ 1.        ,  3.14159265,  0.        ],\n           [ 1.        ,  3.92699082,  1.        ],\n           [ 1.        ,  4.71238898,  2.        ],\n           [ 1.        ,  5.49778714,  3.        ],\n           [ 1.        ,  6.28318531,  4.        ]])\n\n    Create equivalent set of vectors in Cartesian coordinates\n\n    >>> v = cylindrical_to_cartesian(u)\n    >>> v\n    array([[ 1.00000000e+00,  0.00000000e+00, -4.00000000e+00],\n           [ 7.07106781e-01,  7.07106781e-01, -3.00000000e+00],\n           [ 6.12323400e-17,  1.00000000e+00, -2.00000000e+00],\n           [-7.07106781e-01,  7.07106781e-01, -1.00000000e+00],\n           [-1.00000000e+00,  1.22464680e-16,  0.00000000e+00],\n           [-7.07106781e-01, -7.07106781e-01,  1.00000000e+00],\n           [-1.83697020e-16, -1.00000000e+00,  2.00000000e+00],\n           [ 7.07106781e-01, -7.07106781e-01,  3.00000000e+00],\n           [ 1.00000000e+00, -2.44929360e-16,  4.00000000e+00]])\n    \"\"\"\n    grid = np.atleast_2d(grid)\n\n    if vec is None:\n        return np.hstack(\n            [\n                mkvc(grid[:, 0] * np.cos(grid[:, 1]), 2),\n                mkvc(grid[:, 0] * np.sin(grid[:, 1]), 2),\n                mkvc(grid[:, 2], 2),\n            ]\n        )\n    vec = np.asanyarray(vec)\n    if len(vec.shape) == 1 or vec.shape[1] == 1:\n        vec = vec.reshape(grid.shape, order=\"F\")\n\n    x = vec[:, 0] * np.cos(grid[:, 1]) - vec[:, 1] * np.sin(grid[:, 1])\n    y = vec[:, 0] * np.sin(grid[:, 1]) + vec[:, 1] * np.cos(grid[:, 1])\n\n    newvec = [x, y]\n    if grid.shape[1] == 3:\n        z = vec[:, 2]\n        newvec += [z]\n\n    return np.vstack(newvec).T\n\n\ndef cyl2cart(grid, vec=None):\n    \"\"\"Transform from cylindrical to cartesian coordinates.\n\n    An alias for `cylindrical_to_cartesian``.\n\n    See Also\n    --------\n    cylindrical_to_cartesian\n    \"\"\"\n    return cylindrical_to_cartesian(grid, vec)\n\n\ndef cartesian_to_cylindrical(grid, vec=None):\n    r\"\"\"Transform from cartesian to cylindrical coordinates.\n\n    Transform a grid or a vector from Cartesian coordinates :math:`(x, y, z)` to\n    cylindrical coordinates :math:`(r, \\theta, z)`.\n\n    Parameters\n    ----------\n    grid : (n, 3) array_like\n        Location points defined in Cartesian coordinates :math:`(x, y z)`.\n    vec : (n, 3) array_like, optional\n        Vector defined in Cartesian coordinates. This also accepts a flattened array\n        with the same total elements in column major order.\n\n    Returns\n    -------\n    (n, 3) numpy.ndarray\n        If `vec` is ``None``, this returns the transformed `grid` array, otherwise\n        this is the transformed `vec` array.\n\n    Examples\n    --------\n    Here, we convert a series of vectors in 3D space from Cartesian coordinates\n    to cylindrical coordinates.\n\n    >>> from discretize.utils import cartesian_to_cylindrical\n    >>> import numpy as np\n\n    Create set of vectors in Cartesian coordinates\n\n    >>> r = np.ones(9)\n    >>> phi = np.linspace(0, 2*np.pi, 9)\n    >>> z = np.linspace(-4., 4., 9)\n    >>> x = r*np.cos(phi)\n    >>> y = r*np.sin(phi)\n    >>> u = np.c_[x, y, z]\n    >>> u\n    array([[ 1.00000000e+00,  0.00000000e+00, -4.00000000e+00],\n           [ 7.07106781e-01,  7.07106781e-01, -3.00000000e+00],\n           [ 6.12323400e-17,  1.00000000e+00, -2.00000000e+00],\n           [-7.07106781e-01,  7.07106781e-01, -1.00000000e+00],\n           [-1.00000000e+00,  1.22464680e-16,  0.00000000e+00],\n           [-7.07106781e-01, -7.07106781e-01,  1.00000000e+00],\n           [-1.83697020e-16, -1.00000000e+00,  2.00000000e+00],\n           [ 7.07106781e-01, -7.07106781e-01,  3.00000000e+00],\n           [ 1.00000000e+00, -2.44929360e-16,  4.00000000e+00]])\n\n    Compute equivalent set of vectors in cylindrical coordinates\n\n    >>> v = cartesian_to_cylindrical(u)\n    >>> v\n    array([[ 1.00000000e+00,  0.00000000e+00, -4.00000000e+00],\n           [ 1.00000000e+00,  7.85398163e-01, -3.00000000e+00],\n           [ 1.00000000e+00,  1.57079633e+00, -2.00000000e+00],\n           [ 1.00000000e+00,  2.35619449e+00, -1.00000000e+00],\n           [ 1.00000000e+00,  3.14159265e+00,  0.00000000e+00],\n           [ 1.00000000e+00, -2.35619449e+00,  1.00000000e+00],\n           [ 1.00000000e+00, -1.57079633e+00,  2.00000000e+00],\n           [ 1.00000000e+00, -7.85398163e-01,  3.00000000e+00],\n           [ 1.00000000e+00, -2.44929360e-16,  4.00000000e+00]])\n    \"\"\"\n    grid = as_array_n_by_dim(grid, 3)\n    theta = np.arctan2(grid[:, 1], grid[:, 0])\n    if vec is None:\n        return np.c_[np.linalg.norm(grid[:, :2], axis=-1), theta, grid[:, 2]]\n    vec = as_array_n_by_dim(vec, 3)\n\n    return np.hstack(\n        [\n            mkvc(np.cos(theta) * vec[:, 0] + np.sin(theta) * vec[:, 1], 2),\n            mkvc(-np.sin(theta) * vec[:, 0] + np.cos(theta) * vec[:, 1], 2),\n            mkvc(vec[:, 2], 2),\n        ]\n    )\n\n\ndef cart2cyl(grid, vec=None):\n    \"\"\"Transform from cartesian to cylindrical coordinates.\n\n    An alias for cartesian_to_cylindrical\n\n    See Also\n    --------\n    cartesian_to_cylindrical\n    \"\"\"\n    return cartesian_to_cylindrical(grid, vec)\n\n\ndef rotation_matrix_from_normals(v0, v1, tol=1e-20):\n    r\"\"\"Generate a 3x3 rotation matrix defining the rotation from vector v0 to v1.\n\n    This function uses Rodrigues' rotation formula to generate the rotation\n    matrix :math:`\\mathbf{A}` going from vector :math:`\\mathbf{v_0}` to\n    vector :math:`\\mathbf{v_1}`. Thus:\n\n    .. math::\n        \\mathbf{Av_0} = \\mathbf{v_1}\n\n    For detailed desciption of the algorithm, see\n    https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula\n\n    Parameters\n    ----------\n    v0 : (3) numpy.ndarray\n        Starting orientation direction\n    v1 : (3) numpy.ndarray\n        Finishing orientation direction\n    tol : float, optional\n        Numerical tolerance. If the length of the rotation axis is below this value,\n        it is assumed to be no rotation, and an identity matrix is returned.\n\n    Returns\n    -------\n    (3, 3) numpy.ndarray\n        The rotation matrix from v0 to v1.\n    \"\"\"\n    # ensure both v0, v1 are vectors of length 1\n    if len(v0) != 3:\n        raise ValueError(\"Length of n0 should be 3\")\n    if len(v1) != 3:\n        raise ValueError(\"Length of n1 should be 3\")\n\n    # ensure both are true normals\n    n0 = v0 * 1.0 / np.linalg.norm(v0)\n    n1 = v1 * 1.0 / np.linalg.norm(v1)\n\n    n0dotn1 = n0.dot(n1)\n\n    # define the rotation axis, which is the cross product of the two vectors\n    rotAx = np.cross(n0, n1)\n\n    if np.linalg.norm(rotAx) < tol:\n        return np.eye(3, dtype=float)\n\n    rotAx *= 1.0 / np.linalg.norm(rotAx)\n\n    cosT = n0dotn1 / (np.linalg.norm(n0) * np.linalg.norm(n1))\n    sinT = np.sqrt(1.0 - n0dotn1**2)\n\n    ux = np.array(\n        [\n            [0.0, -rotAx[2], rotAx[1]],\n            [rotAx[2], 0.0, -rotAx[0]],\n            [-rotAx[1], rotAx[0], 0.0],\n        ],\n        dtype=float,\n    )\n\n    return np.eye(3, dtype=float) + sinT * ux + (1.0 - cosT) * (ux.dot(ux))\n\n\ndef rotate_points_from_normals(xyz, v0, v1, x0=np.r_[0.0, 0.0, 0.0]):\n    r\"\"\"Rotate a set of xyz locations about a specified point.\n\n    Rotate a grid of Cartesian points about a location x0 according to the\n    rotation defined from vector v0 to v1.\n\n    Let :math:`\\mathbf{x}` represent an input xyz location, let :math:`\\mathbf{x_0}` be\n    the origin of rotation, and let :math:`\\mathbf{R}` denote the rotation matrix from\n    vector v0 to v1. Where :math:`\\mathbf{x'}` is the new xyz location, this function\n    outputs the following operation for all input locations:\n\n    .. math::\n        \\mathbf{x'} = \\mathbf{R (x - x_0)} + \\mathbf{x_0}\n\n    Parameters\n    ----------\n    xyz : (n, 3) numpy.ndarray\n        locations to rotate\n    v0 : (3) numpy.ndarray\n        Starting orientation direction\n    v1 : (3) numpy.ndarray\n        Finishing orientation direction\n    x0 : (3) numpy.ndarray, optional\n        The origin of rotation.\n\n    Returns\n    -------\n    (n, 3) numpy.ndarray\n        The rotated xyz locations.\n    \"\"\"\n    # Compute rotation matrix between v0 and v1\n    R = rotation_matrix_from_normals(v0, v1)\n\n    if xyz.shape[1] != 3:\n        raise ValueError(\"Grid of xyz points should be n x 3\")\n    if len(x0) != 3:\n        raise ValueError(\"x0 should have length 3\")\n\n    # Define origin\n    X0 = np.ones([xyz.shape[0], 1]) * mkvc(x0)\n\n    return (xyz - X0).dot(R.T) + X0  # equivalent to (R*(xyz - X0)).T + X0\n\n\nrotationMatrixFromNormals = deprecate_function(\n    rotation_matrix_from_normals,\n    \"rotationMatrixFromNormals\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\nrotatePointsFromNormals = deprecate_function(\n    rotate_points_from_normals,\n    \"rotatePointsFromNormals\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\n"
  },
  {
    "path": "discretize/utils/coordutils.py",
    "content": "from discretize.utils.coordinate_utils import *  # NOQA F401,F403\n\nraise ImportError(\n    \"Importing from discretize.coordutils is deprecated behavoir. Please import \"\n    \"from discretize.utils. This message will be removed in version 1.0.0 of discretize.\",\n)\n"
  },
  {
    "path": "discretize/utils/curvilinear_utils.py",
    "content": "\"\"\"Functions for working with curvilinear meshes.\"\"\"\n\nimport numpy as np\nfrom discretize.utils.matrix_utils import mkvc, ndgrid, sub2ind\nfrom discretize.utils.code_utils import deprecate_function\n\n\ndef example_curvilinear_grid(nC, exType):\n    \"\"\"Create the gridded node locations for a curvilinear mesh.\n\n    Parameters\n    ----------\n    nC : list of int\n        list of number of cells in each dimension. Must be length 2 or 3\n    exType : {\"rect\", \"rotate\", \"sphere\"}\n        String specifying the style of example curvilinear mesh.\n\n    Returns\n    -------\n    list of numpy.ndarray\n        List containing the gridded x, y (and z) node locations for the\n        curvilinear mesh.\n    \"\"\"\n    if not isinstance(nC, list):\n        raise TypeError(\"nC must be a list containing the number of nodes\")\n    if len(nC) != 2 and len(nC) != 3:\n        raise ValueError(\"nC must either two or three dimensions\")\n    exType = exType.lower()\n\n    possibleTypes = [\"rect\", \"rotate\", \"sphere\"]\n    if exType not in possibleTypes:\n        raise TypeError(\"Not a possible example type.\")\n\n    if exType == \"rect\":\n        return list(\n            ndgrid([np.cumsum(np.r_[0, np.ones(nx) / nx]) for nx in nC], vector=False)\n        )\n    elif exType == \"sphere\":\n        nodes = list(\n            ndgrid(\n                [np.cumsum(np.r_[0, np.ones(nx) / nx]) - 0.5 for nx in nC], vector=False\n            )\n        )\n        nodes = np.stack(nodes, axis=-1)\n        nodes = 2 * nodes\n        # L_inf distance to center\n        r0 = np.linalg.norm(nodes, ord=np.inf, axis=-1)\n        # L2 distance to center\n        r2 = np.linalg.norm(nodes, axis=-1)\n        r0[r0 == 0.0] = 1.0\n        r2[r2 == 0.0] = 1.0\n        scale = r0 / r2\n        nodes = nodes * scale[..., None]\n        nodes = np.transpose(nodes, (-1, *np.arange(len(nC))))\n        nodes = [node for node in nodes]  # turn it into a list\n        return nodes\n    elif exType == \"rotate\":\n        if len(nC) == 2:\n            X, Y = ndgrid(\n                [np.cumsum(np.r_[0, np.ones(nx) / nx]) for nx in nC], vector=False\n            )\n            amt = 0.5 - np.sqrt((X - 0.5) ** 2 + (Y - 0.5) ** 2)\n            amt[amt < 0] = 0\n            return [X + (-(Y - 0.5)) * amt, Y + (+(X - 0.5)) * amt]\n        elif len(nC) == 3:\n            X, Y, Z = ndgrid(\n                [np.cumsum(np.r_[0, np.ones(nx) / nx]) for nx in nC], vector=False\n            )\n            amt = 0.5 - np.sqrt((X - 0.5) ** 2 + (Y - 0.5) ** 2 + (Z - 0.5) ** 2)\n            amt[amt < 0] = 0\n            return [\n                X + (-(Y - 0.5)) * amt,\n                Y + (-(Z - 0.5)) * amt,\n                Z + (-(X - 0.5)) * amt,\n            ]\n\n\ndef volume_tetrahedron(xyz, A, B, C, D):\n    r\"\"\"Return the tetrahedron volumes for a specified set of verticies.\n\n    Let *xyz* be an (n, 3) array denoting a set of vertex locations.\n    Any 4 vertex locations *a, b, c* and *d* can be used to define a tetrahedron.\n    For the set of tetrahedra whose verticies are indexed in vectors\n    *A, B, C* and *D*, this function returns the corresponding volumes.\n    See algorithm: https://en.wikipedia.org/wiki/Tetrahedron#Volume\n\n    .. math::\n       vol = {1 \\over 6} \\big | ( \\mathbf{a - d} ) \\cdot\n       ( ( \\mathbf{b - d} ) \\times ( \\mathbf{c - d} ) ) \\big |\n\n    Parameters\n    ----------\n    xyz : (n_pts, 3) numpy.ndarray\n        x,y, and z locations for all verticies\n    A : (n_tetra) numpy.ndarray of int\n        Vector containing the indicies for the **a** vertex locations\n    B : (n_tetra) numpy.ndarray of int\n        Vector containing the indicies for the **b** vertex locations\n    C : (n_tetra) numpy.ndarray of int\n        Vector containing the indicies for the **c** vertex locations\n    D : (n_tetra) numpy.ndarray of int\n        Vector containing the indicies for the **d** vertex locations\n\n    Returns\n    -------\n    (n_tetra) numpy.ndarray\n        Volumes of the tetrahedra whose vertices are indexed by\n        *A, B, C* and *D*.\n\n    Examples\n    --------\n    Here we define a small 3D tensor mesh. 4 nodes are chosen to\n    be the verticies of a tetrahedron. We compute the volume of this\n    tetrahedron. Note that xyz locations for the verticies can be\n    scattered and do not require regular spacing.\n\n    >>> from discretize.utils import volume_tetrahedron\n    >>> from discretize import TensorMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> import matplotlib as mpl\n\n    Define corners of a uniform cube\n\n    >>> h = [1, 1]\n    >>> mesh = TensorMesh([h, h, h])\n    >>> xyz = mesh.nodes\n\n    Specify the indicies of the corner points\n\n    >>> A = np.array([0])\n    >>> B = np.array([6])\n    >>> C = np.array([8])\n    >>> D = np.array([24])\n\n    Compute volume for all tetrahedra and the extract first one\n\n    >>> vol = volume_tetrahedron(xyz, A, B, C, D)\n    >>> vol = vol[0]\n    >>> vol\n    array([1.33333333])\n\n    Plotting small mesh and tetrahedron\n\n    >>> fig = plt.figure(figsize=(7, 7))\n    >>> ax = plt.subplot(projection='3d')\n    >>> mesh.plot_grid(ax=ax)\n    >>> k = [0, 6, 8, 0, 24, 6, 24, 8]\n    >>> xyz_tetra = xyz[k, :]\n    >>> ax.plot(xyz_tetra[:, 0], xyz_tetra[:, 1], xyz_tetra[:, 2], 'r')\n    >>> ax.text(-0.25, 0., 3., 'Volume of the tetrahedron: {:g} $m^3$'.format(vol))\n    >>> plt.show()\n    \"\"\"\n    AD = xyz[A, :] - xyz[D, :]\n    BD = xyz[B, :] - xyz[D, :]\n    CD = xyz[C, :] - xyz[D, :]\n\n    V = (\n        (BD[:, 0] * CD[:, 1] - BD[:, 1] * CD[:, 0]) * AD[:, 2]\n        - (BD[:, 0] * CD[:, 2] - BD[:, 2] * CD[:, 0]) * AD[:, 1]\n        + (BD[:, 1] * CD[:, 2] - BD[:, 2] * CD[:, 1]) * AD[:, 0]\n    )\n    return np.abs(V / 6)\n\n\ndef index_cube(nodes, grid_shape, n=None):\n    \"\"\"Return the index of nodes on a tensor (or curvilinear) mesh.\n\n    For 2D tensor meshes, each cell is defined by nodes\n    *A, B, C* and *D*. And for 3D tensor meshes, each cell\n    is defined by nodes *A* through *H* (see below). *index_cube*\n    outputs the indices for the specified node(s) for all\n    cells in the mesh.\n\n    TWO DIMENSIONS::\n\n      node(i,j+1)      node(i+i,j+1)\n           B -------------- C\n           |                |\n           |    cell(i,j)   |\n           |        I       |\n           |                |\n           A -------------- D\n       node(i,j)        node(i+1,j)\n\n    THREE DIMENSIONS::\n\n        node(i,j+1,k+1)    node(i+1,j+1,k+1)\n                F ---------------- G\n               /|                / |\n              / |               /  |\n             /  |              /   |\n     node(i,j,k+1)     node(i+1,j,k+1)\n           E --------------- H     |\n           |    B -----------|---- C\n           |   / cell(i,j,k) |   /\n           |  /        I     |  /\n           | /               | /\n           A --------------- D\n      node(i,j,k)     node(i+1,j,k)\n\n    Parameters\n    ----------\n    nodes : str\n        String specifying which nodes to return. For 2D meshes,\n        *nodes* must be a string containing combinations of the characters 'A', 'B',\n        'C', or 'D'. For 3D meshes, *nodes* can also be 'E', 'F', 'G', or 'H'. Note that\n        order is preserved. E.g. if we want to return the C, D and A node indices in\n        that particular order, we input *nodes* = 'CDA'.\n    grid_shape : list of int\n        Number of nodes along the i,j,k directions; e.g. [ni,nj,nk]\n    nc : list of int\n        Number of cells along the i,j,k directions; e.g. [nci,ncj,nck]\n\n    Returns\n    -------\n    index : tuple of numpy.ndarray\n        Each entry of the tuple is a 1D :class:`numpy.ndarray` containing the indices of\n        the nodes specified in the input *nodes* in the order asked;\n        e.g. if *nodes* = 'DCBA', the tuple returned is ordered (D,C,B,A).\n\n    Examples\n    --------\n    Here, we construct a small 2D tensor mesh\n    (works for a curvilinear mesh as well) and use *index_cube*\n    to find the indices of the 'A' and 'C' nodes. We then\n    plot the mesh, as well as the 'A' and 'C' node locations.\n\n    >>> from discretize import TensorMesh\n    >>> from discretize.utils import index_cube\n    >>> from matplotlib import pyplot as plt\n    >>> import numpy as np\n\n    Create a simple tensor mesh.\n\n    >>> n_cells = 5\n    >>> h = 2*np.ones(n_cells)\n    >>> mesh = TensorMesh([h, h], x0='00')\n\n    Get indices of 'A' and 'C' nodes for all cells.\n\n    >>> A, C = index_cube('AC', [n_cells+1, n_cells+1])\n\n    Plot mesh and the locations of the A and C nodes\n\n    >>> fig1 = plt.figure(figsize=(5, 5))\n    >>> ax1 = fig1.add_axes([0.1, 0.1, 0.8, 0.8])\n    >>> mesh.plot_grid(ax=ax1)\n    >>> ax1.scatter(mesh.nodes[A, 0], mesh.nodes[A, 1], 100, 'r', marker='^')\n    >>> ax1.scatter(mesh.nodes[C, 0], mesh.nodes[C, 1], 100, 'g', marker='v')\n    >>> ax1.set_title('A nodes (red) and C nodes (green)')\n    >>> plt.show()\n    \"\"\"\n    if not isinstance(nodes, str):\n        raise TypeError(\"Nodes must be a str variable: e.g. 'ABCD'\")\n    nodes = nodes.upper()\n    try:\n        dim = len(grid_shape)\n        if n is None:\n            n = tuple(x - 1 for x in grid_shape)\n    except TypeError:\n        return TypeError(\"grid_shape must be iterable\")\n    # Make sure that we choose from the possible nodes.\n    possibleNodes = \"ABCD\" if dim == 2 else \"ABCDEFGH\"\n    for node in nodes:\n        if node not in possibleNodes:\n            raise ValueError(\"Nodes must be chosen from: '{0!s}'\".format(possibleNodes))\n\n    if dim == 2:\n        ij = ndgrid(np.arange(n[0]), np.arange(n[1]))\n        i, j = ij[:, 0], ij[:, 1]\n    elif dim == 3:\n        ijk = ndgrid(np.arange(n[0]), np.arange(n[1]), np.arange(n[2]))\n        i, j, k = ijk[:, 0], ijk[:, 1], ijk[:, 2]\n    else:\n        raise Exception(\"Only 2 and 3 dimensions supported.\")\n\n    nodeMap = {\n        \"A\": [0, 0, 0],\n        \"B\": [0, 1, 0],\n        \"C\": [1, 1, 0],\n        \"D\": [1, 0, 0],\n        \"E\": [0, 0, 1],\n        \"F\": [0, 1, 1],\n        \"G\": [1, 1, 1],\n        \"H\": [1, 0, 1],\n    }\n    out = ()\n    for node in nodes:\n        shift = nodeMap[node]\n        if dim == 2:\n            out += (sub2ind(grid_shape, np.c_[i + shift[0], j + shift[1]]).flatten(),)\n        elif dim == 3:\n            out += (\n                sub2ind(\n                    grid_shape, np.c_[i + shift[0], j + shift[1], k + shift[2]]\n                ).flatten(),\n            )\n\n    return out\n\n\ndef face_info(xyz, A, B, C, D, average=True, normalize_normals=True, **kwargs):\n    r\"\"\"Return normal surface vectors and areas for a given set of faces.\n\n    Let *xyz* be an (n, 3) array denoting a set of vertex locations.\n    Now let vertex locations *a, b, c* and *d* define a quadrilateral\n    (regular or irregular) in 2D or 3D space. For this quadrilateral,\n    we organize the vertices as follows:\n\n    CELL VERTICES::\n\n            a -------Vab------- b\n           /                   /\n          /                   /\n        Vda       (X)       Vbc\n        /                   /\n       /                   /\n      d -------Vcd------- c\n\n    where the normal vector *(X)* is pointing into the page. For a set\n    of quadrilaterals whose vertices are indexed in arrays *A, B, C* and *D* ,\n    this function returns the normal surface vector(s) and the area\n    for each quadrilateral.\n\n    At each vertex, there are 4 cross-products that can be used to compute the\n    vector normal the surface defined by the quadrilateral. In 3D space however,\n    the vertices indexed may not define a quadrilateral exactly and thus the normal vectors\n    computed at each vertex might not be identical. In this case, you may choose output\n    the normal vector at *a, b, c* and *d* or compute\n    the average normal surface vector as follows:\n\n    .. math::\n        \\bf{n} = \\frac{1}{4} \\big (\n        \\bf{v_{ab} \\times v_{da}} +\n        \\bf{v_{bc} \\times v_{ab}} +\n        \\bf{v_{cd} \\times v_{bc}} +\n        \\bf{v_{da} \\times v_{cd}} \\big )\n\n\n    For computing the surface area, we assume the vertices define a quadrilateral.\n\n    Parameters\n    ----------\n    xyz : (n, 3) numpy.ndarray\n        The x,y, and z locations for all verticies\n    A : (n_face) numpy.ndarray\n        Vector containing the indicies for the **a** vertex locations\n    B : (n_face) numpy.ndarray\n        Vector containing the indicies for the **b** vertex locations\n    C : (n_face) numpy.ndarray\n        Vector containing the indicies for the **c** vertex locations\n    D : (n_face) numpy.ndarray\n        Vector containing the indicies for the **d** vertex locations\n    average : bool, optional\n        If *True*, the function returns the average surface\n        normal vector for each surface. If *False* , the function will\n        return the normal vectors computed at the *A, B, C* and *D*\n        vertices in a cell array {nA,nB,nC,nD}.\n    normalize_normal : bool, optional\n        If *True*, the function will normalize the surface normal\n        vectors. This is applied regardless of whether the *average* parameter\n        is set to *True* or *False*. If *False*, the vectors are not normalized.\n\n    Returns\n    -------\n    N : (n_face) numpy.ndarray or (4) list of (n_face) numpy.ndarray\n        Normal vector(s) for each surface. If *average* = *True*, the function\n        returns an ndarray with the average surface normal vectos. If *average* = *False* ,\n        the function returns a cell array {nA,nB,nC,nD} containing the normal vectors\n        computed using each vertex of the surface.\n    area : (n_face) numpy.ndarray\n        The surface areas.\n\n    Examples\n    --------\n    Here we define a set of vertices for a tensor mesh. We then\n    index 4 vertices for an irregular quadrilateral. The\n    function *face_info* is used to compute the normal vector\n    and the surface area.\n\n    >>> from discretize.utils import face_info\n    >>> from discretize import TensorMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> import matplotlib as mpl\n\n    Define Corners of a uniform cube.\n\n    >>> h = [1, 1]\n    >>> mesh = TensorMesh([h, h, h])\n    >>> xyz = mesh.nodes\n\n    Choose the face indices,\n\n    >>> A = np.array([0])\n    >>> B = np.array([4])\n    >>> C = np.array([26])\n    >>> D = np.array([18])\n\n    Compute average surface normal vector (normalized),\n\n    >>> nvec, area = face_info(xyz, A, B, C, D)\n    >>> nvec, area\n    (array([[-0.70710678,  0.70710678,  0.        ]]), array([4.24264069]))\n\n    Plot surface for example 1 on mesh\n\n    >>> fig = plt.figure(figsize=(7, 7))\n    >>> ax = plt.subplot(projection='3d')\n    >>> mesh.plot_grid(ax=ax)\n    >>> k = [0, 4, 26, 18, 0]\n    >>> xyz_quad = xyz[k, :]\n    >>> ax.plot(xyz_quad[:, 0], xyz_quad[:, 1], xyz_quad[:, 2], 'r')\n    >>> ax.text(-0.25, 0., 3., 'Area of the surface: {:g} $m^2$'.format(area[0]))\n    >>> ax.text(-0.25, 0., 2.8, 'Normal vector: ({:.2f}, {:.2f}, {:.2f})'.format(\n    ...     nvec[0, 0], nvec[0, 1], nvec[0, 2])\n    ... )\n    >>> plt.show()\n\n    In our second example, the vertices are unable to define a flat\n    surface in 3D space. However, we will demonstrate the *face_info*\n    returns the average normal vector and an approximate surface area.\n\n    Define the face indicies\n    >>> A = np.array([0])\n    >>> B = np.array([5])\n    >>> C = np.array([26])\n    >>> D = np.array([18])\n\n    Compute average surface normal vector\n\n    >>> nvec, area = face_info(xyz, A, B, C, D)\n    >>> nvec, area\n    (array([[-0.4472136 ,  0.89442719,  0.        ]]), array([2.23606798]))\n\n    Plot surface for example 2 on mesh\n\n    >>> fig = plt.figure(figsize=(7, 7))\n    >>> ax = plt.subplot(projection='3d')\n    >>> mesh.plot_grid(ax=ax)\n    >>> k = [0, 5, 26, 18, 0]\n    >>> xyz_quad = xyz[k, :]\n    >>> ax.plot(xyz_quad[:, 0], xyz_quad[:, 1], xyz_quad[:, 2], 'g')\n    >>> ax.text(-0.25, 0., 3., 'Area of the surface: {:g} $m^2$'.format(area[0]))\n    >>> ax.text(-0.25, 0., 2.8, 'Average normal vector: ({:.2f}, {:.2f}, {:.2f})'.format(\n    ...     nvec[0, 0], nvec[0, 1], nvec[0, 2])\n    ... )\n    >>> plt.show()\n    \"\"\"\n    if \"normalizeNormals\" in kwargs:\n        raise TypeError(\n            \"The normalizeNormals keyword argument has been removed, please use normalize_normals. \"\n            \"This will be removed in discretize 1.0.0\",\n        )\n    if not isinstance(average, bool):\n        raise TypeError(\"average must be a boolean\")\n    if not isinstance(normalize_normals, bool):\n        raise TypeError(\"normalize_normals must be a boolean\")\n\n    AB = xyz[B, :] - xyz[A, :]\n    BC = xyz[C, :] - xyz[B, :]\n    CD = xyz[D, :] - xyz[C, :]\n    DA = xyz[A, :] - xyz[D, :]\n\n    def cross(X, Y):\n        return np.c_[\n            X[:, 1] * Y[:, 2] - X[:, 2] * Y[:, 1],\n            X[:, 2] * Y[:, 0] - X[:, 0] * Y[:, 2],\n            X[:, 0] * Y[:, 1] - X[:, 1] * Y[:, 0],\n        ]\n\n    nA = cross(AB, DA)\n    nB = cross(BC, AB)\n    nC = cross(CD, BC)\n    nD = cross(DA, CD)\n\n    def length(x):\n        return np.sqrt(x[:, 0] ** 2 + x[:, 1] ** 2 + x[:, 2] ** 2)\n\n    def normalize(x):\n        return x / np.kron(np.ones((1, x.shape[1])), mkvc(length(x), 2))\n\n    if average:\n        # average the normals at each vertex.\n        N = (nA + nB + nC + nD) / 4  # this is intrinsically weighted by area\n        # normalize\n        N = normalize(N)\n    else:\n        if normalize_normals:\n            N = [normalize(nA), normalize(nB), normalize(nC), normalize(nD)]\n        else:\n            N = [nA, nB, nC, nD]\n\n    # Area calculation\n    #\n    # Approximate by 4 different triangles, and divide by 2.\n    # Each triangle is one half of the length of the cross product\n    #\n    # So also could be viewed as the average parallelogram.\n    #\n    # TODO: This does not compute correctly for concave quadrilaterals\n    area = (length(nA) + length(nB) + length(nC) + length(nD)) / 4\n\n    return N, area\n\n\nexampleLrmGrid = deprecate_function(\n    example_curvilinear_grid,\n    \"exampleLrmGrid\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\nvolTetra = deprecate_function(\n    volume_tetrahedron, \"volTetra\", removal_version=\"1.0.0\", error=True\n)\nindexCube = deprecate_function(\n    index_cube, \"indexCube\", removal_version=\"1.0.0\", error=True\n)\nfaceInfo = deprecate_function(\n    face_info, \"faceInfo\", removal_version=\"1.0.0\", error=True\n)\n"
  },
  {
    "path": "discretize/utils/curvutils.py",
    "content": "from discretize.utils.curvilinear_utils import *  # NOQA F401,F403\n\nraise ImportError(\n    \"Importing from discretize.curvutils is deprecated behavoir. Please import \"\n    \"from discretize.utils. This message will be removed in version 1.0.0 of discretize.\",\n)\n"
  },
  {
    "path": "discretize/utils/interpolation_utils.py",
    "content": "\"\"\"Utilities for creating averaging operators.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as sp\nfrom discretize.utils.matrix_utils import mkvc, sub2ind\nfrom discretize.utils.code_utils import deprecate_function\n\ntry:\n    from discretize._extensions import interputils_cython as pyx\n\n    _interp_point_1D = pyx._interp_point_1D\n    _interpmat1D = pyx._interpmat1D\n    _interpmat2D = pyx._interpmat2D\n    _interpmat3D = pyx._interpmat3D\n    _vol_interp = pyx._tensor_volume_averaging\n    _interpCython = True\nexcept ImportError as err:\n    print(err)\n    import os\n\n    # Check if being called from non-standard location (i.e. a git repository)\n    # is tree_ext.cpp here? will not be in the folder if installed to site-packages...\n    file_test = (\n        os.path.dirname(os.path.abspath(__file__))\n        + \"/_extensions/interputils_cython.pyx\"\n    )\n    if os.path.isfile(file_test):\n        # Then we are being run from a repository\n        print(\n            \"\"\"\n            Unable to import interputils_cython.\n\n            It would appear that discretize is being imported from its repository.\n            If this is intentional, you need to run:\n\n            python setup.py build_ext --inplace\n\n            to build the cython code.\n            \"\"\"\n        )\n    _interpCython = False\n\n\ndef interpolation_matrix(locs, x, y=None, z=None):\n    \"\"\"\n    Generate interpolation matrix which maps a tensor quantity to a set of locations.\n\n    This function generates a sparse matrix for interpolating tensor quantities to a set\n    of specified locations. It uses nD linear interpolation. The user may generate the\n    interpolation matrix for tensor quantities that live on 1D, 2D or 3D tensors. This\n    functionality is frequently used to interpolate quantites from cell centers or nodes\n    to specified locations.\n\n    In higher dimensions the ordering of the output has the 1st dimension changing the\n    quickest.\n\n    Parameters\n    ----------\n    locs : (n, dim) numpy.ndarray\n        The locations for the interpolated values. Here *n* is\n        the number of locations and *dim* is the dimension (1, 2 or 3)\n    x : (nx) numpy.ndarray\n        Vector defining the locations of the tensor along the x-axis\n    y : (ny) numpy.ndarray, optional\n        Vector defining the locations of the tensor along the y-axis. Required if\n        ``dim`` is 2.\n    z : (nz) numpy.ndarray, optional\n        Vector defining the locations of the tensor along the z-axis. Required if\n        ``dim`` is 3.\n\n    Returns\n    -------\n    (n, nx * ny * nz) scipy.sparse.csr_matrix\n        A sparse matrix which interpolates the tensor quantity on cell centers or nodes\n        to the set of specified locations.\n\n    Examples\n    --------\n    Here is a 1D example where a function evaluated on a regularly spaced grid\n    is interpolated to a set of random locations. To compare the accuracy, the\n    function is evaluated at the set of random locations.\n\n    >>> from discretize.utils import interpolation_matrix\n    >>> from discretize import TensorMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> rng = np.random.default_rng(14)\n\n    Create an interpolation matrix\n\n    >>> locs = rng.random(50)*0.8+0.1\n    >>> x = np.linspace(0, 1, 7)\n    >>> dense = np.linspace(0, 1, 200)\n    >>> fun = lambda x: np.cos(2*np.pi*x)\n    >>> Q = interpolation_matrix(locs, x)\n\n    Plot original function and interpolation\n\n    >>> fig1 = plt.figure(figsize=(5, 3))\n    >>> ax = fig1.add_axes([0.1, 0.1, 0.8, 0.8])\n    >>> ax.plot(dense, fun(dense), 'k:', lw=3)\n    >>> ax.plot(x, fun(x), 'ks', markersize=8)\n    >>> ax.plot(locs, Q*fun(x), 'go', markersize=4)\n    >>> ax.plot(locs, fun(locs), 'rs', markersize=4)\n    >>> ax.legend(\n    ...     [\n    ...         'True Function',\n    ...         'True (discrete loc.)',\n    ...         'Interpolated (computed)',\n    ...         'True (interp. loc.)'\n    ...     ],\n    ...     loc='upper center'\n    ... )\n    >>> plt.show()\n\n    Here, demonstrate a similar example on a 2D mesh using a 2D Gaussian distribution.\n    We interpolate the Gaussian from the nodes to cell centers and examine the relative\n    error.\n\n    >>> hx = np.ones(10)\n    >>> hy = np.ones(10)\n    >>> mesh = TensorMesh([hx, hy], x0='CC')\n    >>> def fun(x, y):\n    ...     return np.exp(-(x**2 + y**2)/2**2)\n\n    Define the the value at the mesh nodes,\n\n    >>> nodes = mesh.nodes\n    >>> val_nodes = fun(nodes[:, 0], nodes[:, 1])\n\n    >>> centers = mesh.cell_centers\n    >>> A = interpolation_matrix(\n    ...     centers, mesh.nodes_x, mesh.nodes_y\n    ... )\n    >>> val_interp = A.dot(val_nodes)\n\n    Plot the interpolated values, along with the true values at cell centers,\n\n    >>> val_centers = fun(centers[:, 0], centers[:, 1])\n    >>> fig = plt.figure(figsize=(11,3.3))\n    >>> clim = (0., 1.)\n    >>> ax1 = fig.add_subplot(131)\n    >>> ax2 = fig.add_subplot(132)\n    >>> ax3 = fig.add_subplot(133)\n    >>> mesh.plot_image(val_centers, ax=ax1, clim=clim)\n    >>> mesh.plot_image(val_interp, ax=ax2, clim=clim)\n    >>> mesh.plot_image(val_centers-val_interp, ax=ax3, clim=clim)\n    >>> ax1.set_title('Analytic at Centers')\n    >>> ax2.set_title('Interpolated from Nodes')\n    >>> ax3.set_title('Relative Error')\n    >>> plt.show()\n    \"\"\"\n    npts = locs.shape[0]\n    locs = locs.astype(float)\n    x = x.astype(float)\n    if y is None and z is None:\n        shape = [x.size]\n        inds, vals = _interpmat1D(mkvc(locs), x)\n    elif z is None:\n        y = y.astype(float)\n        shape = [x.size, y.size]\n        inds, vals = _interpmat2D(locs, x, y)\n    else:\n        y = y.astype(float)\n        z = z.astype(float)\n        shape = [x.size, y.size, z.size]\n        inds, vals = _interpmat3D(locs, x, y, z)\n\n    I = np.repeat(range(npts), 2 ** len(shape))\n    J = sub2ind(shape, inds)\n    Q = sp.csr_matrix((vals, (I, J)), shape=(npts, np.prod(shape)))\n    return Q\n\n\ndef volume_average(mesh_in, mesh_out, values=None, output=None):\n    \"\"\"Volume averaging interpolation between meshes.\n\n    This volume averaging function looks for overlapping cells in each mesh,\n    and weights the output values by the partial volume ratio of the overlapping\n    input cells. The volume average operation should result in an output such that\n    ``np.sum(mesh_in.cell_volumes*values)`` = ``np.sum(mesh_out.cell_volumes*output)``,\n    when the input and output meshes have the exact same extent. When the output mesh\n    extent goes beyond the input mesh, it is assumed to have constant values in that\n    direction. When the output mesh extent is smaller than the input mesh, only the\n    overlapping extent of the input mesh contributes to the output.\n\n    This function operates in three different modes. If only *mesh_in* and\n    *mesh_out* are given, the returned value is a ``scipy.sparse.csr_matrix``\n    that represents this operation (so it could potentially be applied repeatedly).\n    If *values* is given, the volume averaging is performed right away (without\n    internally forming the matrix) and the returned value is the result of this.\n    If *output* is given as well, it will be filled with the values of the\n    operation and then returned (assuming it has the correct ``dtype``).\n\n    Parameters\n    ----------\n    mesh_in : ~discretize.TensorMesh or ~discretize.TreeMesh\n        Input mesh (the mesh you are interpolating from)\n    mesh_out : ~discretize.TensorMesh or ~discretize.TreeMesh\n        Output mesh (the mesh you are interpolating to)\n    values : (mesh_in.n_cells) numpy.ndarray, optional\n        Array with values defined at the cells of ``mesh_in``\n    output : (mesh_out.n_cells) numpy.ndarray of float, optional\n        Output array to be overwritten\n\n    Returns\n    -------\n    (mesh_out.n_cells, mesh_in.n_cells) scipy.sparse.csr_matrix or (mesh_out.n_cells) numpy.ndarray\n        If *values* = *None* , the returned value is a matrix representing this\n        operation, otherwise it is a :class:`numpy.ndarray` of the result of the\n        operation.\n\n    Examples\n    --------\n    Create two meshes with the same extent, but different divisions (the meshes\n    do not have to be the same extent).\n\n    >>> import numpy as np\n    >>> from discretize import TensorMesh\n    >>> rng = np.random.default_rng(853)\n    >>> h1 = np.ones(32)\n    >>> h2 = np.ones(16)*2\n    >>> mesh_in = TensorMesh([h1, h1])\n    >>> mesh_out = TensorMesh([h2, h2])\n\n    Create a random model defined on the input mesh, and use volume averaging to\n    interpolate it to the output mesh.\n\n    >>> from discretize.utils import volume_average\n    >>> model1 = rng.random(mesh_in.nC)\n    >>> model2 = volume_average(mesh_in, mesh_out, model1)\n\n    Because these two meshes' cells are perfectly aligned, but the output mesh\n    has 1 cell for each 4 of the input cells, this operation should effectively\n    look like averaging each of those cells values\n\n    >>> import matplotlib.pyplot as plt\n    >>> plt.figure(figsize=(6, 3))\n    >>> ax1 = plt.subplot(121)\n    >>> mesh_in.plot_image(model1, ax=ax1)\n    >>> ax2 = plt.subplot(122)\n    >>> mesh_out.plot_image(model2, ax=ax2)\n    >>> plt.show()\n\n    \"\"\"\n    try:\n        in_type = mesh_in._meshType\n        out_type = mesh_out._meshType\n    except AttributeError:\n        raise TypeError(\"Both input and output mesh must be valid discetize meshes\")\n\n    valid_meshs = [\"TENSOR\", \"TREE\"]\n    if in_type not in valid_meshs or out_type not in valid_meshs:\n        raise NotImplementedError(\n            f\"Volume averaging is only implemented for TensorMesh and TreeMesh, \"\n            f\"not {type(mesh_in).__name__} and/or {type(mesh_out).__name__}\"\n        )\n\n    if mesh_in.dim != mesh_out.dim:\n        raise ValueError(\"Both meshes must have the same dimension\")\n\n    if values is not None and len(values) != mesh_in.nC:\n        raise ValueError(\n            \"Input array does not have the same length as the number of cells in input mesh\"\n        )\n    if output is not None and len(output) != mesh_out.nC:\n        raise ValueError(\n            \"Output array does not have the same length as the number of cells in output mesh\"\n        )\n\n    if values is not None:\n        values = np.asarray(values, dtype=np.float64)\n    if output is not None:\n        output = np.asarray(output, dtype=np.float64)\n\n    if in_type == \"TENSOR\":\n        if out_type == \"TENSOR\":\n            return _vol_interp(mesh_in, mesh_out, values, output)\n        elif out_type == \"TREE\":\n            return mesh_out._vol_avg_from_tens(mesh_in, values, output)\n    elif in_type == \"TREE\":\n        if out_type == \"TENSOR\":\n            return mesh_in._vol_avg_to_tens(mesh_out, values, output)\n        elif out_type == \"TREE\":\n            return mesh_out._vol_avg_from_tree(mesh_in, values, output)\n    else:\n        raise TypeError(\"Unsupported mesh types\")\n\n\ninterpmat = deprecate_function(\n    interpolation_matrix, \"interpmat\", removal_version=\"1.0.0\", error=True\n)\n"
  },
  {
    "path": "discretize/utils/interputils.py",
    "content": "from discretize.utils.interpolation_utils import *  # NOQA F401,F403\n\nraise ImportError(\n    \"Importing from discretize.interputils is deprecated behavoir. Please import \"\n    \"from discretize.utils. This message will be removed in version 1.0.0 of discretize.\",\n)\n"
  },
  {
    "path": "discretize/utils/io_utils.py",
    "content": "\"\"\"Simple input/output routines.\"\"\"\n\nfrom urllib.request import urlretrieve\nimport os\nimport importlib\nimport json\n\n\ndef load_mesh(file_name):\n    \"\"\"Load discretize mesh saved to json file.\n\n    For a discretize mesh that has been converted to dictionary and\n    written to a json file, the function **load_mesh** loads the\n    json file and reconstructs the mesh object.\n\n    Parameters\n    ----------\n    file_name : str\n        Name of the json file being read in. Contains all information required to\n        reconstruct the mesh.\n\n    Returns\n    -------\n    discretize.base.BaseMesh\n        A discretize mesh defined by the class and parameters stored in the json file\n    \"\"\"\n    with open(file_name, \"r\") as outfile:\n        jsondict = json.load(outfile)\n        module_name = jsondict.pop(\n            \"__module__\", \"discretize\"\n        )  # default to loading from discretize\n        class_name = jsondict.pop(\"__class__\")\n        mod = importlib.import_module(module_name)\n        cls = getattr(mod, class_name)\n        if \"_n\" in jsondict:\n            jsondict[\"shape_cells\"] = jsondict.pop(\n                \"_n\"\n            )  # need to catch this old _n property here\n        data = cls(**jsondict)\n    return data\n\n\ndef download(url, folder=\".\", overwrite=False, verbose=True):\n    \"\"\"\n    Download file(s) stored in a cloud directory.\n\n    Parameters\n    ----------\n    url : str or list of str\n        url or list of urls for the file(s) being downloaded\n    folder : str, optional\n        Local folder where downloaded files are to be stored\n    overwrite : bool, optional\n        Overwrite files if they have the same name as newly downloaded files\n    verbose : bool, optional\n        Print progress when downloading multiple files\n\n    Returns\n    -------\n    os.path or list of os.path\n        The path or a list of paths for all downloaded files\n    \"\"\"\n\n    def rename_path(downloadpath):\n        splitfullpath = downloadpath.split(os.path.sep)\n\n        # grab just the file name\n        fname = splitfullpath[-1]\n        fnamesplit = fname.split(\".\")\n        newname = fnamesplit[0]\n\n        # check if we have already re-numbered\n        newnamesplit = newname.split(\"(\")\n\n        # add (num) to the end of the file name\n        if len(newnamesplit) == 1:\n            num = 1\n        else:\n            num = int(newnamesplit[-1][:-1])\n            num += 1\n\n        newname = \"{}({}).{}\".format(newnamesplit[0], num, fnamesplit[-1])\n        return os.path.sep.join(splitfullpath[:-1] + newnamesplit[:-1] + [newname])\n\n    # ensure we are working with absolute paths and home directories dealt with\n    folder = os.path.abspath(os.path.expanduser(folder))\n\n    # make the directory if it doesn't currently exist\n    if not os.path.exists(folder):\n        os.makedirs(folder)\n\n    if isinstance(url, str):\n        file_names = [url.split(\"/\")[-1]]\n    elif isinstance(url, list):\n        file_names = [u.split(\"/\")[-1] for u in url]\n\n    downloadpath = [os.path.sep.join([folder, f]) for f in file_names]\n\n    # check if the directory already exists\n    for i, download in enumerate(downloadpath):\n        if os.path.exists(download):\n            if overwrite:\n                if verbose:\n                    print(\"overwriting {}\".format(download))\n            else:\n                while os.path.exists(download):\n                    download = rename_path(download)\n\n                if verbose:\n                    print(\"file already exists, new file is called {}\".format(download))\n                downloadpath[i] = download\n\n    # download files\n    urllist = url if isinstance(url, list) else [url]\n    for u, f in zip(urllist, downloadpath):\n        print(\"Downloading {}\".format(u))\n        urlretrieve(u, f)\n        print(\"   saved to: \" + f)\n\n    print(\"Download completed!\")\n    return downloadpath if isinstance(url, list) else downloadpath[0]\n"
  },
  {
    "path": "discretize/utils/matrix_utils.py",
    "content": "\"\"\"Useful functions for working with vectors and matrices.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as sp\nfrom discretize.utils.code_utils import is_scalar, deprecate_function\nimport warnings\n\n\ndef mkvc(x, n_dims=1, **kwargs):\n    \"\"\"Coerce a vector to the specified dimensionality.\n\n    This function converts a :class:`numpy.ndarray` to a vector. In general,\n    the output vector has a dimension of 1. However, the dimensionality\n    can be specified if the user intends to carry out a dot product with\n    a higher order array.\n\n    Parameters\n    ----------\n    x : array_like\n        An array that will be reorganized and output as a vector. The input array\n        will be flattened on input in Fortran order.\n    n_dims : int\n        The dimension of the output vector. :data:`numpy.newaxis` are appended to the\n        output array until it has this many axes.\n\n    Returns\n    -------\n    numpy.ndarray\n        The output vector, with at least ``n_dims`` axes.\n\n    Examples\n    --------\n    Here, we reorganize a simple 2D array as a vector and demonstrate the\n    impact of the *n_dim* argument.\n\n    >>> from discretize.utils import mkvc\n    >>> import numpy as np\n    >>> rng = np.random.default_rng(856)\n\n    >>> a = rng.random(3, 2)\n    >>> a\n    array([[0.33534155, 0.25334363],\n           [0.07147884, 0.81080958],\n           [0.85892774, 0.74357806]])\n\n    >>> v = mkvc(a)\n    >>> v\n    array([0.33534155, 0.07147884, 0.85892774, 0.25334363, 0.81080958,\n           0.74357806])\n\n    In Higher dimensions:\n\n    >>> for ii in range(1, 4):\n    ...     v = mkvc(a, ii)\n    ...     print('Shape of output with n_dim =', ii, ': ', v.shape)\n    Shape of output with n_dim = 1 :  (6,)\n    Shape of output with n_dim = 2 :  (6, 1)\n    Shape of output with n_dim = 3 :  (6, 1, 1)\n    \"\"\"\n    if \"numDims\" in kwargs:\n        raise TypeError(\n            \"The numDims keyword argument has been removed, please use n_dims. \"\n            \"This will be removed in discretize 1.0.0\",\n        )\n    if isinstance(x, np.matrix):\n        x = np.array(x)\n\n    if hasattr(x, \"tovec\"):\n        x = x.tovec()\n\n    if isinstance(x, Zero):\n        return x\n\n    if not isinstance(x, np.ndarray):\n        raise TypeError(\"Vector must be a numpy array\")\n\n    if n_dims == 1:\n        return x.flatten(order=\"F\")\n    elif n_dims == 2:\n        return x.flatten(order=\"F\")[:, np.newaxis]\n    elif n_dims == 3:\n        return x.flatten(order=\"F\")[:, np.newaxis, np.newaxis]\n\n\ndef sdiag(v):\n    \"\"\"Generate sparse diagonal matrix from a vector.\n\n    This function creates a sparse diagonal matrix whose diagonal elements\n    are defined by the input vector *v*. For a vector of length *n*,\n    the output matrix has shape (n,n).\n\n    Parameters\n    ----------\n    v : (n) numpy.ndarray or discretize.utils.Zero\n        The vector defining the diagonal elements of the sparse matrix being constructed\n\n    Returns\n    -------\n    (n, n) scipy.sparse.csr_matrix or discretize.utils.Zero\n        The sparse diagonal matrix.\n\n    Examples\n    --------\n    Use a 1D array of values to construct a sparse diagonal matrix.\n\n    >>> from discretize.utils import sdiag\n    >>> import numpy as np\n    >>> v = np.array([6., 3., 1., 8., 0., 5.])\n    >>> M = sdiag(v)\n    \"\"\"\n    if isinstance(v, Zero):\n        return Zero()\n\n    return sp.spdiags(mkvc(v), 0, v.size, v.size, format=\"csr\")\n\n\ndef sdinv(M):\n    \"\"\"Return inverse of a sparse diagonal matrix.\n\n    This function extracts the diagonal elements of the input matrix *M*\n    and creates a sparse diagonal matrix from the reciprocal these elements.\n    If the input matrix *M* is diagonal, the output is the inverse of *M*.\n\n    Parameters\n    ----------\n    M : (n, n) scipy.sparse.csr_matrix\n        A sparse diagonal matrix\n\n    Returns\n    -------\n    (n, n) scipy.sparse.csr_matrix\n        The inverse of the sparse diagonal matrix.\n\n    Examples\n    --------\n    >>> from discretize.utils import sdiag, sdinv\n    >>> import numpy as np\n\n    >>> v = np.array([6., 3., 1., 8., 0., 5.])\n    >>> M = sdiag(v)\n    >>> Minv = sdinv(M)\n\n    \"\"\"\n    return sdiag(1.0 / M.diagonal())\n\n\ndef speye(n):\n    \"\"\"Generate sparse identity matrix.\n\n    Parameters\n    ----------\n    n : int\n        The dimensions of the sparse identity matrix.\n\n    Returns\n    -------\n    (n, n) scipy.sparse.csr_matrix\n        The sparse identity matrix.\n    \"\"\"\n    return sp.identity(n, format=\"csr\")\n\n\ndef kron3(A, B, C):\n    r\"\"\"Compute kronecker products between 3 sparse matricies.\n\n    Where :math:`\\otimes` denotes the Kronecker product and *A, B* and *C* are\n    sparse matrices, this function outputs :math:`(A \\otimes B) \\otimes C`.\n\n    Parameters\n    ----------\n    A, B, C : scipy.sparse.spmatrix\n        Sparse matrices.\n\n    Returns\n    -------\n    scipy.sparse.csr_matrix\n        Kroneker between the 3 sparse matrices.\n    \"\"\"\n    return sp.kron(sp.kron(A, B), C, format=\"csr\")\n\n\ndef spzeros(n1, n2):\n    \"\"\"Generate sparse matrix of zeros of shape=(n1, n2).\n\n    Parameters\n    ----------\n    n1 : int\n        Number of rows.\n    n2 : int\n        Number of columns.\n\n    Returns\n    -------\n    (n1, n2) scipy.sparse.dia_matrix\n        A sparse matrix of zeros.\n    \"\"\"\n    return sp.dia_matrix((n1, n2))\n\n\ndef ddx(n):\n    r\"\"\"Create 1D difference (derivative) operator from nodes to centers.\n\n    For n cells, the 1D difference (derivative) operator from nodes to\n    centers is sparse, has shape (n, n+1) and takes the form:\n\n    .. math::\n        \\begin{bmatrix}\n        -1 & 1 & & & \\\\\n        & -1 & 1 & & \\\\\n        & & \\ddots & \\ddots & \\\\\n        & & & -1 & 1\n        \\end{bmatrix}\n\n    Parameters\n    ----------\n    n : int\n        Number of cells\n\n    Returns\n    -------\n    (n, n + 1) scipy.sparse.csr_matrix\n        The 1D difference operator from nodes to centers.\n    \"\"\"\n    return sp.spdiags((np.ones((n + 1, 1)) * [-1, 1]).T, [0, 1], n, n + 1, format=\"csr\")\n\n\ndef av(n):\n    r\"\"\"Create 1D averaging operator from nodes to cell-centers.\n\n    For n cells, the 1D averaging operator from nodes to centerss\n    is sparse, has shape (n, n+1) and takes the form:\n\n    .. math::\n        \\begin{bmatrix}\n        1/2 & 1/2 & & & \\\\\n        & 1/2 & 1/2 & & \\\\\n        & & \\ddots & \\ddots & \\\\\n        & & & 1/2 & 1/2\n        \\end{bmatrix}\n\n    Parameters\n    ----------\n    n : int\n        Number of cells\n\n    Returns\n    -------\n    (n, n + 1) scipy.sparse.csr_matrix\n        The 1D averaging operator from nodes to centers.\n    \"\"\"\n    return sp.spdiags(\n        (0.5 * np.ones((n + 1, 1)) * [1, 1]).T, [0, 1], n, n + 1, format=\"csr\"\n    )\n\n\ndef av_extrap(n):\n    r\"\"\"Create 1D averaging operator from cell-centers to nodes.\n\n    For n cells, the 1D averaging operator from cell centers to nodes\n    is sparse and has shape (n+1, n). Values at the outmost nodes are\n    extrapolated from the nearest cell center value. Thus the operator\n    takes the form:\n\n    .. math::\n        \\begin{bmatrix}\n        1 & & & & \\\\\n        1/2 & 1/2 & & & \\\\\n        & 1/2 & 1/2 & & & \\\\\n        & & \\ddots & \\ddots & \\\\\n        & & & 1/2 & 1/2 \\\\\n        & & & & 1\n        \\end{bmatrix}\n\n    Parameters\n    ----------\n    n : int\n        Number of cells\n\n    Returns\n    -------\n    (n+1, n) scipy.sparse.csr_matrix\n        The 1D averaging operator from cell-centers to nodes.\n    \"\"\"\n    Av = sp.spdiags(\n        (0.5 * np.ones((n, 1)) * [1, 1]).T, [-1, 0], n + 1, n, format=\"csr\"\n    ) + sp.csr_matrix(([0.5, 0.5], ([0, n], [0, n - 1])), shape=(n + 1, n))\n    return Av\n\n\ndef ndgrid(*args, vector=True, order=\"F\"):\n    \"\"\"Generate gridded locations for 1D, 2D, or 3D tensors.\n\n    For 1D, 2D, or 3D tensors, this function takes the unique positions defining\n    a tensor along each of its axis and returns the gridded locations.\n    For 2D and 3D meshes, the user may treat the unique *x*, *y* (and *z*)\n    positions a successive positional arguments or as a single argument using\n    a list [*x*, *y*, (*z*)].\n\n    For outputs, let *dim* be the number of dimension (1, 2 or 3) and let *n* be\n    the total number of gridded locations. The gridded *x*, *y* (and *z*) locations\n    can be return as a single numpy array of shape [n, ndim]. The user can also\n    return the gridded *x*, *y* (and *z*) locations as a list of length *ndim*.\n    The list contains entries contain the *x*, *y* (and *z*) locations as tensors.\n    See examples.\n\n    Parameters\n    ----------\n    *args : (n, dim) numpy.ndarray or (dim) list of (n) numpy.ndarray\n        Positions along each axis of the tensor. The user can define these as\n        successive positional arguments *x*, *y*, (and *z*) or as a single argument\n        using a list [*x*, *y*, (*z*)].\n    vector : bool, optional\n        If *True*, the output is a numpy array of dimension [n, ndim]. If *False*,\n        the gridded x, y (and z) locations are returned as separate ndarrays in a list.\n        Default is *True*.\n    order : {'F', 'C', 'A'}\n        Define ordering using one of the following options:\n        'C' is C-like ordering, 'F' is Fortran-like ordering, 'A' is Fortran\n        ordering if memory is contigious and C-like otherwise. Default = 'F'.\n        See :func:`numpy.reshape` for more on this argument.\n\n    Returns\n    -------\n    numpy.ndarray or list of numpy.ndarray\n        If *vector* = *True* the gridded *x*, *y*, (and *z*) locations are\n        returned as a numpy array of shape [n, ndim]. If *vector* = *False*,\n        the gridded *x*, *y*, (and *z*) are returned as a list of vectors.\n\n    Examples\n    --------\n    >>> from discretize.utils import ndgrid\n    >>> import numpy as np\n\n    >>> x = np.array([1, 2, 3])\n    >>> y = np.array([2, 4])\n\n    >>> ndgrid([x, y])\n    array([[1, 2],\n           [2, 2],\n           [3, 2],\n           [1, 4],\n           [2, 4],\n           [3, 4]])\n\n    >>> ndgrid(x, y, order='C')\n    array([[1, 2],\n           [1, 4],\n           [2, 2],\n           [2, 4],\n           [3, 2],\n           [3, 4]])\n\n    >>> ndgrid(x, y, vector=False)\n    [array([[1, 1],\n           [2, 2],\n           [3, 3]]), array([[2, 4],\n           [2, 4],\n           [2, 4]])]\n    \"\"\"\n    # Read the keyword arguments, and only accept a vector=True/False\n    if not isinstance(vector, bool):\n        raise TypeError(\"'vector' keyword must be a bool\")\n\n    # you can either pass a list [x1, x2, x3] or each seperately\n    if isinstance(args[0], list):\n        xin = args[0]\n    else:\n        xin = args\n\n    # Each vector needs to be a numpy array\n    try:\n        if len(xin) == 1:\n            return np.array(xin[0])\n        meshed = np.meshgrid(*xin, indexing=\"ij\")\n    except Exception:\n        raise TypeError(\"All arguments must be array like\")\n\n    if vector:\n        return np.column_stack([x.reshape(-1, order=order) for x in meshed])\n    return meshed\n\n\ndef make_boundary_bool(shape, bdir=\"xyz\", **kwargs):\n    r\"\"\"Return boundary indices of a tensor grid.\n\n    For a tensor grid whose shape is given (1D, 2D or 3D), this function\n    returns a boolean index array identifying the x, y and/or z\n    boundary locations.\n\n    Parameters\n    ----------\n    shape : (dim) tuple of int\n        Defines the shape of the tensor (1D, 2D or 3D).\n    bdir : str containing characters 'x', 'y' and/or 'z'\n        Specify the boundaries whose indices you want returned; e.g. for a 3D\n        tensor, you may set *dir* = 'xz' to return the indices of the x and\n        z boundary locations.\n\n    Returns\n    -------\n    numpy.ndarray of bool\n        Indices of boundary locations of the tensor for specified boundaries. The\n        returned order matches the order the items occur in the flattened ``ndgrid``\n\n    Examples\n    --------\n    Here we construct a 3x3 tensor and find the indices of the boundary locations.\n\n    >>> from discretize.utils.matrix_utils import ndgrid, make_boundary_bool\n    >>> import numpy as np\n\n    Define a 3x3 tensor grid\n\n    >>> x = np.array([1, 2, 3])\n    >>> y = np.array([2, 4, 6])\n    >>> tensor_grid = ndgrid(x, y)\n\n    Find indices of boundary locations.\n\n    >>> shape = (len(x), len(y))\n    >>> bool_ind = make_boundary_bool(shape)\n    >>> tensor_grid[bool_ind]\n    array([[1, 2],\n           [2, 2],\n           [3, 2],\n           [1, 4],\n           [3, 4],\n           [1, 6],\n           [2, 6],\n           [3, 6]])\n\n    Find indices of locations of only the x boundaries,\n\n    >>> bool_ind_x = make_boundary_bool(shape, 'x')\n    >>> tensor_grid[bool_ind_x]\n    array([[1, 2],\n           [3, 2],\n           [1, 4],\n           [3, 4],\n           [1, 6],\n           [3, 6]])\n    \"\"\"\n    old_dir = kwargs.pop(\"dir\", None)\n    if old_dir is not None:\n        warnings.warn(\n            \"The `dir` keyword argument has been renamed to `bdir` to avoid shadowing the \"\n            \"builtin variable `dir`. This will be removed in discretize 1.0.0\",\n            FutureWarning,\n            stacklevel=2,\n        )\n        bdir = old_dir\n    is_b = np.zeros(shape, dtype=bool, order=\"F\")\n    if \"x\" in bdir:\n        is_b[[0, -1]] = True\n    if len(shape) > 1:\n        if \"y\" in bdir:\n            is_b[:, [0, -1]] = True\n    if len(shape) > 2:\n        if \"z\" in bdir:\n            is_b[:, :, [0, -1]] = True\n    return is_b.reshape(-1, order=\"F\")\n\n\ndef ind2sub(shape, inds):\n    r\"\"\"Return subscripts of tensor grid elements from indices.\n\n    This function is a wrapper for :func:`numpy.unravel_index` with a hard-coded Fortran\n    order.\n\n    Consider the :math:`n^{th}` element of a tensor grid with *N* elements.\n    The position of this element in the tensor grid can also be defined by\n    subscripts (i,j,k). For an array containing the indices for a set of\n    tensor elements, this function returns the corresponding subscripts.\n\n    Parameters\n    ----------\n    shape : (dim) tuple of int\n        Defines the shape of the tensor (1D, 2D or 3D).\n    inds : array_like of int\n        The indices of the tensor elements whose subscripts you want returned.\n\n    Returns\n    -------\n    (dim) tuple of numpy.ndarray\n        Corresponding subscipts for the indices provided. The output is a\n        tuple containing 1D integer arrays for the i, j and k subscripts, respectively.\n        The output array will match the shape of the **inds** input.\n\n    See Also\n    --------\n    numpy.unravel_index\n    \"\"\"\n    return np.unravel_index(inds, shape, order=\"F\")\n\n\ndef sub2ind(shape, subs):\n    r\"\"\"Return indices of tensor grid elements from subscripts.\n\n    This function is a wrapper for :func:`numpy.ravel_multi_index` with a hard-coded\n    Fortran order, and a column order for the ``multi_index``\n\n    Consider elements of a tensors grid whose positions are given by the\n    subscripts (i,j,k). This function will return the corresponding indices\n    of these elements. Each row of the input array *subs* defines the\n    ijk for a particular tensor element.\n\n    Parameters\n    ----------\n    shape : (dim) tuple of int\n        Defines the shape of the tensor (1D, 2D or 3D).\n    subs : (N, dim) array_like of int\n        The subscripts of the tensor grid elements. Each rows defines the position\n        of a particular tensor element. The shape of of the array is (N, ndim).\n\n    Returns\n    -------\n    numpy.ndarray of int\n        The indices of the tensor grid elements defined by *subs*.\n\n    See Also\n    --------\n    numpy.ravel_multi_index\n\n    Examples\n    --------\n    He we recreate the examples from :func:`numpy.ravel_multi_index` to illustrate the\n    differences. The indices corresponding to each dimension are now columns in the\n    array (instead of rows), and it assumed to use a Fortran order.\n\n    >>> import numpy as np\n    >>> from discretize.utils import sub2ind\n    >>> arr = np.array([[3, 4], [6, 5], [6, 1]])\n    >>> sub2ind((7, 6), arr)\n    array([31, 41, 13], dtype=int64)\n    \"\"\"\n    if len(shape) == 1:\n        return subs\n    subs = np.atleast_2d(subs)\n    if subs.shape[1] != len(shape):\n        raise ValueError(\n            \"Indexing must be done as a column vectors. e.g. [[3,6],[6,2],...]\"\n        )\n    inds = np.ravel_multi_index(subs.T, shape, order=\"F\")\n    return mkvc(inds)\n\n\ndef get_subarray(A, ind):\n    \"\"\"Extract a subarray.\n\n    For a :class:`numpy.ndarray`, the function **get_subarray** extracts a subset of\n    the array. The portion of the original array being extracted is defined\n    by providing the indices along each axis.\n\n    Parameters\n    ----------\n    A : numpy.ndarray\n        The original numpy array. Must be 1, 2 or 3 dimensions.\n    ind : (dim) list of numpy.ndarray\n        A list of numpy arrays containing the indices being extracted along each\n        dimension. The length of the list must equal the dimensions of the input array.\n\n    Returns\n    -------\n    numpy.ndarray\n        The subarray extracted from the original array\n\n    Examples\n    --------\n    Here we construct a random 3x3 numpy array and use **get_subarray** to extract\n    the first column.\n\n    >>> from discretize.utils import get_subarray\n    >>> import numpy as np\n    >>> rng = np.random.default_rng(421)\n    >>> A = rng.random((3, 3))\n    >>> A\n    array([[1.07969034e-04, 9.78613931e-01, 6.62123429e-01],\n           [8.80722877e-01, 7.61035691e-01, 7.42546796e-01],\n           [9.09488911e-01, 7.80626334e-01, 8.67663825e-01]])\n\n    Define the indexing along the columns and rows and create the indexing list\n\n    >>> ind_x = np.array([0, 1, 2])\n    >>> ind_y = np.array([0, 2])\n    >>> ind = [ind_x, ind_y]\n\n    Extract the first, and third column of A\n\n    >>> get_subarray(A, ind)\n    array([[1.07969034e-04, 6.62123429e-01],\n           [8.80722877e-01, 7.42546796e-01],\n           [9.09488911e-01, 8.67663825e-01]])\n    \"\"\"\n    if not isinstance(ind, list):\n        raise TypeError(\"ind must be a list of vectors\")\n    if len(A.shape) != len(ind):\n        raise ValueError(\"ind must have the same length as the dimension of A\")\n\n    if len(A.shape) == 2:\n        return A[ind[0], :][:, ind[1]]\n    elif len(A.shape) == 3:\n        return A[ind[0], :, :][:, ind[1], :][:, :, ind[2]]\n    else:\n        raise Exception(\"get_subarray does not support dimension asked.\")\n\n\ndef inverse_3x3_block_diagonal(\n    a11, a12, a13, a21, a22, a23, a31, a32, a33, return_matrix=True, **kwargs\n):\n    r\"\"\"Invert a set of 3x3 matricies from vectors containing their elements.\n\n    Parameters\n    ----------\n    a11, a12, ..., a33 : (n_blocks) numpy.ndarray\n        Vectors which contain the\n        corresponding element for all 3x3 matricies\n    return_matrix : bool, optional\n        - **True**: Returns the sparse block 3x3 matrix *M* (default).\n        - **False:** Returns the vectors containing the elements of each matrix' inverse.\n\n    Returns\n    -------\n    (3 * n_blocks, 3 * n_blocks) scipy.sparse.coo_matrix or list of (n_blocks)\n        numpy.ndarray. If *return_matrix = False*, the function will return vectors\n        *b11, b12, b13, b21, b22, b23, b31, b32, b33*. If *return_matrix = True*, the\n        function will return the block matrix *M*.\n\n    Notes\n    -----\n    The elements of a 3x3 matrix *A* are given by:\n\n    .. math::\n        A = \\begin{bmatrix}\n        a_{11} & a_{12} & a_{13} \\\\\n        a_{21} & a_{22} & a_{23} \\\\\n        a_{31} & a_{32} & a_{33}\n        \\end{bmatrix}\n\n    For a set of 3x3 matricies, the elements may be stored in a set of 9 distinct vectors\n    :math:`\\mathbf{a_{11}}`, :math:`\\mathbf{a_{12}}`, ..., :math:`\\mathbf{a_{33}}`.\n    For each matrix, **inverse_3x3_block_diagonal** ouputs the vectors containing the\n    elements of each matrix' inverse; i.e.\n    :math:`\\mathbf{b_{11}}`, :math:`\\mathbf{b_{12}}`, ..., :math:`\\mathbf{b_{33}}`\n    where:\n\n    .. math::\n        A^{-1} = B = \\begin{bmatrix}\n        b_{11} & b_{12} & b_{13} \\\\\n        b_{21} & b_{22} & b_{23} \\\\\n        b_{31} & b_{32} & b_{33}\n        \\end{bmatrix}\n\n    For special applications, we may want to output the elements of the inverses\n    of the matricies as a 3x3 block matrix of the form:\n\n    .. math::\n        M = \\begin{bmatrix}\n        D_{11} & D_{12} & D_{13} \\\\\n        D_{21} & D_{22} & D_{23} \\\\\n        D_{31} & D_{32} & D_{33}\n        \\end{bmatrix}\n\n    where :math:`D_{ij}` are diagonal matrices whose non-zero elements\n    are defined by vector :math:`\\\\mathbf{b_{ij}}`. Where *n* is the\n    number of matricies, the block matrix is sparse with dimensions\n    (3n, 3n).\n\n    Examples\n    --------\n    Here, we define four 3x3 matricies and reorganize their elements into\n    9 vectors a11, a12, ..., a33. We then examine the outputs of the\n    function **inverse_3x3_block_diagonal** when the argument\n    *return_matrix* is set to both *True* and *False*.\n\n    >>> from discretize.utils import inverse_3x3_block_diagonal\n    >>> import numpy as np\n    >>> import scipy as sp\n    >>> import matplotlib.pyplot as plt\n\n    Define four 3x3 matricies, and organize their elements into nine vectors\n\n    >>> A1 = np.random.uniform(1, 10, (3, 3))\n    >>> A2 = np.random.uniform(1, 10, (3, 3))\n    >>> A3 = np.random.uniform(1, 10, (3, 3))\n    >>> A4 = np.random.uniform(1, 10, (3, 3))\n    >>> [[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]] = np.stack(\n    ...     [A1, A2, A3, A4], axis=-1\n    ... )\n\n    Return the elements of their inverse and validate\n\n    >>> b11, b12, b13, b21, b22, b23, b31, b32, b33 = inverse_3x3_block_diagonal(\n    ...     a11, a12, a13, a21, a22, a23, a31, a32, a33, return_matrix=False\n    ... )\n    >>> Bs = np.stack([[b11, b12, b13],[b21, b22, b23],[b31, b32, b33]])\n    >>> B1, B2, B3, B4 = Bs.transpose((2, 0, 1))\n\n    >>> np.linalg.inv(A1)\n    array([[ 0.20941584,  0.18477151, -0.22637147],\n           [-0.06420656, -0.34949639,  0.29216461],\n           [-0.14226339,  0.11160555,  0.0907583 ]])\n    >>> B1\n    array([[ 0.20941584,  0.18477151, -0.22637147],\n           [-0.06420656, -0.34949639,  0.29216461],\n           [-0.14226339,  0.11160555,  0.0907583 ]])\n\n    We can also return this as a sparse matrix with block diagonal inverse\n\n    >>> M = inverse_3x3_block_diagonal(\n    ...     a11, a12, a13, a21, a22, a23, a31, a32, a33\n    ... )\n    >>> plt.spy(M)\n    >>> plt.show()\n    \"\"\"\n    if \"returnMatrix\" in kwargs:\n        warnings.warn(\n            \"The returnMatrix keyword argument has been deprecated, please use return_matrix. \"\n            \"This will be removed in discretize 1.0.0\",\n            FutureWarning,\n            stacklevel=2,\n        )\n        return_matrix = kwargs[\"returnMatrix\"]\n\n    a11 = mkvc(a11)\n    a12 = mkvc(a12)\n    a13 = mkvc(a13)\n    a21 = mkvc(a21)\n    a22 = mkvc(a22)\n    a23 = mkvc(a23)\n    a31 = mkvc(a31)\n    a32 = mkvc(a32)\n    a33 = mkvc(a33)\n\n    detA = (\n        a31 * a12 * a23\n        - a31 * a13 * a22\n        - a21 * a12 * a33\n        + a21 * a13 * a32\n        + a11 * a22 * a33\n        - a11 * a23 * a32\n    )\n\n    b11 = +(a22 * a33 - a23 * a32) / detA\n    b12 = -(a12 * a33 - a13 * a32) / detA\n    b13 = +(a12 * a23 - a13 * a22) / detA\n\n    b21 = +(a31 * a23 - a21 * a33) / detA\n    b22 = -(a31 * a13 - a11 * a33) / detA\n    b23 = +(a21 * a13 - a11 * a23) / detA\n\n    b31 = -(a31 * a22 - a21 * a32) / detA\n    b32 = +(a31 * a12 - a11 * a32) / detA\n    b33 = -(a21 * a12 - a11 * a22) / detA\n\n    if not return_matrix:\n        return b11, b12, b13, b21, b22, b23, b31, b32, b33\n\n    return sp.vstack(\n        (\n            sp.hstack((sdiag(b11), sdiag(b12), sdiag(b13))),\n            sp.hstack((sdiag(b21), sdiag(b22), sdiag(b23))),\n            sp.hstack((sdiag(b31), sdiag(b32), sdiag(b33))),\n        )\n    )\n\n\ndef inverse_2x2_block_diagonal(a11, a12, a21, a22, return_matrix=True, **kwargs):\n    r\"\"\"\n    Invert a set of 2x2 matricies from vectors containing their elements.\n\n    Parameters\n    ----------\n    a11, a12, a21, a22 : (n_blocks) numpy.ndarray\n        All arguments a11, a12, a21, a22 are vectors which contain the\n        corresponding element for all 2x2 matricies\n    return_matrix : bool, optional\n        - **True:** Returns the sparse block 2x2 matrix *M*.\n        - **False:** Returns the vectors containing the elements of each matrix' inverse.\n\n    Returns\n    -------\n    (2 * n_blocks, 2 * n_blocks) scipy.sparse.coo_matrix or list of (n_blocks) numpy.ndarray\n        If *return_matrix = False*, the function will return vectors\n        *b11, b12, b21, b22*.\n        If *return_matrix = True*, the function will return the\n        block matrix *M*\n\n    Notes\n    -----\n    The elements of a 2x2 matrix *A* are given by:\n\n    .. math::\n        A = \\begin{bmatrix}\n        a_{11} & a_{12} \\\\\n        a_{21} & a_{22}\n        \\end{bmatrix}\n\n    For a set of 2x2 matricies, the elements may be stored in a set of 4 distinct vectors\n    :math:`\\mathbf{a_{11}}`, :math:`\\mathbf{a_{12}}`, :math:`\\mathbf{a_{21}}` and\n    :math:`\\mathbf{a_{22}}`.\n    For each matrix, **inverse_2x2_block_diagonal** ouputs the vectors containing the\n    elements of each matrix' inverse; i.e.\n    :math:`\\mathbf{b_{11}}`, :math:`\\mathbf{b_{12}}`, :math:`\\mathbf{b_{21}}` and\n    :math:`\\mathbf{b_{22}}` where:\n\n    .. math::\n        A^{-1} = B = \\begin{bmatrix}\n        b_{11} & b_{12} \\\\\n        b_{21} & b_{22}\n        \\end{bmatrix}\n\n    For special applications, we may want to output the elements of the inverses\n    of the matricies as a 2x2 block matrix of the form:\n\n    .. math::\n        M = \\begin{bmatrix}\n        D_{11} & D_{12} \\\\\n        D_{21} & D_{22}\n        \\end{bmatrix}\n\n    where :math:`D_{ij}` are diagonal matrices whose non-zero elements\n    are defined by vector :math:`\\mathbf{b_{ij}}`. Where *n* is the\n    number of matricies, the block matrix is sparse with dimensions\n    (2n, 2n).\n\n    Examples\n    --------\n    Here, we define four 2x2 matricies and reorganize their elements into\n    4 vectors a11, a12, a21 and a22. We then examine the outputs of the\n    function **inverse_2x2_block_diagonal** when the argument\n    *return_matrix* is set to both *True* and *False*.\n\n    >>> from discretize.utils import inverse_2x2_block_diagonal\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n\n    Define four 3x3 matricies, and organize their elements into four vectors\n\n    >>> A1 = np.random.uniform(1, 10, (2, 2))\n    >>> A2 = np.random.uniform(1, 10, (2, 2))\n    >>> A3 = np.random.uniform(1, 10, (2, 2))\n    >>> A4 = np.random.uniform(1, 10, (2, 2))\n    >>> [[a11, a12], [a21, a22]] = np.stack([A1, A2, A3, A4], axis=-1)\n\n    Return the elements of their inverse and validate\n\n    >>> b11, b12, b21, b22 = inverse_2x2_block_diagonal(\n    ...     a11, a12, a21, a22, return_matrix=False\n    ... )\n    >>> Bs = np.stack([[b11, b12],[b21, b22]])\n    >>> B1, B2, B3, B4 = Bs.transpose((2, 0, 1))\n\n    >>> np.linalg.inv(A1)\n    array([[ 0.34507439, -0.4831833 ],\n           [-0.24286626,  0.57531461]])\n    >>> B1\n    array([[ 0.34507439, -0.4831833 ],\n           [-0.24286626,  0.57531461]])\n\n    Plot the sparse block matrix containing elements of the inverses\n\n    >>> M = inverse_2x2_block_diagonal(\n    ...     a11, a12, a21, a22\n    ... )\n    >>> plt.spy(M)\n    >>> plt.show()\n    \"\"\"\n    if \"returnMatrix\" in kwargs:\n        warnings.warn(\n            \"The returnMatrix keyword argument has been deprecated, please use return_matrix. \"\n            \"This will be removed in discretize 1.0.0\",\n            FutureWarning,\n            stacklevel=2,\n        )\n        return_matrix = kwargs[\"returnMatrix\"]\n\n    a11 = mkvc(a11)\n    a12 = mkvc(a12)\n    a21 = mkvc(a21)\n    a22 = mkvc(a22)\n\n    # compute inverse of the determinant.\n    detAinv = 1.0 / (a11 * a22 - a21 * a12)\n\n    b11 = +detAinv * a22\n    b12 = -detAinv * a12\n    b21 = -detAinv * a21\n    b22 = +detAinv * a11\n\n    if not return_matrix:\n        return b11, b12, b21, b22\n\n    return sp.vstack(\n        (sp.hstack((sdiag(b11), sdiag(b12))), sp.hstack((sdiag(b21), sdiag(b22))))\n    )\n\n\ndef invert_blocks(A):\n    \"\"\"Invert a set of 2x2 or 3x3 matricies.\n\n    This is a shortcut function that will only invert 2x2 and 3x3 matrices.\n    The function is broadcast over the last two dimensions of A.\n\n    Parameters\n    ----------\n    A : (..., N, N) numpy.ndarray\n        the block of matrices to invert, N must be either 2 or 3.\n\n    Returns\n    -------\n    (..., N, N) numpy.ndarray\n        the block of inverted matrices\n\n    See Also\n    --------\n    numpy.linalg.inv : Similar to this function, but is not specialized to 2x2 or 3x3\n    inverse_2x2_block_diagonal : use when each element of the blocks is separated\n    inverse_3x3_block_diagonal : use when each element of the blocks is separated\n\n    Examples\n    --------\n    >>> from discretize.utils import invert_blocks\n    >>> import numpy as np\n    >>> x = np.ones((1000, 3, 3))\n    >>> x[..., 1, 1] = 0\n    >>> x[..., 1, 2] = 0\n    >>> x[..., 2, 1] = 0\n    >>> As = np.einsum('...ij,...jk', x, x.transpose(0, 2, 1))\n    >>> Ainvs = invert_blocks(As)\n    >>> As[0] @ Ainvs[0]\n    array([[1., 0., 0.],\n           [0., 1., 0.],\n           [0., 0., 1.]])\n    \"\"\"\n    if A.shape[-1] != A.shape[-2]:\n        raise ValueError(f\"Last two dimensions are not equal, got {A.shape}\")\n\n    if A.shape[-1] == 2:\n        a11 = A[..., 0, 0]\n        a12 = A[..., 0, 1]\n        a21 = A[..., 1, 0]\n        a22 = A[..., 1, 1]\n\n        detA = a11 * a22 - a21 * a12\n        B = np.empty_like(A)\n        B[..., 0, 0] = a22 / detA\n        B[..., 0, 1] = -a12 / detA\n        B[..., 1, 0] = -a21 / detA\n        B[..., 1, 1] = a11 / detA\n\n    elif A.shape[-1] == 3:\n        a11 = A[..., 0, 0]\n        a12 = A[..., 0, 1]\n        a13 = A[..., 0, 2]\n        a21 = A[..., 1, 0]\n        a22 = A[..., 1, 1]\n        a23 = A[..., 1, 2]\n        a31 = A[..., 2, 0]\n        a32 = A[..., 2, 1]\n        a33 = A[..., 2, 2]\n\n        B = np.empty_like(A)\n        B[..., 0, 0] = a22 * a33 - a23 * a32\n        B[..., 0, 1] = a13 * a32 - a12 * a33\n        B[..., 0, 2] = a12 * a23 - a13 * a22\n\n        B[..., 1, 0] = a31 * a23 - a21 * a33\n        B[..., 1, 1] = a11 * a33 - a31 * a13\n        B[..., 1, 2] = a21 * a13 - a11 * a23\n\n        B[..., 2, 0] = a21 * a32 - a31 * a22\n        B[..., 2, 1] = a31 * a12 - a11 * a32\n        B[..., 2, 2] = a11 * a22 - a21 * a12\n\n        detA = a11 * B[..., 0, 0] + a21 * B[..., 0, 1] + a31 * B[..., 0, 2]\n        B /= detA[..., None, None]\n    else:\n        raise NotImplementedError(\"Only supports 2x2 and 3x3 blocks\")\n    return B\n\n\nclass TensorType(object):\n    r\"\"\"Class for determining property tensor type.\n\n    For a given *mesh*, the **TensorType** class examines the :class:`numpy.ndarray`\n    *tensor* to determine whether *tensor* defines a scalar, isotropic,\n    diagonal anisotropic or full tensor anisotropic constitutive relationship\n    for each cell on the mesh. The general theory behind this functionality\n    is explained below.\n\n    Parameters\n    ----------\n    mesh : discretize.base.BaseTensorMesh\n        An instance of any of the mesh classes support in discretize; i.e. *TensorMesh*,\n        *CylindricalMesh*, *TreeMesh* or *CurvilinearMesh*.\n    tensor : numpy.ndarray or a float\n        The shape of the input argument *tensor* must fall into one of these\n        classifications:\n\n        - *Scalar:* A float is entered.\n        - *Isotropic:* A 1D numpy.ndarray with a property value for every cell.\n        - *Anisotropic:* A (*nCell*, *dim*) numpy.ndarray of shape where each row\n          defines the diagonal-anisotropic property parameters for each cell.\n          *nParam* = 2 for 2D meshes and *nParam* = 3 for 3D meshes.\n        - *Tensor:* A (*nCell*, *nParam*) numpy.ndarray where each row\n          defines the full anisotropic property parameters for each cell.\n          *nParam* = 3 for 2D meshes and *nParam* = 6 for 3D meshes.\n\n    Notes\n    -----\n    The relationship between a quantity and its response to external\n    stimuli (e.g. Ohm's law) can be defined by a scalar quantity:\n\n    .. math::\n        \\vec{j} = \\sigma \\vec{e}\n\n    Or in the case of anisotropy, the relationship is defined generally by\n    a symmetric tensor:\n\n    .. math::\n        \\vec{j} = \\Sigma \\vec{e} \\;\\;\\; where \\;\\;\\;\n        \\Sigma = \\begin{bmatrix}\n        \\sigma_{xx} & \\sigma_{xy} & \\sigma_{xz} \\\\\n        \\sigma_{xy} & \\sigma_{yy} & \\sigma_{yz} \\\\\n        \\sigma_{xz} & \\sigma_{yz} & \\sigma_{zz}\n        \\end{bmatrix}\n\n    In 3D, the tensor is defined by 6 independent element (3 independent elements in\n    2D). When using the input argument *tensor* to define the consitutive relationship\n    for every cell in the *mesh*, there are 4 classifications recognized by discretize:\n\n    - **Scalar:** :math:`\\vec{j} = \\sigma \\vec{e}`, where :math:`\\sigma` a constant.\n      Thus the input argument *tensor* is a float.\n    - **Isotropic:** :math:`\\vec{j} = \\sigma \\vec{e}`, where :math:`\\sigma` varies\n      spatially. Thus the input argument *tensor* is a 1D array that provides a\n      :math:`\\sigma` value for every cell in the mesh.\n    - **Anisotropic:** :math:`\\vec{j} = \\Sigma \\vec{e}`, where the off-diagonal elements\n      are zero. That is, :math:`\\Sigma` is diagonal. In this case, the input argument\n      *tensor* defining the physical properties in each cell is a :class:`numpy.ndarray`\n      of shape (*nCells*, *dim*).\n    - **Tensor:** :math:`\\vec{j} = \\Sigma \\vec{e}`, where off-diagonal elements are\n      non-zero and :math:`\\Sigma` is a full tensor. In this case, the input argument\n      *tensor* defining the physical properties in each cell is a :class:`numpy.ndarray`\n      of shape (*nCells*, *nParam*). In 2D, *nParam* = 3 and in 3D, *nParam* = 6.\n    \"\"\"\n\n    def __init__(self, mesh, tensor):\n        if tensor is None:  # default is ones\n            self._tt = -1\n            self._tts = \"none\"\n        elif is_scalar(tensor):\n            self._tt = 0\n            self._tts = \"scalar\"\n        elif tensor.size == mesh.nC:\n            self._tt = 1\n            self._tts = \"isotropic\"\n        elif (mesh.dim == 2 and tensor.size == mesh.nC * 2) or (\n            mesh.dim == 3 and tensor.size == mesh.nC * 3\n        ):\n            self._tt = 2\n            self._tts = \"anisotropic\"\n        elif (mesh.dim == 2 and tensor.size == mesh.nC * 3) or (\n            mesh.dim == 3 and tensor.size == mesh.nC * 6\n        ):\n            self._tt = 3\n            self._tts = \"tensor\"\n        else:\n            raise Exception(\"Unexpected shape of tensor: {}\".format(tensor.shape))\n\n    def __str__(self):\n        \"\"\"Represent tensor type as a string.\"\"\"\n        return \"TensorType[{0:d}]: {1!s}\".format(self._tt, self._tts)\n\n    def __eq__(self, v):\n        \"\"\"Compare tensor type equal to a value.\"\"\"\n        return self._tt == v\n\n    def __le__(self, v):\n        \"\"\"Compare tensor type less than or equal to a value.\"\"\"\n        return self._tt <= v\n\n    def __ge__(self, v):\n        \"\"\"Compare tensor type greater than or equal to a value.\"\"\"\n        return self._tt >= v\n\n    def __lt__(self, v):\n        \"\"\"Compare tensor type less than a value.\"\"\"\n        return self._tt < v\n\n    def __gt__(self, v):\n        \"\"\"Compare tensor type greater than a value.\"\"\"\n        return self._tt > v\n\n\ndef make_property_tensor(mesh, tensor):\n    r\"\"\"Construct the physical property tensor.\n\n    For a given *mesh*, the input parameter *tensor* is a :class:`numpy.ndarray`\n    defining the constitutive relationship (e.g. Ohm's law) between two\n    discrete vector quantities :math:`\\boldsymbol{j}` and\n    :math:`\\boldsymbol{e}` living at cell centers. The function\n    **make_property_tensor** constructs the property tensor\n    :math:`\\boldsymbol{M}` for the entire mesh such that:\n\n    >>> j = M @ e\n\n    where the Cartesian components of the discrete vector for\n    are organized according to:\n\n    >>> e = np.r_[ex, ey, ez]\n    >>> j = np.r_[jx, jy, jz]\n\n    Parameters\n    ----------\n    mesh : discretize.base.BaseMesh\n       A mesh\n    tensor : numpy.ndarray or a float\n        - *Scalar:* A float is entered.\n        - *Isotropic:* A 1D numpy.ndarray with a property value for every cell.\n        - *Anisotropic:* A (*nCell*, *dim*) numpy.ndarray where each row\n          defines the diagonal-anisotropic property parameters for each cell.\n          *nParam* = 2 for 2D meshes and *nParam* = 3 for 3D meshes.\n        - *Tensor:* A (*nCell*, *nParam*) numpy.ndarray where each row defines\n          the full anisotropic property parameters for each cell. *nParam* = 3 for 2D\n          meshes and *nParam* = 6 for 3D meshes.\n\n    Returns\n    -------\n    (dim * n_cells, dim * n_cells) scipy.sparse.coo_matrix\n        The property tensor.\n\n    Notes\n    -----\n    The relationship between a quantity and its response to external\n    stimuli (e.g. Ohm's law) in each cell can be defined by a scalar\n    function :math:`\\sigma` in the isotropic case, or by a tensor\n    :math:`\\Sigma` in the anisotropic case, i.e.:\n\n    .. math::\n        \\vec{j} = \\sigma \\vec{e} \\;\\;\\;\\;\\;\\; \\textrm{or} \\;\\;\\;\\;\\;\\; \\vec{j} = \\Sigma \\vec{e}\n\n    where\n\n    .. math::\n        \\Sigma = \\begin{bmatrix}\n        \\sigma_{xx} & \\sigma_{xy} & \\sigma_{xz} \\\\\n        \\sigma_{xy} & \\sigma_{yy} & \\sigma_{yz} \\\\\n        \\sigma_{xz} & \\sigma_{yz} & \\sigma_{zz}\n        \\end{bmatrix}\n\n    Examples\n    --------\n    For the 4 classifications allowable (scalar, isotropic, anistropic and tensor),\n    we construct and compare the property tensor on a small 2D mesh. For this\n    example, note the following:\n\n        - The dimensions for all property tensors are the same\n        - All property tensors, except in the case of full anisotropy are diagonal\n          sparse matrices\n        - For the scalar case, the non-zero elements are equal\n        - For the isotropic case, the non-zero elements repreat in order for the x, y\n          (and z) components\n        - For the anisotropic case (diagonal anisotropy), the non-zero elements do not\n          repeat\n        - For the tensor caes (full anisotropy), there are off-diagonal components\n\n    >>> from discretize.utils import make_property_tensor\n    >>> from discretize import TensorMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> import matplotlib as mpl\n    >>> rng = np.random.default_rng(421)\n\n    Define a 2D tensor mesh\n\n    >>> h = [1., 1., 1.]\n    >>> mesh = TensorMesh([h, h], origin='00')\n\n    Define a physical property for all cases (2D)\n\n    >>> sigma_scalar = 4.\n    >>> sigma_isotropic = rng.integers(1, 10, mesh.nC)\n    >>> sigma_anisotropic = rng.integers(1, 10, (mesh.nC, 2))\n    >>> sigma_tensor = rng.integers(1, 10, (mesh.nC, 3))\n\n    Construct the property tensor in each case\n\n    >>> M_scalar = make_property_tensor(mesh, sigma_scalar)\n    >>> M_isotropic = make_property_tensor(mesh, sigma_isotropic)\n    >>> M_anisotropic = make_property_tensor(mesh, sigma_anisotropic)\n    >>> M_tensor = make_property_tensor(mesh, sigma_tensor)\n\n    Plot the property tensors.\n\n    >>> M_list = [M_scalar, M_isotropic, M_anisotropic, M_tensor]\n    >>> case_list = ['Scalar', 'Isotropic', 'Anisotropic', 'Full Tensor']\n    >>> ax1 = 4*[None]\n    >>> fig = plt.figure(figsize=(15, 4))\n    >>> for ii in range(0, 4):\n    ...     ax1[ii] = fig.add_axes([0.05+0.22*ii, 0.05, 0.18, 0.9])\n    ...     ax1[ii].imshow(\n    ...         M_list[ii].todense(), interpolation='none', cmap='binary', vmax=10.\n    ...     )\n    ...     ax1[ii].set_title(case_list[ii], fontsize=24)\n    >>> ax2 = fig.add_axes([0.92, 0.15, 0.01, 0.7])\n    >>> norm = mpl.colors.Normalize(vmin=0., vmax=10.)\n    >>> cbar = mpl.colorbar.ColorbarBase(\n    ...     ax2, norm=norm, orientation=\"vertical\", cmap=mpl.cm.binary\n    ... )\n    >>> plt.show()\n    \"\"\"\n    if tensor is None:  # default is ones\n        tensor = np.ones(mesh.nC)\n\n    if is_scalar(tensor):\n        tensor = tensor * np.ones(mesh.nC)\n\n    propType = TensorType(mesh, tensor)\n    if propType == 1:  # Isotropic!\n        Sigma = sp.kron(sp.identity(mesh.dim), sdiag(mkvc(tensor)))\n    elif propType == 2:  # Diagonal tensor\n        Sigma = sdiag(mkvc(tensor))\n    elif mesh.dim == 2 and tensor.size == mesh.nC * 3:  # Fully anisotropic, 2D\n        tensor = tensor.reshape((mesh.nC, 3), order=\"F\")\n        row1 = sp.hstack((sdiag(tensor[:, 0]), sdiag(tensor[:, 2])))\n        row2 = sp.hstack((sdiag(tensor[:, 2]), sdiag(tensor[:, 1])))\n        Sigma = sp.vstack((row1, row2))\n    elif mesh.dim == 3 and tensor.size == mesh.nC * 6:  # Fully anisotropic, 3D\n        tensor = tensor.reshape((mesh.nC, 6), order=\"F\")\n        row1 = sp.hstack(\n            (sdiag(tensor[:, 0]), sdiag(tensor[:, 3]), sdiag(tensor[:, 4]))\n        )\n        row2 = sp.hstack(\n            (sdiag(tensor[:, 3]), sdiag(tensor[:, 1]), sdiag(tensor[:, 5]))\n        )\n        row3 = sp.hstack(\n            (sdiag(tensor[:, 4]), sdiag(tensor[:, 5]), sdiag(tensor[:, 2]))\n        )\n        Sigma = sp.vstack((row1, row2, row3))\n    else:\n        raise Exception(\"Unexpected shape of tensor\")\n\n    return Sigma\n\n\ndef inverse_property_tensor(mesh, tensor, return_matrix=False, **kwargs):\n    r\"\"\"Construct the inverse of the physical property tensor.\n\n    For a given *mesh*, the input parameter *tensor* is a :class:`numpy.ndarray`\n    defining the constitutive relationship (e.g. Ohm's law) between two\n    discrete vector quantities :math:`\\boldsymbol{j}` and\n    :math:`\\boldsymbol{e}` living at cell centers. Where :math:`\\boldsymbol{M}`\n    is the physical property tensor, **inverse_property_tensor**\n    explicitly constructs the inverse of the physical\n    property tensor :math:`\\boldsymbol{M^{-1}}` for all cells such that:\n\n    >>> e = Mi @ j\n\n    where the Cartesian components of the discrete vectors are\n    organized according to:\n\n    >>> j = np.r_[jx, jy, jz]\n    >>> e = np.r_[ex, ey, ez]\n\n    Parameters\n    ----------\n    mesh : discretize.base.BaseMesh\n       A mesh\n    tensor : numpy.ndarray or float\n        - *Scalar:* A float is entered.\n        - *Isotropic:* A 1D numpy.ndarray with a property value for every cell.\n        - *Anisotropic:* A (*nCell*, *dim*) numpy.ndarray where each row\n          defines the diagonal-anisotropic property parameters for each cell.\n          *nParam* = 2 for 2D meshes and *nParam* = 3 for 3D meshes.\n        - *Tensor:* A (*nCell*, *nParam*) numpy.ndarray where each row defines\n          the full anisotropic property parameters for each cell. *nParam* = 3 for 2D\n          meshes and *nParam* = 6 for 3D meshes.\n\n    return_matrix : bool, optional\n        - *True:* the function returns the inverse of the property tensor.\n        - *False:* the function returns the non-zero elements of the inverse of the\n          property tensor in a numpy.ndarray in the same order as the input argument\n          *tensor*.\n\n    Returns\n    -------\n    numpy.ndarray or scipy.sparse.coo_matrix\n        - If *return_matrix* = *False*, the function outputs the parameters defining the\n          inverse of the property tensor in a numpy.ndarray with the same dimensions as\n          the input argument *tensor*\n        - If *return_natrix* = *True*, the function outputs the inverse of the property\n          tensor as a *scipy.sparse.coo_matrix*.\n\n    Notes\n    -----\n    The relationship between a quantity and its response to external\n    stimuli (e.g. Ohm's law) in each cell can be defined by a scalar\n    function :math:`\\sigma` in the isotropic case, or by a tensor\n    :math:`\\Sigma` in the anisotropic case, i.e.:\n\n    .. math::\n        \\vec{j} = \\sigma \\vec{e} \\;\\;\\;\\;\\;\\; \\textrm{or} \\;\\;\\;\\;\\;\\;\n        \\vec{j} = \\Sigma \\vec{e}\n\n    where\n\n    .. math::\n        \\Sigma = \\begin{bmatrix}\n        \\sigma_{xx} & \\sigma_{xy} & \\sigma_{xz} \\\\\n        \\sigma_{xy} & \\sigma_{yy} & \\sigma_{yz} \\\\\n        \\sigma_{xz} & \\sigma_{yz} & \\sigma_{zz}\n        \\end{bmatrix}\n\n    Examples\n    --------\n    For the 4 classifications allowable (scalar, isotropic, anistropic and tensor),\n    we construct the property tensor on a small 2D mesh. We then construct the\n    inverse of the property tensor and compare.\n\n    >>> from discretize.utils import make_property_tensor, inverse_property_tensor\n    >>> from discretize import TensorMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> import matplotlib as mpl\n    >>> rng = np.random.default_rng(421)\n\n    Define a 2D tensor mesh\n\n    >>> h = [1., 1., 1.]\n    >>> mesh = TensorMesh([h, h], origin='00')\n\n    Define a physical property for all cases (2D)\n\n    >>> sigma_scalar = 4.\n    >>> sigma_isotropic = rng.integers(1, 10, mesh.nC)\n    >>> sigma_anisotropic = rng.integers(1, 10, (mesh.nC, 2))\n    >>> sigma_tensor = rng.integers(1, 10, (mesh.nC, 3))\n\n    Construct the property tensor in each case\n\n    >>> M_scalar = make_property_tensor(mesh, sigma_scalar)\n    >>> M_isotropic = make_property_tensor(mesh, sigma_isotropic)\n    >>> M_anisotropic = make_property_tensor(mesh, sigma_anisotropic)\n    >>> M_tensor = make_property_tensor(mesh, sigma_tensor)\n\n    Construct the inverse property tensor in each case\n\n    >>> Minv_scalar = inverse_property_tensor(mesh, sigma_scalar, return_matrix=True)\n    >>> Minv_isotropic = inverse_property_tensor(mesh, sigma_isotropic, return_matrix=True)\n    >>> Minv_anisotropic = inverse_property_tensor(mesh, sigma_anisotropic, return_matrix=True)\n    >>> Minv_tensor = inverse_property_tensor(mesh, sigma_tensor, return_matrix=True)\n\n    Plot the property tensors.\n\n    >>> M_list = [M_scalar, M_isotropic, M_anisotropic, M_tensor]\n    >>> Minv_list = [Minv_scalar, Minv_isotropic, Minv_anisotropic, Minv_tensor]\n    >>> case_list = ['Scalar', 'Isotropic', 'Anisotropic', 'Full Tensor']\n    >>> fig1 = plt.figure(figsize=(15, 4))\n    >>> ax1 = 4*[None]\n    >>> for ii in range(0, 4):\n    ...     ax1[ii] = fig1.add_axes([0.05+0.22*ii, 0.05, 0.18, 0.9])\n    ...     ax1[ii].imshow(\n    ...         M_list[ii].todense(), interpolation='none', cmap='binary', vmax=10.\n    ...     )\n    ...     ax1[ii].set_title('$M$ (' + case_list[ii] + ')', fontsize=24)\n    >>> cax1 = fig1.add_axes([0.92, 0.15, 0.01, 0.7])\n    >>> norm1 = mpl.colors.Normalize(vmin=0., vmax=10.)\n    >>> cbar1 = mpl.colorbar.ColorbarBase(\n    ...     cax1, norm=norm1, orientation=\"vertical\", cmap=mpl.cm.binary\n    ... )\n    >>> plt.show()\n\n    Plot the inverse property tensors.\n\n    >>> fig2 = plt.figure(figsize=(15, 4))\n    >>> ax2 = 4*[None]\n    >>> for ii in range(0, 4):\n    ...     ax2[ii] = fig2.add_axes([0.05+0.22*ii, 0.05, 0.18, 0.9])\n    ...     ax2[ii].imshow(\n    ...         Minv_list[ii].todense(), interpolation='none', cmap='binary', vmax=1.\n    ...     )\n    ...     ax2[ii].set_title('$M^{-1}$ (' + case_list[ii] + ')', fontsize=24)\n    >>> cax2 = fig2.add_axes([0.92, 0.15, 0.01, 0.7])\n    >>> norm2 = mpl.colors.Normalize(vmin=0., vmax=1.)\n    >>> cbar2 = mpl.colorbar.ColorbarBase(\n    ...     cax2, norm=norm2, orientation=\"vertical\", cmap=mpl.cm.binary\n    ... )\n    >>> plt.show()\n    \"\"\"\n    if \"returnMatrix\" in kwargs:\n        warnings.warn(\n            \"The returnMatrix keyword argument has been deprecated, please use return_matrix. \"\n            \"This will be removed in discretize 1.0.0\",\n            FutureWarning,\n            stacklevel=2,\n        )\n        return_matrix = kwargs[\"returnMatrix\"]\n\n    propType = TensorType(mesh, tensor)\n\n    if is_scalar(tensor):\n        T = 1.0 / tensor\n    elif propType < 3:  # Isotropic or Diagonal\n        T = 1.0 / mkvc(tensor)  # ensure it is a vector.\n    elif mesh.dim == 2 and tensor.size == mesh.nC * 3:  # Fully anisotropic, 2D\n        tensor = tensor.reshape((mesh.nC, 3), order=\"F\")\n        B = inverse_2x2_block_diagonal(\n            tensor[:, 0], tensor[:, 2], tensor[:, 2], tensor[:, 1], return_matrix=False\n        )\n        b11, b12, b21, b22 = B\n        T = np.r_[b11, b22, b12]\n    elif mesh.dim == 3 and tensor.size == mesh.nC * 6:  # Fully anisotropic, 3D\n        tensor = tensor.reshape((mesh.nC, 6), order=\"F\")\n        B = inverse_3x3_block_diagonal(\n            tensor[:, 0],\n            tensor[:, 3],\n            tensor[:, 4],\n            tensor[:, 3],\n            tensor[:, 1],\n            tensor[:, 5],\n            tensor[:, 4],\n            tensor[:, 5],\n            tensor[:, 2],\n            return_matrix=False,\n        )\n        b11, b12, b13, b21, b22, b23, b31, b32, b33 = B\n        T = np.r_[b11, b22, b33, b12, b13, b23]\n    else:\n        raise Exception(\"Unexpected shape of tensor\")\n\n    if return_matrix:\n        return make_property_tensor(mesh, T)\n\n    return T\n\n\ndef cross2d(x, y):\n    \"\"\"Compute the cross product of two vectors.\n\n    This function will calculate the cross product as if\n    the third component of each of these vectors was zero.\n\n    The returned direction is perpendicular to both inputs,\n    making it be solely in the third dimension.\n\n    Parameters\n    ----------\n    x, y : array_like\n        The vectors for the cross product.\n\n    Returns\n    -------\n    x_cross_y : numpy.ndarray\n        The cross product of x and y.\n    \"\"\"\n    x = np.asarray(x)\n    y = np.asarray(y)\n    # np.cross(x, y) is deprecated for 2D input\n    return x[..., 0] * y[..., 1] - x[..., 1] * y[..., 0]\n\n\nclass Zero(object):\n    \"\"\"Carries out arithmetic operations between 0 and arbitrary quantities.\n\n    This class was designed to manage basic arithmetic operations between\n    0 and :class:`numpy.ndarray` of any shape. It is a short circuiting evaluation that\n    will return the expected values.\n\n    Examples\n    --------\n    >>> import numpy as np\n    >>> from discretize.utils import Zero\n    >>> Z = Zero()\n    >>> Z\n    Zero\n    >>> x = np.arange(5)\n    >>> x + Z\n    array([0, 1, 2, 3, 4])\n    >>> Z - x\n    array([ 0, -1, -2, -3, -4])\n    >>> Z * x\n    Zero\n    >>> Z @ x\n    Zero\n    >>> Z[0]\n    Zero\n    \"\"\"\n\n    __numpy_ufunc__ = True\n    __array_ufunc__ = None\n\n    def __repr__(self):\n        \"\"\"Represent zeros a string.\"\"\"\n        return \"Zero\"\n\n    def __bool__(self):\n        \"\"\"Return False for zero matrix.\"\"\"\n        return False\n\n    def __add__(self, v):\n        \"\"\"Add a value to zero.\"\"\"\n        return v\n\n    def __radd__(self, v):\n        \"\"\"Add zero to a value.\"\"\"\n        return v\n\n    def __iadd__(self, v):\n        \"\"\"Add zero to a value inplace.\"\"\"\n        return v\n\n    def __sub__(self, v):\n        \"\"\"Subtract a value from zero.\"\"\"\n        return -v\n\n    def __rsub__(self, v):\n        \"\"\"Subtract zero from a value.\"\"\"\n        return v\n\n    def __isub__(self, v):\n        \"\"\"Subtract zero from a value inplace.\"\"\"\n        return v\n\n    def __mul__(self, v):\n        \"\"\"Multiply zero by a value.\"\"\"\n        return self\n\n    def __rmul__(self, v):\n        \"\"\"Multiply a value by zero.\"\"\"\n        return self\n\n    def __matmul__(self, v):\n        \"\"\"Multiply zero by a matrix.\"\"\"\n        return self\n\n    def __rmatmul__(self, v):\n        \"\"\"Multiply a matrix by zero.\"\"\"\n        return self\n\n    def __div__(self, v):\n        \"\"\"Divide zero by a value.\"\"\"\n        return self\n\n    def __truediv__(self, v):\n        \"\"\"Divide zero by a value.\"\"\"\n        return self\n\n    def __rdiv__(self, v):\n        \"\"\"Try to divide a value by zero.\"\"\"\n        raise ZeroDivisionError(\"Cannot divide by zero.\")\n\n    def __rtruediv__(self, v):\n        \"\"\"Try to divide a value by zero.\"\"\"\n        raise ZeroDivisionError(\"Cannot divide by zero.\")\n\n    def __rfloordiv__(self, v):\n        \"\"\"Try to divide a value by zero.\"\"\"\n        raise ZeroDivisionError(\"Cannot divide by zero.\")\n\n    def __pos__(self):\n        \"\"\"Return zero.\"\"\"\n        return self\n\n    def __neg__(self):\n        \"\"\"Negate zero.\"\"\"\n        return self\n\n    def __lt__(self, v):\n        \"\"\"Compare less than zero.\"\"\"\n        return 0 < v\n\n    def __le__(self, v):\n        \"\"\"Compare less than or equal to zero.\"\"\"\n        return 0 <= v\n\n    def __eq__(self, v):\n        \"\"\"Compare equal to zero.\"\"\"\n        return v == 0\n\n    def __ne__(self, v):\n        \"\"\"Compare not equal to zero.\"\"\"\n        return not (0 == v)\n\n    def __ge__(self, v):\n        \"\"\"Compare greater than or equal to zero.\"\"\"\n        return 0 >= v\n\n    def __gt__(self, v):\n        \"\"\"Compare greater than zero.\"\"\"\n        return 0 > v\n\n    def transpose(self):\n        \"\"\"Return the transpose of the *Zero* class, i.e. itself.\"\"\"\n        return self\n\n    def __getitem__(self, key):\n        \"\"\"Get an element of the *Zero* class, i.e. itself.\"\"\"\n        return self\n\n    @property\n    def ndim(self):\n        \"\"\"Return the dimension of *Zero* class, i.e. *None*.\"\"\"\n        return None\n\n    @property\n    def shape(self):\n        \"\"\"Return the shape *Zero* class, i.e. *None*.\"\"\"\n        return _inftup(None)\n\n    @property\n    def T(self):\n        \"\"\"Return the *Zero* class as an operator.\"\"\"\n        return self\n\n\nclass Identity(object):\n    \"\"\"Carries out arithmetic operations involving the identity.\n\n    This class was designed to manage basic arithmetic operations between the identity\n    matrix and :class:`numpy.ndarray` of any shape. It is a short circuiting evaluation\n    that will return the expected values.\n\n    Parameters\n    ----------\n    positive : bool, optional\n        Whether it is a positive (or negative) Identity matrix\n\n    Examples\n    --------\n    >>> import numpy as np\n    >>> from discretize.utils import Identity, Zero\n    >>> Z = Zero()\n    >>> I = Identity()\n    >>> x = np.arange(5)\n    >>> x + I\n    array([1, 2, 3, 4, 5])\n    >>> I - x\n    array([ 1, 0, -1, -2, -3])\n    >>> I * x\n    array([0, 1, 2, 3, 4])\n    >>> I @ x\n    array([0, 1, 2, 3, 4])\n    >>> I @ Z\n    Zero\n    \"\"\"\n\n    __numpy_ufunc__ = True\n    __array_ufunc__ = None\n\n    _positive = True\n\n    def __init__(self, positive=True):\n        self._positive = positive\n\n    def __repr__(self):\n        \"\"\"Represent 1 (or -1 if not positive).\"\"\"\n        if self._positive:\n            return \"I\"\n        else:\n            return \"-I\"\n\n    def __bool__(self):\n        \"\"\"Return True for identity matrix.\"\"\"\n        return True\n\n    def __pos__(self):\n        \"\"\"Return positive 1 (or -1 if not positive).\"\"\"\n        return self\n\n    def __neg__(self):\n        \"\"\"Negate 1 (or -1 if not positive).\"\"\"\n        return Identity(not self._positive)\n\n    def __add__(self, v):\n        \"\"\"Add 1 (or -1 if not positive) to a value.\"\"\"\n        if sp.issparse(v):\n            return v + speye(v.shape[0]) if self._positive else v - speye(v.shape[0])\n        return v + 1 if self._positive else v - 1\n\n    def __radd__(self, v):\n        \"\"\"Add 1 (or -1 if not positive) to a value.\"\"\"\n        return self.__add__(v)\n\n    def __sub__(self, v):\n        \"\"\"Subtract a value from 1 (or -1 if not positive).\"\"\"\n        return self + -v\n\n    def __rsub__(self, v):\n        \"\"\"Subtract 1 (or -1 if not positive) from a value.\"\"\"\n        return -self + v\n\n    def __mul__(self, v):\n        \"\"\"Multiply 1 (or -1 if not positive) by a value.\"\"\"\n        return v if self._positive else -v\n\n    def __rmul__(self, v):\n        \"\"\"Multiply 1 (or -1 if not positive) by a value.\"\"\"\n        return v if self._positive else -v\n\n    def __matmul__(self, v):\n        \"\"\"Multiply 1 (or -1 if not positive) by a matrix.\"\"\"\n        return v if self._positive else -v\n\n    def __rmatmul__(self, v):\n        \"\"\"Multiply a matrix by 1 (or -1 if not positive).\"\"\"\n        return v if self._positive else -v\n\n    def __div__(self, v):\n        \"\"\"Divide 1 (or -1 if not positive) by a value.\"\"\"\n        if sp.issparse(v):\n            raise NotImplementedError(\"Sparse arrays not divisibile.\")\n        return 1 / v if self._positive else -1 / v\n\n    def __truediv__(self, v):\n        \"\"\"Divide 1 (or -1 if not positive) by a value.\"\"\"\n        if sp.issparse(v):\n            raise NotImplementedError(\"Sparse arrays not divisibile.\")\n        return 1.0 / v if self._positive else -1.0 / v\n\n    def __rdiv__(self, v):\n        \"\"\"Divide a value by 1 (or -1 if not positive).\"\"\"\n        return v if self._positive else -v\n\n    def __rtruediv__(self, v):\n        \"\"\"Divide a value by 1 (or -1 if not positive).\"\"\"\n        return v if self._positive else -v\n\n    def __floordiv__(self, v):\n        \"\"\"Flooring division of 1 (or -1 if not positive) by a value.\"\"\"\n        return 1 // v if self._positive else -1 // v\n\n    def __rfloordiv__(self, v):\n        \"\"\"Flooring division of a value by 1 (or -1 if not positive).\"\"\"\n        return v // 1 if self._positive else v // -1\n\n    def __lt__(self, v):\n        \"\"\"Compare less than 1 (or -1 if not positive).\"\"\"\n        return 1 < v if self._positive else -1 < v\n\n    def __le__(self, v):\n        \"\"\"Compare less than or equal to 1 (or -1 if not positive).\"\"\"\n        return 1 <= v if self._positive else -1 <= v\n\n    def __eq__(self, v):\n        \"\"\"Compare equal to 1 (or -1 if not positive).\"\"\"\n        return v == 1 if self._positive else v == -1\n\n    def __ne__(self, v):\n        \"\"\"Compare not equal to 1 (or -1 if not positive).\"\"\"\n        return (not (1 == v)) if self._positive else (not (-1 == v))\n\n    def __ge__(self, v):\n        \"\"\"Compare greater than or equal to 1 (or -1 if not positive).\"\"\"\n        return 1 >= v if self._positive else -1 >= v\n\n    def __gt__(self, v):\n        \"\"\"Compare greater than 1 (or -1 if not positive).\"\"\"\n        return 1 > v if self._positive else -1 > v\n\n    @property\n    def ndim(self):\n        \"\"\"Return the dimension of *Identity* class, i.e. *None*.\"\"\"\n        return None\n\n    @property\n    def shape(self):\n        \"\"\"Return the shape of *Identity* class, i.e. *None*.\"\"\"\n        return _inftup(None)\n\n    @property\n    def T(self):\n        \"\"\"Return the *Identity* class as an operator.\"\"\"\n        return self\n\n    def transpose(self):\n        \"\"\"Return the transpose of the *Identity* class, i.e. itself.\"\"\"\n        return self\n\n\nclass _inftup(tuple):\n    \"\"\"An infinitely long tuple of a value repeated infinitely.\"\"\"\n\n    def __init__(self, val=None):\n        self._val = val\n\n    def __getitem__(self, key):\n        if isinstance(key, slice):\n            return _inftup(self._val)\n        return self._val\n\n    def __len__(self):\n        return 0\n\n    def __repr__(self):\n        return f\"({self._val}, {self._val}, ...)\"\n\n\n################################################\n#             DEPRECATED FUNCTIONS\n################################################\n\nsdInv = deprecate_function(sdinv, \"sdInv\", removal_version=\"1.0.0\", error=True)\n\ngetSubArray = deprecate_function(\n    get_subarray, \"getSubArray\", removal_version=\"1.0.0\", error=True\n)\n\ninv3X3BlockDiagonal = deprecate_function(\n    inverse_3x3_block_diagonal,\n    \"inv3X3BlockDiagonal\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\n\ninv2X2BlockDiagonal = deprecate_function(\n    inverse_2x2_block_diagonal,\n    \"inv2X2BlockDiagonal\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\n\nmakePropertyTensor = deprecate_function(\n    make_property_tensor,\n    \"makePropertyTensor\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\n\ninvPropertyTensor = deprecate_function(\n    inverse_property_tensor,\n    \"invPropertyTensor\",\n    removal_version=\"1.0.0\",\n    error=True,\n)\n"
  },
  {
    "path": "discretize/utils/matutils.py",
    "content": "from discretize.utils.matrix_utils import *  # NOQA F401,F403\n\nraise ImportError(\n    \"Importing from discretize.matutils is deprecated behavoir. Please import \"\n    \"from discretize.utils. This message will be removed in version 1.0.0 of discretize.\",\n)\n"
  },
  {
    "path": "discretize/utils/mesh_utils.py",
    "content": "\"\"\"Useful tools for working with meshes.\"\"\"\n\nimport numpy as np\nimport scipy.ndimage as ndi\nimport scipy.sparse as sp\n\nfrom discretize.utils.code_utils import is_scalar\nfrom scipy.spatial import cKDTree, Delaunay\nfrom scipy import interpolate\nimport discretize\nfrom discretize.utils.code_utils import deprecate_function\nimport warnings\n\nnum_types = [int, float]\n\n\ndef random_model(\n    shape, random_seed=None, anisotropy=None, its=100, bounds=None, seed=None\n):\n    \"\"\"Create random tensor model.\n\n    Creates a random tensor model by convolving a kernel function with a\n    uniformly distributed model. The user specifies the number of cells\n    along the x, (y and z) directions with the input argument *shape* and\n    the function outputs a tensor model with the same shape. Afterwards,\n    the user may use the :py:func:`~discretize.utils.mkvc` function\n    to convert the tensor to a vector which can be plotting on a\n    corresponding tensor mesh.\n\n    Parameters\n    ----------\n    shape : (dim) tuple of int\n        shape of the model.\n    random_seed : numpy.random.Generator, int, optional\n        pick which model to produce, prints the seed if you don't choose\n    anisotropy : numpy.ndarray, optional\n        this is the kernel that is convolved with the model\n    its : int, optional\n        number of smoothing iterations\n    bounds : list, optional\n        Lower and upper bounds on the model. Has the form [lower_bound, upper_bound].\n\n    Returns\n    -------\n    numpy.ndarray\n        A random generated model whose shape was specified by the input parameter *shape*\n\n    Examples\n    --------\n    Here, we generate a random model for a 2D tensor mesh and plot.\n\n    >>> from discretize import TensorMesh\n    >>> from discretize.utils import random_model, mkvc\n    >>> import matplotlib as mpl\n    >>> import matplotlib.pyplot as plt\n\n    >>> h = [(1., 50)]\n    >>> vmin, vmax = 0., 1.\n    >>> mesh = TensorMesh([h, h])\n\n    >>> model = random_model(mesh.shape_cells, random_seed=4, bounds=[vmin, vmax])\n\n    >>> fig = plt.figure(figsize=(5, 4))\n    >>> ax = plt.subplot(111)\n    >>> im, = mesh.plot_image(model, grid=False, ax=ax, clim=[vmin, vmax])\n    >>> cbar = plt.colorbar(im)\n    >>> ax.set_title('Random Tensor Model')\n    >>> plt.show()\n    \"\"\"\n    if bounds is None:\n        bounds = [0, 1]\n\n    if seed is not None:\n        warnings.warn(\n            \"Deprecated in version 0.11.0. The `seed` keyword argument has been renamed to `random_seed` \"\n            \"for consistency across the package. Please update your code to use the new keyword argument.\",\n            FutureWarning,\n            stacklevel=2,\n        )\n        random_seed = seed\n\n    rng = np.random.default_rng(random_seed)\n    if random_seed is None:\n        print(\"Using a seed of: \", rng.bit_generator.seed_seq)\n\n    if type(shape) in num_types:\n        shape = (shape,)  # make it a tuple for consistency\n\n    mr = rng.random(shape)\n    if anisotropy is None:\n        if len(shape) == 1:\n            smth = np.array([1, 10.0, 1], dtype=float)\n        elif len(shape) == 2:\n            smth = np.array([[1, 7, 1], [2, 10, 2], [1, 7, 1]], dtype=float)\n        elif len(shape) == 3:\n            kernal = np.array([1, 4, 1], dtype=float).reshape((1, 3))\n            smth = np.array(\n                sp.kron(sp.kron(kernal, kernal.T).todense()[:], kernal).todense()\n            ).reshape((3, 3, 3))\n    else:\n        if len(anisotropy.shape) != len(shape):\n            raise ValueError(\"Anisotropy must be the same shape.\")\n        smth = np.array(anisotropy, dtype=float)\n\n    smth = smth / smth.sum()  # normalize\n    mi = mr\n    for _i in range(its):\n        mi = ndi.convolve(mi, smth)\n\n    # scale the model to live between the bounds.\n    mi = (mi - mi.min()) / (mi.max() - mi.min())  # scaled between 0 and 1\n    mi = mi * (bounds[1] - bounds[0]) + bounds[0]\n\n    return mi\n\n\ndef unpack_widths(value):\n    \"\"\"Unpack a condensed representation of cell widths or time steps.\n\n    For a list of numbers, if the same value is repeat or expanded by a constant\n    factor, it may be represented in a condensed form using list of floats\n    and/or tuples. **unpack_widths** takes a list of floats and/or tuples in\n    condensed form, e.g.:\n\n        [ float, (cellSize, numCell), (cellSize, numCell, factor) ]\n\n    and expands the representation to a list containing all widths in order. That is:\n\n        [ w1, w2, w3, ..., wn ]\n\n    Parameters\n    ----------\n    value : list of float and/or tuple\n        The list of floats and/or tuples that are to be unpacked\n\n    Returns\n    -------\n    numpy.ndarray\n        The unpacked list with all widths in order\n\n    Examples\n    --------\n    Time stepping for time-domain codes can be represented in condensed form, e.g.:\n\n    >>> from discretize.utils import unpack_widths\n    >>> dt = [ (1e-5, 10), (1e-4, 4), 1e-3 ]\n\n    The above means to take 10 steps at a step width of 1e-5 s and then\n    4 more at 1e-4 s, and then one step of 1e-3 s. When unpacked, the output is\n    of length 15 and is given by:\n\n    >>> unpack_widths(dt)\n    array([1.e-05, 1.e-05, 1.e-05, 1.e-05, 1.e-05, 1.e-05, 1.e-05, 1.e-05,\n           1.e-05, 1.e-05, 1.e-04, 1.e-04, 1.e-04, 1.e-04, 1.e-03])\n\n    Each axis of a tensor mesh can also be defined as a condensed list of floats\n    and/or tuples. When a third number is defined in any tuple, the width value\n    is successively expanded by that factor, e.g.:\n\n    >>> dt = [ 6., 8., (10.0, 3), (8.0, 4, 2.) ]\n    >>> unpack_widths(dt)\n    array([  6.,   8.,  10.,  10.,  10.,  16.,  32.,  64., 128.])\n    \"\"\"\n    if type(value) is not list:\n        raise Exception(\"unpack_widths must be a list of scalars and tuples.\")\n\n    proposed = []\n    for v in value:\n        if is_scalar(v):\n            proposed += [float(v)]\n        elif type(v) is tuple and len(v) == 2:\n            proposed += [float(v[0])] * int(v[1])\n        elif type(v) is tuple and len(v) == 3:\n            start = float(v[0])\n            num = int(v[1])\n            factor = float(v[2])\n            pad = ((np.ones(num) * np.abs(factor)) ** (np.arange(num) + 1)) * start\n            if factor < 0:\n                pad = pad[::-1]\n            proposed += pad.tolist()\n        else:\n            raise Exception(\n                \"unpack_widths must contain only scalars and len(2) or len(3) tuples.\"\n            )\n\n    return np.array(proposed)\n\n\ndef closest_points_index(mesh, pts, grid_loc=\"CC\", **kwargs):\n    \"\"\"Find the indicies for the nearest grid location for a set of points.\n\n    Parameters\n    ----------\n    mesh : discretize.base.BaseMesh\n        An instance of *discretize.base.BaseMesh*\n    pts : (n, dim) numpy.ndarray\n        Points to query.\n    grid_loc : {'CC', 'N', 'Fx', 'Fy', 'Fz', 'Ex', 'Ex', 'Ey', 'Ez'}\n        Specifies the grid on which points are being moved to.\n\n    Returns\n    -------\n    (n ) numpy.ndarray of int\n        Vector of length *n* containing the indicies for the closest\n        respective cell center, node, face or edge.\n\n    Examples\n    --------\n    Here we define a set of random (x, y) locations and find the closest\n    cell centers and nodes on a mesh.\n\n    >>> from discretize import TensorMesh\n    >>> from discretize.utils import closest_points_index\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> h = 2*np.ones(5)\n    >>> mesh = TensorMesh([h, h], x0='00')\n\n    Define some random locations, grid cell centers and grid nodes,\n\n    >>> xy_random = np.random.uniform(0, 10, size=(4,2))\n    >>> xy_centers = mesh.cell_centers\n    >>> xy_nodes = mesh.nodes\n\n    Find indicies of closest cell centers and nodes,\n\n    >>> ind_centers = closest_points_index(mesh, xy_random, 'CC')\n    >>> ind_nodes = closest_points_index(mesh, xy_random, 'N')\n\n    Plot closest cell centers and nodes\n\n    >>> fig = plt.figure(figsize=(5, 5))\n    >>> ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])\n    >>> mesh.plot_grid(ax=ax)\n    >>> ax.scatter(xy_random[:, 0], xy_random[:, 1], 50, 'k')\n    >>> ax.scatter(xy_centers[ind_centers, 0], xy_centers[ind_centers, 1], 50, 'r')\n    >>> ax.scatter(xy_nodes[ind_nodes, 0], xy_nodes[ind_nodes, 1], 50, 'b')\n    >>> plt.show()\n    \"\"\"\n    if \"gridLoc\" in kwargs:\n        raise TypeError(\n            \"The gridLoc keyword argument has been removed, please use grid_loc. \"\n            \"This message will be removed in discretize 1.0.0\",\n        )\n    warnings.warn(\n        \"The closest_points_index utilty function has been moved to be a method of \"\n        \"a class object. Please access it as mesh.closest_points_index(). This will \"\n        \"be removed in a future version of discretize\",\n        DeprecationWarning,\n        stacklevel=2,\n    )\n    return mesh.closest_points_index(pts, grid_loc=grid_loc, discard=True)\n\n\ndef extract_core_mesh(xyzlim, mesh, mesh_type=\"tensor\"):\n    \"\"\"Extract the core mesh from a global mesh.\n\n    Parameters\n    ----------\n    xyzlim : (dim, 2) numpy.ndarray\n        2D array defining the x, y and z cutoffs for the core mesh region. Each\n        row contains the minimum and maximum limit for the x, y and z axis,\n        respectively.\n    mesh : discretize.TensorMesh\n        The mesh\n    mesh_type : str, optional\n        Unused currently\n\n    Returns\n    -------\n    tuple: (**active_index**, **core_mesh**)\n        **active_index** is a boolean array that maps from the global the mesh\n        to core mesh. **core_mesh** is a *discretize.base.BaseMesh* object representing\n        the core mesh.\n\n    Examples\n    --------\n    Here, we define a 2D tensor mesh that has both a core region and padding.\n    We use the function **extract_core_mesh** to return a mesh which contains\n    only the core region.\n\n    >>> from discretize.utils import extract_core_mesh\n    >>> from discretize import TensorMesh\n    >>> import numpy as np\n    >>> import matplotlib.pyplot as plt\n    >>> import matplotlib as mpl\n    >>> mpl.rcParams.update({\"font.size\": 14})\n\n    Form a mesh of a uniform cube\n\n    >>> h = [(1., 5, -1.5), (1., 20), (1., 5, 1.5)]\n    >>> mesh = TensorMesh([h, h], origin='CC')\n\n    Plot original mesh\n\n    >>> fig = plt.figure(figsize=(7, 7))\n    >>> ax = fig.add_subplot(111)\n    >>> mesh.plot_grid(ax=ax)\n    >>> ax.set_title('Original Tensor Mesh')\n    >>> plt.show()\n\n    Set the limits for the cutoff of the core mesh (dim, 2)\n\n    >>> xlim = np.c_[-10., 10]\n    >>> ylim = np.c_[-10., 10]\n    >>> core_limits = np.r_[xlim, ylim]\n\n    Extract indices of core mesh cells and the core mesh, then plot\n\n    >>> core_ind, core_mesh = extract_core_mesh(core_limits, mesh)\n    >>> fig = plt.figure(figsize=(4, 4))\n    >>> ax = fig.add_subplot(111)\n    >>> core_mesh.plot_grid(ax=ax)\n    >>> ax.set_title('Core Mesh')\n    >>> plt.show()\n    \"\"\"\n    if not isinstance(mesh, discretize.TensorMesh):\n        raise Exception(\"Only implemented for class TensorMesh\")\n\n    if mesh.dim == 1:\n        xyzlim = xyzlim.flatten()\n        xmin, xmax = xyzlim[0], xyzlim[1]\n\n        xind = np.logical_and(mesh.cell_centers_x > xmin, mesh.cell_centers_x < xmax)\n\n        xc = mesh.cell_centers_x[xind]\n\n        hx = mesh.h[0][xind]\n\n        origin = [xc[0] - hx[0] * 0.5]\n\n        meshCore = discretize.TensorMesh([hx], origin=origin)\n\n        actind = (mesh.cell_centers > xmin) & (mesh.cell_centers < xmax)\n\n    elif mesh.dim == 2:\n        xmin, xmax = xyzlim[0, 0], xyzlim[0, 1]\n        ymin, ymax = xyzlim[1, 0], xyzlim[1, 1]\n\n        xind = np.logical_and(mesh.cell_centers_x > xmin, mesh.cell_centers_x < xmax)\n        yind = np.logical_and(mesh.cell_centers_y > ymin, mesh.cell_centers_y < ymax)\n\n        xc = mesh.cell_centers_x[xind]\n        yc = mesh.cell_centers_y[yind]\n\n        hx = mesh.h[0][xind]\n        hy = mesh.h[1][yind]\n\n        origin = [xc[0] - hx[0] * 0.5, yc[0] - hy[0] * 0.5]\n\n        meshCore = discretize.TensorMesh([hx, hy], origin=origin)\n\n        actind = (\n            (mesh.cell_centers[:, 0] > xmin)\n            & (mesh.cell_centers[:, 0] < xmax)\n            & (mesh.cell_centers[:, 1] > ymin)\n            & (mesh.cell_centers[:, 1] < ymax)\n        )\n\n    elif mesh.dim == 3:\n        xmin, xmax = xyzlim[0, 0], xyzlim[0, 1]\n        ymin, ymax = xyzlim[1, 0], xyzlim[1, 1]\n        zmin, zmax = xyzlim[2, 0], xyzlim[2, 1]\n\n        xind = np.logical_and(mesh.cell_centers_x > xmin, mesh.cell_centers_x < xmax)\n        yind = np.logical_and(mesh.cell_centers_y > ymin, mesh.cell_centers_y < ymax)\n        zind = np.logical_and(mesh.cell_centers_z > zmin, mesh.cell_centers_z < zmax)\n\n        xc = mesh.cell_centers_x[xind]\n        yc = mesh.cell_centers_y[yind]\n        zc = mesh.cell_centers_z[zind]\n\n        hx = mesh.h[0][xind]\n        hy = mesh.h[1][yind]\n        hz = mesh.h[2][zind]\n\n        origin = [xc[0] - hx[0] * 0.5, yc[0] - hy[0] * 0.5, zc[0] - hz[0] * 0.5]\n\n        meshCore = discretize.TensorMesh([hx, hy, hz], origin=origin)\n\n        actind = (\n            (mesh.cell_centers[:, 0] > xmin)\n            & (mesh.cell_centers[:, 0] < xmax)\n            & (mesh.cell_centers[:, 1] > ymin)\n            & (mesh.cell_centers[:, 1] < ymax)\n            & (mesh.cell_centers[:, 2] > zmin)\n            & (mesh.cell_centers[:, 2] < zmax)\n        )\n\n    else:\n        raise Exception(\"Not implemented!\")\n\n    return actind, meshCore\n\n\ndef mesh_builder_xyz(\n    xyz,\n    h,\n    padding_distance=None,\n    base_mesh=None,\n    depth_core=None,\n    expansion_factor=1.3,\n    mesh_type=\"tensor\",\n    tree_diagonal_balance=None,\n):\n    \"\"\"Generate a tensor or tree mesh using a cloud of points.\n\n    For a cloud of (x,y[,z]) locations and specified minimum cell widths\n    (hx,hy,[hz]), this function creates a tensor or a tree mesh.\n    The lateral extent of the core region is determine by the cloud of points.\n    Other properties of the mesh can be defined automatically or by the user.\n    If *base_mesh* is an instance of :class:`~discretize.TensorMesh` or\n    :class:`~discretize.TreeMesh`, the core cells will be centered\n    on the underlying mesh to reduce interpolation errors.\n\n    Parameters\n    ----------\n    xyz : (n, dim) numpy.ndarray\n        Location points\n    h : (dim ) list\n        Cell size(s) for the core mesh\n    padding_distance : list, optional\n        Padding distances [[W,E], [N,S], [Down,Up]], default is no padding.\n    base_mesh : discretize.TensorMesh or discretize.TreeMesh, optional\n        discretize mesh used to center the core mesh\n    depth_core : float, optional\n        Depth of core mesh below xyz\n    expansion_factor : float. optional\n        Expansion factor for padding cells. Ignored if *mesh_type* = *tree*\n    mesh_type : {'tensor', 'tree'}\n        Specify output mesh type\n    tree_diagonal_balance : bool, optional\n        Whether to diagonally balance the tree mesh, `None` will use the `TreeMesh`\n        default behavoir.\n\n    Returns\n    -------\n    discretize.TensorMesh or discretize.TreeMesh\n        Mesh of type specified by *mesh_type*\n\n    Examples\n    --------\n    >>> import discretize\n    >>> import matplotlib.pyplot as plt\n    >>> import numpy as np\n    >>> rng = np.random.default_rng(87142)\n\n    >>> xy_loc = rng.standard_normal((8,2))\n    >>> mesh = discretize.utils.mesh_builder_xyz(\n    ...     xy_loc, [0.1, 0.1], depth_core=0.5,\n    ...     padding_distance=[[1,2], [1,0]],\n    ...     mesh_type='tensor',\n    ... )\n\n    >>> axs = plt.subplot()\n    >>> mesh.plot_image(mesh.cell_volumes, grid=True, ax=axs)\n    >>> axs.scatter(xy_loc[:,0], xy_loc[:,1], 15, c='w', zorder=3)\n    >>> axs.set_aspect('equal')\n    >>> plt.show()\n    \"\"\"\n    if mesh_type.lower() not in [\"tensor\", \"tree\"]:\n        raise ValueError(\"Revise mesh_type. Only TENSOR | TREE mesh are implemented\")\n\n    if padding_distance is None:\n        padding_distance = [[0, 0], [0, 0], [0, 0]]\n    # Get extent of points\n    limits = []\n    center = []\n    nC = []\n    for dim in range(xyz.shape[1]):\n        max_min = np.r_[xyz[:, dim].max(), xyz[:, dim].min()]\n        limits += [max_min]\n        center += [np.mean(max_min)]\n        nC += [int((max_min[0] - max_min[1]) / h[dim])]\n\n    if depth_core is not None:\n        nC[-1] += int(depth_core / h[-1])\n        limits[-1][1] -= depth_core\n\n    if mesh_type.lower() == \"tensor\":\n        # Figure out padding cells from distance\n        def expand(dx, pad):\n            length = 0\n            nc = 0\n            while length < pad:\n                nc += 1\n                length = np.sum(dx * expansion_factor ** (np.asarray(range(nc)) + 1))\n\n            return nc\n\n        # Define h along each dimension\n        h_dim = []\n        nC_origin = []\n        for dim in range(xyz.shape[1]):\n            h_dim += [\n                [\n                    (\n                        h[dim],\n                        expand(h[dim], padding_distance[dim][0]),\n                        -expansion_factor,\n                    ),\n                    (h[dim], nC[dim]),\n                    (\n                        h[dim],\n                        expand(h[dim], padding_distance[dim][1]),\n                        expansion_factor,\n                    ),\n                ]\n            ]\n\n            nC_origin += [h_dim[-1][0][1]]\n\n        # Create mesh\n        mesh = discretize.TensorMesh(h_dim)\n\n    elif mesh_type.lower() == \"tree\":\n        # Figure out full extent required from input\n        h_dim = []\n        nC_origin = []\n        for ii, _cc in enumerate(nC):\n            extent = limits[ii][0] - limits[ii][1] + np.sum(padding_distance[ii])\n\n            # Number of cells at the small octree level\n            maxLevel = int(np.log2(extent / h[ii])) + 1\n            h_dim += [np.ones(2**maxLevel) * h[ii]]\n\n        # Define the mesh and origin\n        mesh = discretize.TreeMesh(h_dim, diagonal_balance=tree_diagonal_balance)\n\n        for ii, _cc in enumerate(nC):\n            core = limits[ii][0] - limits[ii][1]\n\n            nC_origin += [int(np.ceil((mesh.h[ii].sum() - core) / h[ii] / 2))]\n\n    # Set origin\n    origin = []\n    for ii, hi in enumerate(mesh.h):\n        origin += [limits[ii][1] - np.sum(hi[: nC_origin[ii]])]\n\n    mesh.origin = np.hstack(origin)\n\n    # Shift mesh if global mesh is used based on closest to centroid\n    axis = [\"x\", \"y\", \"z\"]\n    if base_mesh is not None:\n        for dim in range(base_mesh.dim):\n            cc_base = getattr(\n                base_mesh,\n                \"cell_centers_{orientation}\".format(orientation=axis[dim]),\n            )\n\n            cc_local = getattr(\n                mesh, \"cell_centers_{orientation}\".format(orientation=axis[dim])\n            )\n\n            shift = (\n                cc_base[np.max([np.searchsorted(cc_base, center[dim]) - 1, 0])]\n                - cc_local[np.max([np.searchsorted(cc_local, center[dim]) - 1, 0])]\n            )\n\n            origin[dim] += shift\n\n            mesh.origin = np.hstack(origin)\n\n    return mesh\n\n\ndef refine_tree_xyz(\n    mesh,\n    xyz,\n    method=\"radial\",\n    octree_levels=(1, 1, 1),\n    octree_levels_padding=None,\n    finalize=False,\n    min_level=0,\n    max_distance=np.inf,\n):\n    \"\"\"Refine region within a :class:`~discretize.TreeMesh`.\n\n    This function refines the specified region of a tree mesh using\n    one of several methods. These are summarized below:\n\n    **radial:** refines based on radial distances from a set of xy[z] locations.\n    Consider a tree mesh whose smallest cell size has a width of *h* . And\n    *octree_levels = [nc1, nc2, nc3, ...]* . Within a distance of *nc1 x h*\n    from any of the points supplied, the smallest cell size is used. Within a distance of\n    *nc2 x (2h)* , the cells will have a width of *2h* . Within a distance of *nc3 x (4h)* ,\n    the cells will have a width of *4h* . Etc...\n\n    **surface:** refines downward from a triangulated surface.\n    Consider a tree mesh whose smallest cell size has a width of *h*. And\n    *octree_levels = [nc1, nc2, nc3, ...]* . Within a downward distance of *nc1 x h*\n    from the topography (*xy[z]* ) supplied, the smallest cell size is used. The\n    topography is triangulated if the points supplied are coarser than the cell\n    size. No refinement is done above the topography. Within a vertical distance of\n    *nc2 x (2h)* , the cells will have a width of *2h* . Within a vertical distance\n    of *nc3 x (4h)* , the cells will have a width of *4h* . Etc...\n\n    **box:** refines inside the convex hull defined by the xy[z] locations.\n    Consider a tree mesh whose smallest cell size has a width of *h*. And\n    *octree_levels = [nc1, nc2, nc3, ...]* . Within the convex hull defined by *xyz* ,\n    the smallest cell size is used. Within a distance of *nc2 x (2h)* from that convex\n    hull, the cells will have a width of *2h* . Within a distance of *nc3 x (4h)* ,\n    the cells will have a width of *4h* . Etc...\n\n    .. deprecated:: 0.9.0\n          `refine_tree_xyz` will be removed in a future version of discretize. It is\n          replaced by `discretize.TreeMesh.refine_surface`, `discretize.TreeMesh.refine_bounding_box`,\n          and `discretize.TreeMesh.refine_points`, to separate the calling convetions,\n          and improve the individual documentation. Those methods are more explicit\n          about which levels of the TreeMesh you are refining, and provide more\n          flexibility for padding cells in each dimension.\n\n    Parameters\n    ----------\n    mesh : discretize.TreeMesh\n        The tree mesh object to be refined\n    xyz : numpy.ndarray\n        2D array of points (n, dim)\n    method : {'radial', 'surface', 'box'}\n        Method used to refine the mesh based on xyz locations.\n\n        - *radial:* Based on radial distance xy[z] and cell centers\n        - *surface:* Refines downward from a triangulated surface\n        - *box:* Inside limits defined by outer xy[z] locations\n\n    octree_levels : list of int, optional\n        Minimum number of cells around points in each *k* octree level\n        starting from the smallest cells size; i.e. *[nc(k), nc(k-1), ...]* .\n        Note that you *can* set entries to 0; e.g. you don't want to discretize\n        using the smallest cell size.\n    octree_levels_padding : list of int, optional\n        Padding cells added to extend the region of refinement at each level.\n        Used for *method = surface* and *box*. Has the form *[nc(k), nc(k-1), ...]*\n    finalize : bool, optional\n        Finalize the tree mesh.\n    min_level : int, optional\n        Sets the largest cell size allowed in the mesh. The default (*0*),\n        allows the largest cell size to be used.\n    max_distance : float\n        Maximum refinement distance from xy[z] locations.\n        Used if *method* = \"surface\" to reduce interpolation distance\n\n    Returns\n    -------\n    discretize.TreeMesh\n        The refined tree mesh\n\n    See Also\n    --------\n    discretize.TreeMesh.refine_surface\n        Recommended to use instead of this function for the `surface` option.\n    discretize.TreeMesh.refine_bounding_box\n        Recommended to use instead of this function for the `box` option.\n    discretize.TreeMesh.refine_points\n        Recommended to use instead of this function for the `radial` option.\n\n    Examples\n    --------\n    Here we use the **refine_tree_xyz** function refine a tree mesh\n    based on topography as well as a cluster of points.\n\n    >>> from discretize import TreeMesh\n    >>> from discretize.utils import mkvc, refine_tree_xyz\n    >>> import matplotlib.pyplot as plt\n    >>> import numpy as np\n\n    >>> dx = 5  # minimum cell width (base mesh cell width) in x\n    >>> dy = 5  # minimum cell width (base mesh cell width) in y\n    >>> x_length = 300.0  # domain width in x\n    >>> y_length = 300.0  # domain width in y\n\n    Compute number of base mesh cells required in x and y\n\n    >>> nbcx = 2 ** int(np.round(np.log(x_length / dx) / np.log(2.0)))\n    >>> nbcy = 2 ** int(np.round(np.log(y_length / dy) / np.log(2.0)))\n\n    Define the base mesh\n\n    >>> hx = [(dx, nbcx)]\n    >>> hy = [(dy, nbcy)]\n    >>> mesh = TreeMesh([hx, hy], x0=\"CC\")\n\n    Refine surface topography\n\n    >>> xx = mesh.nodes_x\n    >>> yy = -3 * np.exp((xx ** 2) / 100 ** 2) + 50.0\n    >>> pts = np.c_[mkvc(xx), mkvc(yy)]\n    >>> mesh = refine_tree_xyz(\n    ...     mesh, pts, octree_levels=[2, 4], method=\"surface\", finalize=False\n    ... )\n\n    Refine mesh near points\n\n    >>> xx = np.array([-10.0, 10.0, 10.0, -10.0])\n    >>> yy = np.array([-40.0, -40.0, -60.0, -60.0])\n    >>> pts = np.c_[mkvc(xx), mkvc(yy)]\n    >>> mesh = refine_tree_xyz(\n    ...     mesh, pts, octree_levels=[4, 2], method=\"radial\", finalize=True\n    ... )\n\n    Plot the mesh\n\n    >>> fig = plt.figure(figsize=(6, 6))\n    >>> ax = fig.add_subplot(111)\n    >>> mesh.plot_grid(ax=ax)\n    >>> ax.set_xbound(mesh.x0[0], mesh.x0[0] + np.sum(mesh.h[0]))\n    >>> ax.set_ybound(mesh.x0[1], mesh.x0[1] + np.sum(mesh.h[1]))\n    >>> ax.set_title(\"QuadTree Mesh\")\n    >>> plt.show()\n    \"\"\"\n    if octree_levels_padding is not None:\n        if len(octree_levels_padding) != len(octree_levels):\n            raise ValueError(\n                \"'octree_levels_padding' must be the length %i\" % len(octree_levels)\n            )\n\n    else:\n        octree_levels_padding = np.zeros_like(octree_levels)\n    octree_levels = np.asarray(octree_levels)\n    octree_levels_padding = np.asarray(octree_levels_padding)\n\n    # levels = mesh.max_level - np.arange(len(octree_levels))\n    # non_zeros = octree_levels != 0\n    # levels = levels[non_zeros]\n\n    # Trigger different refine methods\n    if method.lower() == \"radial\":\n        # padding = octree_levels[non_zeros]\n        # mesh.refine_points(xyz, levels, padding, finalize=finalize,)\n        warnings.warn(\n            \"The radial option is deprecated as of `0.9.0` please update your code to \"\n            \"use the `TreeMesh.refine_points` functionality. It will be removed in a \"\n            \"future version of discretize.\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n\n        # Compute the outer limits of each octree level\n        rMax = np.cumsum(\n            mesh.h[0].min() * octree_levels * 2 ** np.arange(len(octree_levels))\n        )\n        rs = np.ones(xyz.shape[0])\n        level = np.ones(xyz.shape[0], dtype=np.int32)\n        for ii, _nC in enumerate(octree_levels):\n            # skip \"zero\" sized balls\n            if rMax[ii] > 0:\n                mesh.refine_ball(\n                    xyz, rs * rMax[ii], level * (mesh.max_level - ii), finalize=False\n                )\n        if finalize:\n            mesh.finalize()\n\n    elif method.lower() == \"surface\":\n        warnings.warn(\n            \"The surface option is deprecated as of `0.9.0` please update your code to \"\n            \"use the `TreeMesh.refine_surface` functionality. It will be removed in a \"\n            \"future version of discretize.\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        # padding = np.zeros((len(octree_levels), mesh.dim))\n        # padding[:, -1] = np.maximum(octree_levels - 1, 0)\n        # padding[:, :-1] = octree_levels_padding[:, None]\n        # padding = padding[non_zeros]\n        # mesh.refine_surface(xyz, levels, padding, finalize=finalize, pad_down=True, pad_up=False)\n\n        # Compute centroid\n        centroid = np.mean(xyz, axis=0)\n\n        if mesh.dim == 2:\n            rOut = np.abs(centroid[0] - xyz).max()\n            hz = mesh.h[1].min()\n        else:\n            # Largest outer point distance\n            rOut = np.linalg.norm(\n                np.r_[\n                    np.abs(centroid[0] - xyz[:, 0]).max(),\n                    np.abs(centroid[1] - xyz[:, 1]).max(),\n                ]\n            )\n            hz = mesh.h[2].min()\n\n        # Compute maximum depth of refinement\n        zmax = np.cumsum(hz * octree_levels * 2 ** np.arange(len(octree_levels)))\n\n        # Compute maximum horizontal padding offset\n        padWidth = np.cumsum(\n            mesh.h[0].min()\n            * octree_levels_padding\n            * 2 ** np.arange(len(octree_levels_padding))\n        )\n\n        # Increment the vertical offset\n        zOffset = 0\n        xyPad = -1\n        depth = zmax[-1]\n        # Cycle through the Tree levels backward\n        for ii in range(len(octree_levels) - 1, -1, -1):\n            dx = mesh.h[0].min() * 2**ii\n\n            if mesh.dim == 3:\n                dy = mesh.h[1].min() * 2**ii\n                dz = mesh.h[2].min() * 2**ii\n            else:\n                dz = mesh.h[1].min() * 2**ii\n\n            # Increase the horizontal extent of the surface\n            if xyPad != padWidth[ii]:\n                xyPad = padWidth[ii]\n\n                # Calculate expansion for padding XY cells\n                expansion_factor = (rOut + xyPad) / rOut\n                xLoc = (xyz - centroid) * expansion_factor + centroid\n\n                if mesh.dim == 3:\n                    # Create a new triangulated surface\n                    tri2D = Delaunay(xLoc[:, :2])\n                    F = interpolate.LinearNDInterpolator(tri2D, xLoc[:, 2])\n                else:\n                    F = interpolate.interp1d(\n                        xLoc[:, 0], xLoc[:, 1], fill_value=\"extrapolate\"\n                    )\n\n            limx = np.r_[xLoc[:, 0].max(), xLoc[:, 0].min()]\n            nCx = int(np.ceil((limx[0] - limx[1]) / dx))\n\n            if mesh.dim == 3:\n                limy = np.r_[xLoc[:, 1].max(), xLoc[:, 1].min()]\n                nCy = int(np.ceil((limy[0] - limy[1]) / dy))\n\n                # Create a grid at the octree level in xy\n                CCx, CCy = np.meshgrid(\n                    np.linspace(limx[1], limx[0], nCx),\n                    np.linspace(limy[1], limy[0], nCy),\n                )\n\n                xy = np.c_[CCx.reshape(-1), CCy.reshape(-1)]\n\n                # Only keep points within triangulation\n                indexTri = tri2D.find_simplex(xy)\n\n            else:\n                xy = np.linspace(limx[1], limx[0], nCx)\n                indexTri = np.ones_like(xy, dtype=\"bool\")\n\n            # Interpolate the elevation linearly\n            z = F(xy[indexTri != -1])\n\n            newLoc = np.c_[xy[indexTri != -1], z]\n\n            # Only keep points within max_distance\n            tree = cKDTree(xyz)\n            r, ind = tree.query(newLoc)\n\n            # Apply vertical padding for current octree level\n            dim = mesh.dim - 1\n            zOffset = 0\n            while zOffset < depth:\n                indIn = r < (max_distance + padWidth[ii])\n                nnz = int(np.sum(indIn))\n                if nnz > 0:\n                    mesh.insert_cells(\n                        np.c_[newLoc[indIn, :dim], newLoc[indIn, -1] - zOffset],\n                        np.ones(nnz) * mesh.max_level - ii,\n                        finalize=False,\n                    )\n\n                zOffset += dz\n\n            depth -= dz * octree_levels[ii]\n\n        if finalize:\n            mesh.finalize()\n\n    elif method.lower() == \"box\":\n        warnings.warn(\n            \"The box option is deprecated as of `0.9.0` please update your code to \"\n            \"use the `TreeMesh.refine_bounding_box` functionality. It will be removed in a \"\n            \"future version of discretize.\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        # padding = np.zeros((len(octree_levels), mesh.dim))\n        # padding[:, -1] = np.maximum(octree_levels - 1, 0)\n        # padding[:, :-1] = octree_levels_padding[:, None]\n        # padding = padding[non_zeros]\n        # mesh.refine_bounding_box(xyz, levels, padding, finalize=finalize)\n\n        # Define the data extent [bottom SW, top NE]\n        bsw = np.min(xyz, axis=0)\n        tne = np.max(xyz, axis=0)\n\n        hs = np.asarray([h.min() for h in mesh.h])\n        hx = hs[0]\n        hz = hs[-1]\n\n        # Pre-calculate outer extent of each level\n        # x_pad\n        padWidth = np.cumsum(\n            hx * octree_levels_padding * 2 ** np.arange(len(octree_levels))\n        )\n        if mesh.dim == 3:\n            # y_pad\n            hy = hs[1]\n            padWidth = np.c_[\n                padWidth,\n                np.cumsum(\n                    hy * octree_levels_padding * 2 ** np.arange(len(octree_levels))\n                ),\n            ]\n        # Pre-calculate max depth of each level\n        padWidth = np.c_[\n            padWidth,\n            np.cumsum(\n                hz\n                * np.maximum(octree_levels - 1, 0)\n                * 2 ** np.arange(len(octree_levels))\n            ),\n        ]\n\n        levels = []\n        BSW = []\n        TNE = []\n        for ii, octZ in enumerate(octree_levels):\n            if octZ > 0:\n                levels.append(mesh.max_level - ii)\n                BSW.append(bsw - padWidth[ii])\n                TNE.append(tne + padWidth[ii])\n\n        mesh.refine_box(BSW, TNE, levels, finalize=finalize)\n\n    else:\n        raise NotImplementedError(\n            \"Only method= 'radial', 'surface'\" \" or 'box' have been implemented\"\n        )\n\n    return mesh\n\n\ndef active_from_xyz(mesh, xyz, grid_reference=\"CC\", method=\"linear\"):\n    \"\"\"Return boolean array indicating which cells are below surface.\n\n    For a set of locations defining a surface, **active_from_xyz** outputs a\n    boolean array indicating which mesh cells like below the surface points.\n    This method uses SciPy's interpolation routine to interpolate between\n    location points defining the surface. Nearest neighbour interpolation\n    is used for cells outside the convex hull of the surface points.\n\n    Parameters\n    ----------\n    mesh : discretize.TensorMesh or discretize.TreeMesh or discretize.CylindricalMesh\n        Mesh object. If *mesh* is a cylindrical mesh, it must be symmetric\n    xyz : (N, dim) numpy.ndarray\n        Points defining the surface topography.\n    grid_reference : {'CC', 'N'}\n        Define where the cell is defined relative to surface. Choose between {'CC','N'}\n\n        - If 'CC' is used, cells are active if their centers are below the surface.\n        - If 'N' is used, cells are active if they lie entirely below the surface.\n\n    method : {'linear', 'nearest'}\n        Interpolation method for locations between the xyz points.\n\n    Returns\n    -------\n    (n_cells) numpy.ndarray of bool\n        1D mask array of *bool* for the active cells below xyz.\n\n    Examples\n    --------\n    Here we define the active cells below a parabola. We demonstrate the differences\n    that appear when using the 'CC' and 'N' options for *reference_grid*.\n\n    >>> import matplotlib.pyplot as plt\n    >>> import numpy as np\n    >>> from discretize import TensorMesh\n    >>> from discretize.utils import active_from_xyz\n\n    Determine active cells for a given mesh and topography\n\n    >>> mesh = TensorMesh([5, 5])\n    >>> topo_func = lambda x: -3*(x-0.2)*(x-0.8)+.5\n    >>> topo_points = np.linspace(0, 1)\n    >>> topo_vals = topo_func(topo_points)\n    >>> active_cc = active_from_xyz(mesh, np.c_[topo_points, topo_vals], grid_reference='CC')\n    >>> active_n = active_from_xyz(mesh, np.c_[topo_points, topo_vals], grid_reference='N')\n\n    Plot visual representation\n\n    >>> ax = plt.subplot(121)\n    >>> mesh.plot_image(active_cc, ax=ax)\n    >>> mesh.plot_grid(centers=True, ax=ax)\n    >>> ax.plot(np.linspace(0,1), topo_func(np.linspace(0,1)), color='C3')\n    >>> ax.set_title(\"CC\")\n    >>> ax = plt.subplot(122)\n    >>> mesh.plot_image(active_n, ax=ax)\n    >>> mesh.plot_grid(nodes=True, ax=ax)\n    >>> ax.plot(np.linspace(0,1), topo_func(np.linspace(0,1)), color='C3')\n    >>> ax.set_title(\"N\")\n    >>> plt.show()\n    \"\"\"\n    try:\n        if not mesh.is_symmetric:\n            raise NotImplementedError(\n                \"Unsymmetric CylindricalMesh is not yet supported\"\n            )\n    except AttributeError:\n        pass\n\n    if grid_reference not in [\"N\", \"CC\"]:\n        raise ValueError(\n            \"Value of grid_reference must be 'N' (nodal) or 'CC' (cell center)\"\n        )\n\n    dim = mesh.dim - 1\n\n    if mesh.dim == 3:\n        if xyz.shape[1] != 3:\n            raise ValueError(\"xyz locations of shape (*, 3) required for 3D mesh\")\n        if method == \"linear\":\n            tri2D = Delaunay(xyz[:, :2])\n            z_interpolate = interpolate.LinearNDInterpolator(tri2D, xyz[:, 2])\n        else:\n            z_interpolate = interpolate.NearestNDInterpolator(xyz[:, :2], xyz[:, 2])\n    elif mesh.dim == 2:\n        if xyz.shape[1] != 2:\n            raise ValueError(\"xyz locations of shape (*, 2) required for 2D mesh\")\n        z_interpolate = interpolate.interp1d(\n            xyz[:, 0], xyz[:, 1], bounds_error=False, fill_value=np.nan, kind=method\n        )\n    else:\n        if xyz.ndim != 1:\n            raise ValueError(\"xyz locations of shape (*, ) required for 1D mesh\")\n\n    if grid_reference == \"CC\":\n        # this should work for all 4 mesh types...\n        locations = mesh.cell_centers\n\n        if mesh.dim == 1:\n            active = np.zeros(mesh.nC, dtype=\"bool\")\n            active[np.searchsorted(mesh.cell_centers_x, xyz).max() :] = True\n            return active\n\n    elif grid_reference == \"N\":\n        try:\n            # try for Cyl, Tensor, and Tree operations\n            if mesh.dim == 3:\n                locations = np.vstack(\n                    [\n                        mesh.cell_centers\n                        + (np.c_[-1, 1, 1][:, None] * mesh.h_gridded / 2.0).squeeze(),\n                        mesh.cell_centers\n                        + (np.c_[-1, -1, 1][:, None] * mesh.h_gridded / 2.0).squeeze(),\n                        mesh.cell_centers\n                        + (np.c_[1, 1, 1][:, None] * mesh.h_gridded / 2.0).squeeze(),\n                        mesh.cell_centers\n                        + (np.c_[1, -1, 1][:, None] * mesh.h_gridded / 2.0).squeeze(),\n                    ]\n                )\n\n            elif mesh.dim == 2:\n                locations = np.vstack(\n                    [\n                        mesh.cell_centers\n                        + (np.c_[-1, 1][:, None] * mesh.h_gridded / 2.0).squeeze(),\n                        mesh.cell_centers\n                        + (np.c_[1, 1][:, None] * mesh.h_gridded / 2.0).squeeze(),\n                    ]\n                )\n\n            else:\n                active = np.zeros(mesh.nC, dtype=\"bool\")\n                active[np.searchsorted(mesh.nodes_x, xyz).max() :] = True\n\n                return active\n        except AttributeError:\n            # Try for Curvilinear Mesh\n            gridN = mesh.gridN.reshape((*mesh.vnN, mesh.dim), order=\"F\")\n            if mesh.dim == 3:\n                locations = np.vstack(\n                    [\n                        gridN[:-1, 1:, 1:].reshape((-1, mesh.dim), order=\"F\"),\n                        gridN[:-1, :-1, 1:].reshape((-1, mesh.dim), order=\"F\"),\n                        gridN[1:, 1:, 1:].reshape((-1, mesh.dim), order=\"F\"),\n                        gridN[1:, :-1, 1:].reshape((-1, mesh.dim), order=\"F\"),\n                    ]\n                )\n            elif mesh.dim == 2:\n                locations = np.vstack(\n                    [\n                        gridN[:-1, 1:].reshape((-1, mesh.dim), order=\"F\"),\n                        gridN[1:, 1:].reshape((-1, mesh.dim), order=\"F\"),\n                    ]\n                )\n\n    # Interpolate z values on CC or N\n    z_xyz = z_interpolate(locations[:, :-1]).squeeze()\n\n    # Apply nearest neighbour if in extrapolation\n    ind_nan = np.isnan(z_xyz)\n    if any(ind_nan):\n        tree = cKDTree(xyz)\n        _, ind = tree.query(locations[ind_nan, :])\n        z_xyz[ind_nan] = xyz[ind, dim]\n\n    # Create an active bool of all True\n    active = np.all(\n        (locations[:, dim] < z_xyz).reshape((mesh.nC, -1), order=\"F\"), axis=1\n    )\n\n    return active.ravel()\n\n\ndef example_simplex_mesh(rect_shape):\n    \"\"\"Create a simple tetrahedral mesh on a unit cube in 2D or 3D.\n\n    Returns the nodes and connectivity of a triangulated domain on the [0, 1] cube.\n    This is not necessarily a good triangulation, just a complete one. This is mostly\n    used for testing purposes. In 2D, this discretizes each rectangle into two triangles.\n    In 3D, each cube is broken into 6 tetrahedrons.\n\n    Parameters\n    ----------\n    rect_shape : (dim) array_like of int\n        For each dimension, create n+1 nodes along that axis.\n\n    Returns\n    -------\n    points : (n_points, dim) numpy.ndarray\n        array of created nodes\n    simplics : (n_cells, dim + 1) numpy.ndarray\n        connectivity of nodes for each cell.\n\n    Examples\n    --------\n    >>> from discretize import SimplexMesh\n    >>> from discretize.utils import example_simplex_mesh\n    >>> from matplotlib import pyplot as plt\n    >>> nodes, simplices = example_simplex_mesh((5, 6))\n    >>> mesh = SimplexMesh(nodes, simplices)\n    >>> mesh.plot_grid()\n    >>> plt.show()\n    \"\"\"\n    if len(rect_shape) == 2:\n        n1, n2 = rect_shape\n        xs, ys = np.mgrid[0 : 1 : (n1 + 1) * 1j, 0 : 1 : (n2 + 1) * 1j]\n        points = np.c_[xs.reshape(-1), ys.reshape(-1)]\n\n        node_inds = np.arange((n1 + 1) * (n2 + 1)).reshape((n1 + 1, n2 + 1))\n\n        left_triangs = np.c_[\n            node_inds[:-1, :-1].reshape(-1),  # i00\n            node_inds[1:, :-1].reshape(-1),  # i10\n            node_inds[:-1, 1:].reshape(-1),  # i01\n        ]\n        right_triangs = np.c_[\n            node_inds[1:, 1:].reshape(-1),  # i11\n            node_inds[1:, :-1].reshape(-1),  # i10\n            node_inds[:-1, 1:].reshape(-1),  # i01\n        ]\n\n        simplices = np.r_[left_triangs, right_triangs]\n    if len(rect_shape) == 3:\n        n1, n2, n3 = rect_shape\n        xs, ys, zs = np.mgrid[\n            0 : 1 : (n1 + 1) * 1j, 0 : 1 : (n2 + 1) * 1j, 0 : 1 : (n3 + 1) * 1j\n        ]\n        points = np.c_[xs.reshape(-1), ys.reshape(-1), zs.reshape(-1)]\n\n        node_inds = np.arange((n1 + 1) * (n2 + 1) * (n3 + 1)).reshape(\n            (n1 + 1, n2 + 1, n3 + 1)\n        )\n\n        a_triangs = np.c_[\n            node_inds[1:, :-1, :-1].reshape(-1),  # i100\n            node_inds[:-1, :-1, 1:].reshape(-1),  # i001\n            node_inds[:-1, 1:, :-1].reshape(-1),  # i010\n            node_inds[:-1, :-1, :-1].reshape(-1),  # i000\n        ]\n        b_triangs = np.c_[\n            node_inds[:-1, 1:, 1:].reshape(-1),  # i011\n            node_inds[1:, :-1, :-1].reshape(-1),  # i100\n            node_inds[:-1, :-1, 1:].reshape(-1),  # i001\n            node_inds[:-1, 1:, :-1].reshape(-1),  # i010\n        ]\n        c_triangs = np.c_[\n            node_inds[1:, 1:, :-1].reshape(-1),  # i110\n            node_inds[:-1, 1:, 1:].reshape(-1),  # i011\n            node_inds[1:, :-1, :-1].reshape(-1),  # i100\n            node_inds[:-1, 1:, :-1].reshape(-1),  # i010\n        ]\n        d_triangs = np.c_[\n            node_inds[1:, :-1, 1:].reshape(-1),  # i101\n            node_inds[:-1, 1:, 1:].reshape(-1),  # i011\n            node_inds[1:, :-1, :-1].reshape(-1),  # i100\n            node_inds[:-1, :-1, 1:].reshape(-1),  # i001\n        ]\n        e_triangs = np.c_[\n            node_inds[1:, :-1, 1:].reshape(-1),  # i101\n            node_inds[1:, 1:, :-1].reshape(-1),  # i110\n            node_inds[:-1, 1:, 1:].reshape(-1),  # i011\n            node_inds[1:, 1:, 1:].reshape(-1),  # i111\n        ]\n        f_triangs = np.c_[\n            node_inds[1:, :-1, 1:].reshape(-1),  # i101\n            node_inds[1:, 1:, :-1].reshape(-1),  # i110\n            node_inds[:-1, 1:, 1:].reshape(-1),  # i011\n            node_inds[1:, :-1, :-1].reshape(-1),  # i100\n        ]\n\n        simplices = np.r_[\n            a_triangs, b_triangs, c_triangs, d_triangs, e_triangs, f_triangs\n        ]\n\n    return points, simplices\n\n\nmeshTensor = deprecate_function(\n    unpack_widths, \"meshTensor\", removal_version=\"1.0.0\", error=True\n)\nclosestPoints = deprecate_function(\n    closest_points_index, \"closestPoints\", removal_version=\"1.0.0\", error=True\n)\nExtractCoreMesh = deprecate_function(\n    extract_core_mesh, \"ExtractCoreMesh\", removal_version=\"1.0.0\", error=True\n)\nclosest_points = deprecate_function(\n    closest_points_index, \"closest_points\", removal_version=\"1.0.0\", error=True\n)\n"
  },
  {
    "path": "discretize/utils/meshutils.py",
    "content": "from discretize.utils.mesh_utils import *  # NOQA F401,F403\n\nraise ImportError(\n    \"Importing from discretize.meshutils is deprecated behavoir. Please import \"\n    \"from discretize.utils. This message will be removed in version 1.0.0 of discretize.\",\n)\n"
  },
  {
    "path": "discretize/utils/meson.build",
    "content": "\npython_sources = [\n  '__init__.py',\n  'code_utils.py',\n  'coordinate_utils.py',\n  'curvilinear_utils.py',\n  'interpolation_utils.py',\n  'io_utils.py',\n  'matrix_utils.py',\n  'mesh_utils.py',\n  'codeutils.py',\n  'coordutils.py',\n  'curvutils.py',\n  'interputils.py',\n  'matutils.py',\n  'meshutils.py',\n]\n\npy.install_sources(\n  python_sources,\n  subdir: 'discretize/utils'\n)\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    = -j auto\nSPHINXBUILD   = sphinx-build\nBUILDDIR      = _build\n\n# Internal variables.\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\n\n.PHONY: all api help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  api        to build the api docs\"\n\t@echo \"  html       to make standalone HTML files\"\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 \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\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\nall: html\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\trm -rf api/generated\n\trm -rf examples/\n\trm -rf tutorials/\n\trm -rf sg_execution_times.rst\n\nhtml-noplot:\n\t$(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\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 or in $(BUILDDIR)/linkcheck/output.txt.\"\n\nlinkcheck-noplot:\n\t$(SPHINXBUILD) -D plot_gallery=0 -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\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": "docs/_static/css/custom.css",
    "content": "@import url(https://fonts.googleapis.com/css?family=Raleway);\n\n.bd-header .navbar-header-items__center {\n    margin: auto!important;\n}\n\nbody{\n    font-family: \"Roboto\", sans-serif;\n}\nh1{\n    font-family: \"Raleway\", Helvetica, Arial, sans-serif; font-weight: bold;\n}\nh2{\n    font-family: \"Raleway\", Helvetica, Arial, sans-serif; font-weight: bold;\n}\nh3{\n    font-family: \"Raleway\", Helvetica, Arial, sans-serif; font-weight: bold;\n}\n.column > h3{\n    font-family: \"Raleway\", Helvetica, Arial, sans-serif;\n}\n\n/* Dark theme tweaking\nMatplotlib images are in png and inverted while other output\ntypes are assumed to be normal images.\n*/\nhtml[data-theme=dark] img:not(.only-dark):not(.dark-light) {\n    filter: invert(0.82) brightness(0.8) contrast(1.2);\n}\nhtml[data-theme=dark] img[src*='discretize-logo.png']:not(.only-dark):not(.dark-light){\n    filter: invert(0.82) brightness(0.8) contrast(1.2) hue-rotate(180deg);\n}\nhtml[data-theme=dark] img[src*='discretize-logo.png'] {\n    filter: invert(0.82) brightness(0.8) contrast(1.2) hue-rotate(180deg);\n}\nhtml[data-theme=dark] .MathJax_SVG *  {\n    fill: var(--pst-color-text-base);\n}\n\nhtml[data-theme=dark]  {\n    --pst-color-text-muted: #a6a6a6;\n}\nhtml[data-theme=light]  {\n    --pst-color-text-muted: rgb(51, 51, 51);\n}\n\n\nhtml[data-theme=dark] .navbar-nav>.active>.nav-link {\n    color: #FFFFFF!important;\n}\nhtml[data-theme=light] .navbar-nav>.active>.nav-link {\n    color: #000000!important;\n}\n\nhtml[data-theme=dark] .bd-header {\n    background: #213a1b!important;\n}\nhtml[data-theme=light] .bd-header {\n    background: #acd6af!important;\n}\n"
  },
  {
    "path": "docs/_static/versions.json",
    "content": "[\n    {   \"name\": \"main\",\n        \"version\": \"dev\",\n        \"url\": \"https://discretize.simpeg.xyz/en/main/\"\n    },\n    {\n        \"name\": \"0.12.0 (stable)\",\n        \"version\": \"v0.12.0\",\n        \"url\": \"https://discretize.simpeg.xyz/en/v0.12.0/\",\n        \"preferred\": true\n    },\n    {\n        \"name\": \"0.11.3\",\n        \"version\": \"v0.11.3\",\n        \"url\": \"https://discretize.simpeg.xyz/en/v0.11.3/\"\n    },\n    {\n        \"name\": \"0.11.2\",\n        \"version\": \"v0.11.2\",\n        \"url\": \"https://discretize.simpeg.xyz/en/v0.11.2/\"\n    },\n    {\n        \"name\": \"0.11.1\",\n        \"version\": \"v0.11.1\",\n        \"url\": \"https://discretize.simpeg.xyz/en/v0.11.1/\"\n    },\n    {\n        \"name\": \"0.11.0\",\n        \"version\": \"v0.11.0\",\n        \"url\": \"https://discretize.simpeg.xyz/en/v0.11.0/\"\n    },\n    {\n        \"name\": \"0.10.0\",\n        \"version\": \"v0.10.0\",\n        \"url\": \"https://discretize.simpeg.xyz/en/v0.10.0/\"\n    }\n]\n\n"
  },
  {
    "path": "docs/_templates/autosummary/attribute.rst",
    "content": ":orphan:\n\n{{ fullname | escape | underline}}\n\n.. currentmodule:: {{ module }}\n\n.. auto{{ objtype }}:: {{ objname }}"
  },
  {
    "path": "docs/_templates/autosummary/base.rst",
    "content": "{% if objtype == 'property' %}\n:orphan:\n{% endif %}\n\n{{ fullname | escape | underline}}\n\n.. currentmodule:: {{ module }}\n\n.. auto{{ objtype }}:: {{ objname }}"
  },
  {
    "path": "docs/_templates/autosummary/class.rst",
    "content": "{{ fullname }}\n{{ underline }}\n\n.. currentmodule:: {{ module }}\n\n.. inheritance-diagram:: {{ objname }}\n    :parts: 1\n\n.. autoclass:: {{ objname }}\n\n  {% block methods %}\n   .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages.\n      .. autosummary::\n         :toctree:\n      {% for item in all_methods %}\n         {%- if not item.startswith('_') or item in ['__call__', '__mul__', '__getitem__', '__len__'] %}\n         {{ name }}.{{ item }}\n         {%- endif -%}\n      {%- endfor %}\n      {% for item in inherited_members %}\n         {%- if item in ['__call__', '__mul__', '__getitem__', '__len__'] %}\n         {{ name }}.{{ item }}\n         {%- endif -%}\n      {%- endfor %}\n  {% endblock %}\n\n  {% block attributes %}\n  {% if attributes %}\n   .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages.\n      .. autosummary::\n         :toctree:\n      {% for item in all_attributes %}\n         {%- if not item.startswith('_') %}\n         {{ name }}.{{ item }}\n         {%- endif -%}\n      {%- endfor %}\n  {% endif %}\n  {% endblock %}\n\n.. minigallery:: {{ fullname }}\n    :add-heading: Galleries and Tutorials using ``{{ fullname }}``\n    :heading-level: -\n"
  },
  {
    "path": "docs/_templates/autosummary/function.rst",
    "content": "{{ fullname | escape | underline }}\n\n.. currentmodule:: {{ module }}\n\n.. autofunction:: {{ objname }}\n\n.. minigallery:: {{ fullname }}\n    :add-heading: Galleries and Tutorials using ``{{ fullname }}``\n    :heading-level: -\n"
  },
  {
    "path": "docs/_templates/autosummary/method.rst",
    "content": ":orphan:\n\n{{ fullname | escape | underline}}\n\n.. currentmodule:: {{ module }}\n\n.. auto{{ objtype }}:: {{ objname }}"
  },
  {
    "path": "docs/api/discretize.base.rst",
    "content": ".. automodule:: discretize.base\n"
  },
  {
    "path": "docs/api/discretize.mixins.rst",
    "content": ".. automodule:: discretize.mixins\n"
  },
  {
    "path": "docs/api/discretize.operators.rst",
    "content": ".. automodule:: discretize.operators\n"
  },
  {
    "path": "docs/api/discretize.rst",
    "content": ".. automodule:: discretize\n"
  },
  {
    "path": "docs/api/discretize.tests.rst",
    "content": ".. automodule:: discretize.tests\n"
  },
  {
    "path": "docs/api/discretize.utils.rst",
    "content": ".. automodule:: discretize.utils\n"
  },
  {
    "path": "docs/api/index.rst",
    "content": ".. _api:\n\n=============\nAPI Reference\n=============\n\nMeshes\n======\n\nMeshes supported by the ``discretize`` package.\nThe :class:`~discretize.tree_mesh.TreeCell` class is an additional class used to define cells comprising\ninstances of the :class:`~discretize.TreeMesh` class.\n\n.. toctree::\n  :maxdepth: 3\n\n  discretize\n\nMesh Building Blocks\n====================\n\nBase classes for ``discretize`` meshes, classes for constructing discrete operators,\nand mixins for interfacing with external libraries.\n\n.. toctree::\n  :maxdepth: 2\n\n  discretize.base\n  discretize.operators\n  discretize.mixins\n\nUtilities\n=========\n\nClasses and functions for performing useful operations.\n\n.. toctree::\n  :maxdepth: 3\n\n  discretize.utils\n\nTesting\n=======\n\nClasses and functions for testing the ``discretize`` package.\n\n.. toctree::\n  :maxdepth: 2\n\n  discretize.tests\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# discretize documentation build configuration file, created by\n# sphinx-quickstart on Fri Aug 30 18:42:44 2013.\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 os\nimport sys\nfrom pathlib import Path\nfrom datetime import datetime\nfrom packaging.version import parse\nimport discretize\nimport shutil\nfrom importlib.metadata import version\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.\n# sys.path.insert(0, os.path.abspath('.'))\n# sys.path.append(os.path.pardir)\n\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    \"numpydoc\",\n    \"sphinx.ext.autosummary\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.extlinks\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.mathjax\",\n    \"sphinx.ext.inheritance_diagram\",\n    \"sphinx.ext.graphviz\",\n    \"matplotlib.sphinxext.plot_directive\",\n    \"sphinx_gallery.gen_gallery\",\n]\n\n# Autosummary pages will be generated by sphinx-autogen instead of sphinx-build\nautosummary_generate = True\n\nnumpydoc_attributes_as_param_list = False\n\nnumpydoc_show_inherited_class_members = {\n    \"discretize.base.BaseMesh\": False,\n    \"discretize.base.BaseRegularMesh\": False,\n    \"discretize.base.BaseRectangularMesh\": False,\n    \"discretize.base.BaseTensorMesh\": False,\n    \"discretize.operators.DiffOperators\": False,\n    \"discretize.operators.InnerProducts\": False,\n    \"discretize.mixins.TensorMeshIO\": False,\n    \"discretize.mixins.TreeMeshIO\": False,\n    \"discretize.mixins.InterfaceMPL\": False,\n    \"discretize.mixins.InterfaceVTK\": False,\n    \"discretize.mixins.InterfaceOMF\": False,\n    \"discretize.mixins.Slicer\": False,\n    \"discretize.tests.OrderTest\": False,\n}\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source file names.\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 = \"discretize\"\ncopyright = \"2013 - {}, SimPEG Developers, http://simpeg.xyz\".format(\n    datetime.now().year\n)\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 full version, including alpha/beta/rc tags.\nrelease = version(\"discretize\")\ndiscretize_version = parse(release)\n\n# The short X.Y version.\nversion = discretize_version.public\nif discretize_version.is_devrelease:\n    branch = \"main\"\nelse:\n    branch = f\"v{version}\"\n# The short X.Y version.\n# version = \".\".join(release.split(\".\")[:2])\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 = [\"_build\"]\n\nlinkcheck_ignore = [\n    r\"https://github.com/simpeg/*\",\n]\n\nlinkcheck_retries = 3\nlinkcheck_timeout = 500\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.\n# show_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# source code links\nlink_github = True\n# You can build old with link_github = False\n\nif link_github:\n    import inspect\n\n    extensions.append(\"sphinx.ext.linkcode\")\n\n    def linkcode_resolve(domain, info):\n        \"\"\"\n        Determine the URL corresponding to Python object\n        \"\"\"\n        if domain != \"py\":\n            return None\n\n        modname = info[\"module\"]\n        fullname = info[\"fullname\"]\n\n        submod = sys.modules.get(modname)\n        if submod is None:\n            return None\n\n        obj = submod\n        for part in fullname.split(\".\"):\n            try:\n                obj = getattr(obj, part)\n            except AttributeError:\n                return None\n\n        if inspect.isfunction(obj):\n            obj = inspect.unwrap(obj)\n        try:\n            fn = inspect.getsourcefile(obj)\n        except TypeError:\n            fn = None\n        if not fn or fn.endswith(\"__init__.py\"):\n            try:\n                fn = inspect.getsourcefile(sys.modules[obj.__module__])\n            except (TypeError, AttributeError, KeyError):\n                fn = None\n        if not fn:\n            return None\n\n        try:\n            source, lineno = inspect.getsourcelines(obj)\n        except (OSError, TypeError):\n            lineno = None\n\n        if lineno:\n            linespec = f\"#L{lineno:d}-L{lineno + len(source) - 1:d}\"\n        else:\n            linespec = \"\"\n\n        try:\n            fn = os.path.relpath(fn, start=os.path.dirname(discretize.__file__))\n        except ValueError:\n            return None\n        return f\"https://github.com/simpeg/discretize/blob/{branch}/discretize/{fn}{linespec}\"\n\nelse:\n    extensions.append(\"sphinx.ext.viewcode\")\n\n# Make numpydoc to generate plots for example sections\nnumpydoc_use_plots = True\nplot_pre_code = \"\"\"\nimport numpy as np\nnp.random.seed(0)\n\"\"\"\nplot_include_source = True\nplot_formats = [(\"png\", 100), \"pdf\"]\n\nimport math\n\nphi = (math.sqrt(5) + 1) / 2\n\nplot_rcparams = {\n    \"font.size\": 8,\n    \"axes.titlesize\": 8,\n    \"axes.labelsize\": 8,\n    \"xtick.labelsize\": 8,\n    \"ytick.labelsize\": 8,\n    \"legend.fontsize\": 8,\n    \"figure.figsize\": (3 * phi, 3),\n    \"figure.subplot.bottom\": 0.2,\n    \"figure.subplot.left\": 0.2,\n    \"figure.subplot.right\": 0.9,\n    \"figure.subplot.top\": 0.85,\n    \"figure.subplot.wspace\": 0.4,\n    \"text.usetex\": False,\n}\n\n# -- Options for HTML output ---------------------------------------------------\nexternal_links = [\n    dict(name=\"SimPEG\", url=\"https://simpeg.xyz\"),\n    dict(name=\"Contact\", url=\"https://mattermost.softwareunderground.org/simpeg\"),\n]\n\n\n# Use Pydata Sphinx theme\nhtml_theme = \"pydata_sphinx_theme\"\n\n# If false, no module index is generated.\nhtml_use_modindex = True\n\nhtml_theme_options = {\n    \"external_links\": external_links,\n    \"icon_links\": [\n        {\n            \"name\": \"GitHub\",\n            \"url\": \"https://github.com/simpeg/discretize\",\n            \"icon\": \"fab fa-github\",\n        },\n        {\n            \"name\": \"Mattermost\",\n            \"url\": \"https://mattermost.softwareunderground.org/simpeg\",\n            \"icon\": \"fas fa-comment\",\n        },\n        {\n            \"name\": \"Discourse\",\n            \"url\": \"https://simpeg.discourse.group/\",\n            \"icon\": \"fab fa-discourse\",\n        },\n        {\n            \"name\": \"Youtube\",\n            \"url\": \"https://www.youtube.com/c/geoscixyz\",\n            \"icon\": \"fab fa-youtube\",\n        },\n    ],\n    \"use_edit_page_button\": False,\n    \"collapse_navigation\": True,\n    \"navbar_align\": \"left\",  # make elements closer to logo on the left\n    \"navbar_end\": [\"version-switcher\", \"theme-switcher\", \"navbar-icon-links\"],\n    # Configure version switcher (remember to add it to the \"navbar_end\")\n    \"switcher\": {\n        \"version_match\": \"dev\" if branch == \"main\" else branch,\n        \"json_url\": \"https://discretize.simpeg.xyz/en/main/_static/versions.json\",\n    },\n    \"show_version_warning_banner\": True,\n}\n\nhtml_logo = \"images/discretize-logo.png\"\n\nhtml_static_path = [\"_static\"]\n\nhtml_css_files = [\n    \"css/custom.css\",\n]\n\nhtml_context = {\n    \"github_user\": \"simpeg\",\n    \"github_repo\": \"discretize\",\n    \"github_version\": \"main\",\n    \"doc_path\": \"docs\",\n}\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.\n# html_theme_options = {}\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.\n# html_logo = None\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.\nhtml_favicon = os.path.sep.join([\".\", \"images\", \"discretize-block.ico\"])\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\".\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.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_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.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\nhtml_show_sphinx = False\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\nhtml_show_copyright = False\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 = \"discretize\"\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #'preamble': '',\n}\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    (\n        \"index\",\n        \"discretize.tex\",\n        \"discretize documentation\",\n        \"SimPEG Developers\",\n        \"manual\",\n    ),\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\", \"simpeg\", \"discretize Documentation\", [\"SimPEG Developers\"], 1)]\n\n# If true, show URL addresses after external links.\n# man_show_urls = False\n\n# Intersphinx\nintersphinx_mapping = {\n    \"python\": (\"https://docs.python.org/3\", None),\n    \"numpy\": (\"https://numpy.org/doc/stable\", None),\n    \"scipy\": (\"https://docs.scipy.org/doc/scipy\", None),\n    \"matplotlib\": (\"https://matplotlib.org/stable\", None),\n    \"pyvista\": (\"https://docs.pyvista.org\", None),\n    \"omf\": (\"https://omf.readthedocs.io/en/stable\", None),\n}\nnumpydoc_xref_param_type = True\n\n# -- Options for Texinfo output -----------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        \"index\",\n        \"discretize\",\n        \"discretize documentation\",\n        \"SimPEG Developers\",\n        \"discretize\",\n        \"Finite volume methods for python.\",\n        \"Miscellaneous\",\n    ),\n]\n\n# -- pyvista configuration ---------------------------------------------------\nimport pyvista\n\n# Manage errors\npyvista.set_error_output_file(\"errors.txt\")\n# Ensure that offscreen rendering is used for docs generation\npyvista.OFF_SCREEN = True  # Not necessary - simply an insurance policy\n# Preferred plotting style for documentation\npyvista.set_plot_theme(\"document\")\npyvista.global_theme.window_size = [1024, 768]\npyvista.global_theme.font.size = 22\npyvista.global_theme.font.label_size = 22\npyvista.global_theme.font.title_size = 22\npyvista.global_theme.return_cpos = False\npyvista.set_jupyter_backend(None)\n# Save figures in specified directory\npyvista.FIGURE_PATH = os.path.join(os.path.abspath(\"./images/\"), \"auto-generated/\")\nif not os.path.exists(pyvista.FIGURE_PATH):\n    os.makedirs(pyvista.FIGURE_PATH)\n\n\n# necessary when building the sphinx gallery\npyvista.BUILDING_GALLERY = True\nos.environ[\"PYVISTA_BUILDING_GALLERY\"] = \"true\"\n\n# Sphinx Gallery\nsphinx_gallery_conf = {\n    # path to your examples scripts\n    \"examples_dirs\": [\n        \"../examples\",\n        \"../tutorials/mesh_generation\",\n        \"../tutorials/operators\",\n        \"../tutorials/inner_products\",\n        \"../tutorials/pde\",\n    ],\n    \"gallery_dirs\": [\n        \"examples\",\n        \"tutorials/mesh_generation\",\n        \"tutorials/operators\",\n        \"tutorials/inner_products\",\n        \"tutorials/pde\",\n    ],\n    \"within_subsection_order\": \"FileNameSortKey\",\n    \"filename_pattern\": \"\\\\.py\",\n    \"backreferences_dir\": \"api/generated/backreferences\",\n    \"doc_module\": \"discretize\",\n    \"image_scrapers\": (\"pyvista\", \"matplotlib\"),\n}\n\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\ngraphviz_dot = shutil.which(\"dot\")\n# this must be png, because links on SVG are broken\ngraphviz_output_format = \"png\"\n\nautodoc_member_order = \"bysource\"\n\nnitpick_ignore = [\n    (\"py:class\", \"discretize.CurvilinearMesh.Array\"),\n    (\"py:class\", \"discretize.mixins.vtk_mod.InterfaceTensorread_vtk\"),\n    (\"py:class\", \"callable\"),\n]\n"
  },
  {
    "path": "docs/content/additional_resources.rst",
    "content": ".. _additional_resources:\n\nAdditional Resources\n====================\n\nAn enormous amount of information (including tutorials and examples) can be\nfound on the official websites of the packages\n\n* `Python Website <https://www.python.org/>`_\n* `Numpy Website <http://www.numpy.org/>`_\n* `SciPy Website <http://www.scipy.org/>`_\n* `Matplotlib <http://matplotlib.org/>`_\n\nPython for scientific computing\n-------------------------------\n\n* `Learn Python <https://pyzo.org/learn.html>`_ Links to commonly used packages, Matlab to Python comparison\n* `Python Wiki <http://wiki.python.org/moin/NumericAndScientific>`_ Lists packages and resources for scientific computing in Python\n\nNumpy and Matlab\n----------------\n\n* `NumPy for Matlab Users <https://docs.scipy.org/doc/numpy/user/numpy-for-matlab-users.html>`_\n* `Python vs Matlab <https://pyzo.org/python_vs_matlab.html>`_\n\nLessons in Python\n-----------------\n\n* `Software Carpentry <http://swcarpentry.github.io/python-novice-inflammation/>`_\n* `Introduction to NumPy and Matplotlib <https://www.youtube.com/watch?v=3Fp1zn5ao2M>`_\n\nEditing Python\n--------------\n\nThere are numerous ways to edit and test Python\n(see `PythonWiki <http://wiki.python.org/moin/PythonEditors>`_ for an overview)\nand in our group at least the following options are being used:\n\n* `Sublime <https://www.sublimetext.com/>`_\n* `Jupyter <https://jupyter.org/>`_\n"
  },
  {
    "path": "docs/content/big_picture.rst",
    "content": "Why discretize?\n===============\n\nInverse problems are common across the geosciences: imaging in geophysics,\nhistory matching, parameter estimation, and many of these require constrained\noptimization using partial differential equations (PDEs) where the derivative\nof mesh variables are sought. Finite difference, finite element and finite\nvolume techniques allow subdivision of continuous differential equations into\ndiscrete domains. The knowledge and appropriate application of these methods\nis fundamental to simulating physical processes. Many inverse problems in the\ngeosciences are solved using stochastic techniques or external finite\ndifference based tools (e.g. PEST); these are robust to\nlocal minima and the programmatic implementation, respectively, however these\nmethods do not scale to millions of parameters to be estimated. This sort of\nscale is necessary for solving many of the inverse problems in geophysics and\nincreasingly hydrogeology (e.g. electromagnetics, gravity, and fluid flow\nproblems).\n\nIn the context of the inverse problem, when the physical properties, the\ndomain, and the boundary conditions are not necessarily known, the simplicity\nand efficiency in mesh generation are important criteria. Complex mesh\ngeometries, such as body fitted grids, commonly used when the domain is\nexplicitly given, are less appropriate. Additionally, when considering the\ninverse problem, it is important that operators and their derivatives are\naccessible to interrogation and extension. The goal of this work is to\nprovide a high level background to finite volume techniques abstracted across\nfour mesh types:\n\n    1) tensor product mesh  :class:`discretize.TensorMesh`\n    2) cylindrically symmetric mesh :class:`discretize.CylMesh`\n    3) curvilinear mesh :class:`discretize.CurvilinearMesh`\n    4) octree and quadtree meshes :class:`discretize.TreeMesh`\n\n:code:`discretize` contributes an overview of finite volume techniques in the\ncontext of geoscience inverse problems, which are treated in a consistent way\nacross various mesh types, highlighting similarities and differences.\n\n.. include:: ../../CITATION.rst\n\nAuthors\n-------\n\n.. include:: ../../AUTHORS.rst\n\nLicense\n-------\n\n.. include:: ../../LICENSE\n"
  },
  {
    "path": "docs/content/finite_volume.rst",
    "content": ".. _api_FiniteVolume:\n\nFinite Volume\n*************\n\nAny numerical implementation requires the discretization of continuous\nfunctions into discrete approximations. These approximations are typically\norganized in a mesh, which defines boundaries, locations, and connectivity. Of\nspecific interest to geophysical simulations, we require that averaging,\ninterpolation and differential operators be defined for any mesh. In SimPEG,\nwe have implemented a staggered mimetic finite volume approach (`Hyman and\nShashkov, 1999 <https://doi.org/10.1006/jcph.1999.6225>`_). This\napproach requires the definitions of variables at either cell-centers, nodes,\nfaces, or edges as seen in the figure below.\n\n.. image:: ../images/finitevolrealestate.png\n   :width: 400 px\n   :alt: FiniteVolume\n   :align: center\n"
  },
  {
    "path": "docs/content/getting_started.rst",
    "content": ".. _getting_started:\n\n===============\nGetting Started\n===============\n\nHere you'll find instructions on getting up and running with ``discretize``.\n\n.. toctree::\n  :maxdepth: 2\n\n  big_picture\n  installing\n  additional_resources\n"
  },
  {
    "path": "docs/content/inner_products.rst",
    "content": "Inner Products\n**************\n\nBy using the weak formulation of many of the PDEs in geophysical applications,\nwe can rapidly develop discretizations. Much of this work, however, needs a\ngood understanding of how to approximate inner products on our discretized\nmeshes. We will define the inner product as:\n\n.. math::\n\n    \\left(a,b\\right) = \\int_\\Omega{a \\cdot b}{\\partial v}\n\nwhere a and b are either scalars or vectors.\n\n.. note::\n\n    The InnerProducts class is a base class providing inner product matrices\n    for meshes and cannot run on its own.\n\n\nExample problem for DC resistivity\n----------------------------------\n\nWe will start with the formulation of the Direct Current (DC) resistivity\nproblem in geophysics.\n\n\n.. math::\n\n        \\frac{1}{\\sigma}\\vec{j} = \\nabla \\phi \\\\\n\n        \\nabla\\cdot \\vec{j} = q\n\nIn the following discretization, :math:`\\sigma` and :math:`\\phi`\nwill be discretized on the cell-centers and the flux, :math:`\\vec{j}`,\nwill be on the faces. We will use the weak formulation to discretize\nthe DC resistivity equation.\n\nWe can define in weak form by integrating with a general face function\n:math:`\\vec{f}`:\n\n.. math::\n\n    \\int_{\\Omega}{\\sigma^{-1}\\vec{j} \\cdot \\vec{f}} = \\int_{\\Omega}{\\nabla \\phi  \\cdot \\vec{f}}\n\nHere we can integrate the right side by parts,\n\n.. math::\n\n    \\nabla\\cdot(\\phi\\vec{f})=\\nabla\\phi\\cdot\\vec{f} + \\phi\\nabla\\cdot\\vec{f}\n\nand rearrange it, and apply the Divergence theorem.\n\n.. math::\n\n    \\int_{\\Omega}{\\sigma^{-1}\\vec{j} \\cdot \\vec{f}} =\n    - \\int_{\\Omega}{(\\phi \\nabla \\cdot \\vec{f})}\n    + \\int_{\\partial \\Omega}{ \\phi  \\vec{f} \\cdot \\mathbf{n}}\n\nWe can then discretize for every cell:\n\n.. math::\n\n    v_{\\text{cell}} \\sigma^{-1} (\\mathbf{J}_x \\mathbf{F}_x +\\mathbf{J}_y \\mathbf{F}_y  + \\mathbf{J}_z \\mathbf{F}_z ) = -\\phi^{\\top} v_{\\text{cell}} \\mathbf{D}_{\\text{cell}} \\mathbf{F}  + \\text{BC}\n\n.. note::\n\n    We have discretized the dot product above, but remember that we do not\n    really have a single vector :math:`\\mathbf{J}`, but approximations of\n    :math:`\\vec{j}` on each face of our cell. In 2D that means 2\n    approximations of :math:`\\mathbf{J}_x` and 2 approximations of\n    :math:`\\mathbf{J}_y`. In 3D we also have 2 approximations of\n    :math:`\\mathbf{J}_z`.\n\nRegardless of how we choose to approximate this dot product, we can represent\nthis in vector form (again this is for every cell), and will generalize for\nthe case of anisotropic (tensor) sigma.\n\n.. math::\n\n    \\mathbf{F}_c^{\\top} (\\sqrt{v_{\\text{cell}}} \\Sigma^{-1} \\sqrt{v_{\\text{cell}}})  \\mathbf{J}_c =\n    -\\phi^{\\top} v_{\\text{cell}} \\mathbf{D}_{\\text{cell}} \\mathbf{F})\n    + \\text{BC}\n\nWe multiply by  square-root of volume on each side of the tensor conductivity\nto keep symmetry in the system. Here :math:`\\mathbf{J}_c` is the Cartesian\n:math:`\\mathbf{J}` (on the faces that we choose to use in our approximation)\nand must be calculated differently depending on the mesh:\n\n.. math::\n    \\mathbf{J}_c = \\mathbf{Q}_{(i)}\\mathbf{J}_\\text{TENSOR} \\\\\n    \\mathbf{J}_c = \\mathbf{N}_{(i)}^{-1}\\mathbf{Q}_{(i)}\\mathbf{J}_\\text{Curv}\n\nHere the :math:`i` index refers to where we choose to approximate this integral, as discussed in the note above.\nWe will approximate this integral by taking the fluxes clustered around every node of the cell, there are 8 combinations in 3D, and 4 in 2D. We will use a projection matrix :math:`\\mathbf{Q}_{(i)}` to pick the appropriate fluxes. So, now that we have 8 approximations of this integral, we will just take the average. For the TensorMesh, this looks like:\n\n.. math::\n\n    \\mathbf{F}^{\\top}\n        {1\\over 8}\n        \\left(\\sum_{i=1}^8\n        \\mathbf{Q}_{(i)}^{\\top} \\sqrt{v_{\\text{cell}}} \\Sigma^{-1} \\sqrt{v_{\\text{cell}}}  \\mathbf{Q}_{(i)}\n        \\right)\n        \\mathbf{J}\n        =\n        -\\mathbf{F}^{\\top} \\mathbf{D}_{\\text{cell}}^{\\top} v_{\\text{cell}} \\phi   + \\text{BC}\n\nOr, when generalizing to the entire mesh and dropping our general face function:\n\n.. math::\n\n    \\mathbf{M}^f_{\\Sigma^{-1}} \\mathbf{J}\n        =\n        - \\mathbf{D}^{\\top} \\text{diag}(\\mathbf{v}) \\phi   + \\text{BC}\n\nBy defining the faceInnerProduct (8 combinations of fluxes in 3D, 4 in 2D, 2 in 1D) to be:\n\n.. math::\n\n    \\mathbf{M}^f_{\\Sigma^{-1}} =\n        \\sum_{i=1}^{2^d}\n        \\mathbf{P}_{(i)}^{\\top} \\Sigma^{-1} \\mathbf{P}_{(i)}\n\nWhere :math:`d` is the dimension of the mesh.\nThe :math:`\\mathbf{M}^f` is returned when given the input of :math:`\\Sigma^{-1}`.\n\nHere each :math:`\\mathbf{P} ~ \\in ~ \\mathbb{R}^{(d*nC, nF)}` is a combination\nof the projection, volume, and any normalization to Cartesian coordinates\n(where the dot product is well defined):\n\n.. math::\n\n    \\mathbf{P}_{(i)} =  \\sqrt{ \\frac{1}{2^d} \\mathbf{I}^d \\otimes \\text{diag}(\\mathbf{v})} \\overbrace{\\mathbf{N}_{(i)}^{-1}}^{\\text{Curv only}} \\mathbf{Q}_{(i)}\n\n.. note::\n\n    This is actually completed for each cell in the mesh at the same time, and the full matrices are returned.\n\nIf ``returnP=True`` is requested in any of these methods the projection matrices are returned as a list ordered by nodes around which the fluxes were approximated::\n\n    # In 3D\n    P = [P000, P100, P010, P110, P001, P101, P011, P111]\n    # In 2D\n    P = [P00, P10, P01, P11]\n    # In 1D\n    P = [P0, P1]\n\nThe derivation for ``edgeInnerProducts`` is exactly the same, however, when we\napproximate the integral using the fields around each node, the projection\nmatrices look a bit different because we have 12 edges in 3D instead of just 6\nfaces. The interface to the code is exactly the same.\n\n\nDefining Tensor Properties\n--------------------------\n\n**For 3D:**\n\nDepending on the number of columns (either 1, 3, or 6) of mu, the material\nproperty is interpreted as follows:\n\n.. math::\n\n    \\vec{\\mu} = \\left[\\begin{matrix} \\mu_{1} & 0 & 0 \\\\ 0 & \\mu_{1} & 0 \\\\ 0 & 0 & \\mu_{1}  \\end{matrix}\\right]\n\n    \\vec{\\mu} = \\left[\\begin{matrix} \\mu_{1} & 0 & 0 \\\\ 0 & \\mu_{2} & 0 \\\\ 0 & 0 & \\mu_{3}  \\end{matrix}\\right]\n\n    \\vec{\\mu} = \\left[\\begin{matrix} \\mu_{1} & \\mu_{4} & \\mu_{5} \\\\ \\mu_{4} & \\mu_{2} & \\mu_{6} \\\\ \\mu_{5} & \\mu_{6} & \\mu_{3}  \\end{matrix}\\right]\n\n**For 2D:**\n\n Depending on the number of columns (either 1, 2, or 3) of mu, the material property is interpreted as follows:\n\n.. math::\n    \\vec{\\mu} = \\left[\\begin{matrix} \\mu_{1} & 0 \\\\ 0 & \\mu_{1} \\end{matrix}\\right]\n\n    \\vec{\\mu} = \\left[\\begin{matrix} \\mu_{1} & 0 \\\\ 0 & \\mu_{2} \\end{matrix}\\right]\n\n    \\vec{\\mu} = \\left[\\begin{matrix} \\mu_{1} & \\mu_{3} \\\\ \\mu_{3} & \\mu_{2} \\end{matrix}\\right]\n\n\nStructure of Matrices\n---------------------\n\nBoth the isotropic, and anisotropic material properties result in a diagonal mass matrix.\nWhich is nice and easy to invert if necessary, however, in the fully anisotropic case which is not aligned with the grid, the matrix is not diagonal. This can be seen for a 3D mesh in the figure below.\n\n.. plot::\n\n    import discretize\n    import numpy as np\n    import matplotlib.pyplot as plt\n    mesh = discretize.TensorMesh([10,50,3])\n    m1 = np.random.rand(mesh.nC)\n    m2 = np.random.rand(mesh.nC,3)\n    m3 = np.random.rand(mesh.nC,6)\n    M = list(range(3))\n    M[0] = mesh.get_face_inner_product(m1)\n    M[1] = mesh.get_face_inner_product(m2)\n    M[2] = mesh.get_face_inner_product(m3)\n    plt.figure(figsize=(13,5))\n    for i, lab in enumerate(['Isotropic','Anisotropic','Tensor']):\n        plt.subplot(131 + i)\n        plt.spy(M[i],ms=0.5,color='k')\n        plt.tick_params(axis='both',which='both',labeltop='off',labelleft='off')\n        plt.title(lab + ' Material Property')\n    plt.show()\n\n\nTaking Derivatives\n------------------\n\nWe will take the derivative of the fully anisotropic tensor for a 3D mesh, the\nother cases are easier and will not be discussed here. Let us start with one\npart of the sum which makes up :math:`\\mathbf{M}^f_\\Sigma` and take the\nderivative when this is multiplied by some vector :math:`\\mathbf{v}`:\n\n.. math::\n\n    \\mathbf{P}^\\top \\boldsymbol{\\Sigma} \\mathbf{Pv}\n\nHere we will let :math:`\\mathbf{Pv} = \\mathbf{y}` and :math:`\\mathbf{y}` will have the form:\n\n.. math::\n\n    \\mathbf{y} = \\mathbf{Pv} =\n    \\left[\n        \\begin{matrix}\n            \\mathbf{y}_1\\\\\n            \\mathbf{y}_2\\\\\n            \\mathbf{y}_3\\\\\n        \\end{matrix}\n    \\right]\n\n.. math::\n\n    \\mathbf{P}^\\top\\Sigma\\mathbf{y} =\n    \\mathbf{P}^\\top\n    \\left[\\begin{matrix}\n        \\boldsymbol{\\sigma}_{1} & \\boldsymbol{\\sigma}_{4} & \\boldsymbol{\\sigma}_{5} \\\\\n        \\boldsymbol{\\sigma}_{4} & \\boldsymbol{\\sigma}_{2} & \\boldsymbol{\\sigma}_{6} \\\\\n        \\boldsymbol{\\sigma}_{5} & \\boldsymbol{\\sigma}_{6} & \\boldsymbol{\\sigma}_{3}\n    \\end{matrix}\\right]\n    \\left[\n        \\begin{matrix}\n            \\mathbf{y}_1\\\\\n            \\mathbf{y}_2\\\\\n            \\mathbf{y}_3\\\\\n        \\end{matrix}\n    \\right]\n    =\n    \\mathbf{P}^\\top\n    \\left[\n        \\begin{matrix}\n            \\boldsymbol{\\sigma}_{1}\\circ \\mathbf{y}_1 + \\boldsymbol{\\sigma}_{4}\\circ \\mathbf{y}_2 + \\boldsymbol{\\sigma}_{5}\\circ \\mathbf{y}_3\\\\\n            \\boldsymbol{\\sigma}_{4}\\circ \\mathbf{y}_1 + \\boldsymbol{\\sigma}_{2}\\circ \\mathbf{y}_2 + \\boldsymbol{\\sigma}_{6}\\circ \\mathbf{y}_3\\\\\n            \\boldsymbol{\\sigma}_{5}\\circ \\mathbf{y}_1 + \\boldsymbol{\\sigma}_{6}\\circ \\mathbf{y}_2 + \\boldsymbol{\\sigma}_{3}\\circ \\mathbf{y}_3\\\\\n        \\end{matrix}\n    \\right]\n\nNow it is easy to take the derivative with respect to any one of the\nparameters, for example,\n:math:`\\frac{\\partial}{\\partial\\boldsymbol{\\sigma}_1}`\n\n.. math::\n    \\frac{\\partial}{\\partial \\boldsymbol{\\sigma}_1}\\left(\\mathbf{P}^\\top\\Sigma\\mathbf{y}\\right)\n    =\n    \\mathbf{P}^\\top\n    \\left[\n        \\begin{matrix}\n            \\text{diag}(\\mathbf{y}_1)\\\\\n            0\\\\\n            0\\\\\n        \\end{matrix}\n    \\right]\n\nWhereas :math:`\\frac{\\partial}{\\partial\\boldsymbol{\\sigma}_4}`, for\nexample, is:\n\n.. math::\n    \\frac{\\partial}{\\partial \\boldsymbol{\\sigma}_4}\\left(\\mathbf{P}^\\top\\Sigma\\mathbf{y}\\right)\n    =\n    \\mathbf{P}^\\top\n    \\left[\n        \\begin{matrix}\n            \\text{diag}(\\mathbf{y}_2)\\\\\n            \\text{diag}(\\mathbf{y}_1)\\\\\n            0\\\\\n        \\end{matrix}\n    \\right]\n\nThese are computed for each of the 8 projections, horizontally concatenated,\nand returned.\n"
  },
  {
    "path": "docs/content/installing.rst",
    "content": ".. _api_installing:\n\nInstalling\n**********\n\nWhich Python?\n=============\n\nCurrently, ``discretize`` is tested on python 3.10 through 3.12. We recommend that you\nuse the latest version of Python available on `Anaconda <https://www.anaconda.com/download>`_.\n\nInstalling Python\n-----------------\n\nPython is available on all major operating systems, but if you are getting started with python\nit is best to use a package manager such as\n`Anaconda <https://www.anaconda.com/download>`_.\nYou can download the package manager and use it to install the dependencies above.\n\n.. note::\n    When using Continuum Anaconda, make sure to run::\n\n        conda update conda\n        conda update anaconda\n\n.. _discretize_dependencies:\n\nDependencies\n============\n\n``discretize``'s runtime requirements are:\n\n- `numpy <http://www.numpy.org>`_ 1.22.4 (or greater)\n- `scipy <https://docs.scipy.org/doc/scipy/reference>`_ 1.8 (or greater)\n\nAdditional functionality is provided when the following optional packages\nare installed:\n\n- `matplotlib <https://matplotlib.org/>`_\n- `pyvista <https://pyvista.org/>`_\n- `vtk <https://vtk.org/>`_\n- `omf <https://omf.readthedocs.io/en/latest/>`_\n\nWe also recommend installing:\n\n- `pymatsolver <https://pymatsolver.readthedocs.io/en/latest/>`_ 0.1.2 (or greater)\n\nInstalling discretize\n=====================\n\n``discretize`` is available on conda-forge and using the ``conda`` (or ``mamba``) package manager\nis our recommended way of installing `discretize``::\n\n    conda install -c conda-forge discretize\n\n``discretize`` is also available on pypi::\n\n    pip install discretize\n\nThere are currently pre-built wheels for windows available on pypi, but other operating\nsystems will require a build.\n\nInstalling from Source\n----------------------\n.. attention::\n    Install ``discretize`` from the source code only if you need to run the development version.\n    Otherwise it's usually better to install it from ``conda-forge`` or ``pypi``.\n\nAs ``discretize`` contains several compiled extensions and is not a pure python pacakge,\ninstalling ``discretize`` from the source code requires a C/C++ compiler capable of\nusing a C++ 17 standard.\n\n``discretize`` uses a ``pyproject.toml`` file to define the build and install steps. As such\nthere is no ``setup.py`` file to run. You must use ``pip`` to install ``discretize``. As long as\nyou have an available compiler you should be able to install ``discretize`` from the source as::\n\n    pip install .\n\n\n.. note::\n    For Windows users, make sure you are using compilers that are compatible with your python\n    installation. If you have gnu compilers on your system, ``meson`` will default to using those,\n    and you might have to force ``meson`` to use the ``msvc`` compilers by appending::\n\n        --config-settings=setup-args=\"--vsenv\"\n\n    to the ``pip`` installation command.\n\nEditable Installs\n^^^^^^^^^^^^^^^^^\nIf you are an active developer of ``discretize``, and find yourself modifying the code often,\nyou might want to install it from source, in an editable installation. ``discretize`` uses\n``meson-python`` to build the external modules and install the package. As such, there are a few extra\nsteps to take:\n\n1. Make sure you have the runtime dependencies installed in your environment (see :ref:`discretize_dependencies` listed above).\n   However, you **must** install ``numpy >=2.0`` when *building* ``discretize``.\n2. You must also install packages needed to build ``discretize`` into your environment. You can do so with ``pip``::\n\n      pip install meson-python meson ninja cython setuptools_scm\n\n   Or with ``conda``::\n\n      conda install -c conda-forge meson-python meson ninja cython setuptools_scm\n\n   This will allow you to use the build backend required by `discretize`.\n\n3. Finally, you should then be able to perform an editable install using the source code::\n\n      pip install --no-build-isolation --editable .\n\n\nThis builds and installs the local directory to your active python environment in an\n\"editable\" mode; when source code is changed, you will be able to make use of it immediately. It also builds against the\npackages installed in your environment instead of creating and isolated environment to build a wheel for the package,\nwhich is why we needed to install the build requirements into the environment.\n\nTesting your installation\n=========================\n\nHead over to the :ref:`sphx_glr_examples` and download and run any of the notebooks or python scripts.\n"
  },
  {
    "path": "docs/content/theory.rst",
    "content": ".. _theory:\n\n======\nTheory\n======\nThis section is a resource for the fundamental finite volume theory used behind\n`discretize`.\n\n.. toctree::\n  :maxdepth: 2\n\n  finite_volume\n  inner_products\n"
  },
  {
    "path": "docs/content/user_guide.rst",
    "content": ".. _user_guide:\n\n==========\nUser Guide\n==========\nWe've included some tutorials and gallery examples that will walk you through using\ndiscretize to solve your PDE. For more details on any of the functions, check out the\nAPI documentation.\n\nTutorials\n---------\n.. toctree::\n  :maxdepth: 2\n\n  ../tutorials/mesh_generation/index\n  ../tutorials/operators/index\n  ../tutorials/inner_products/index\n  ../tutorials/pde/index\n\nExamples\n--------\n.. toctree::\n  :maxdepth: 2\n\n  ../examples/index\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. include:: ../README.rst\n\n.. toctree::\n    :maxdepth: 3\n    :hidden:\n    :titlesonly:\n\n    content/getting_started\n    content/user_guide\n    api/index\n    content/theory\n    release/index\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nset I18NSPHINXOPTS=%SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\SimPEG.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\SimPEG.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/release/0.10.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.10.0_notes:\n\n===================================\n``discretize`` 0.10.0 Release Notes\n===================================\n\nOctober 27, 2023\n\nThis minor release changes ``discretize`` to use a ``pyproject.toml`` file with a\n``meson-python`` build backend, and does away with the old `setup.py` style. This should\nallow the package to more reliably be built from the source code, properly setting the\nbuild-requirements.\n\nIt also adds more functionality for integrating properties that are defined on cell faces\nand edges for finite volume formulations (previously we only supported integrating with properties\nthat were defined on cell centers).\n\nBuild system\n------------\n``discretize`` now uses a ``pyproject.toml`` file with a ``meson-python`` backend to build\nthe compiled external modules (used for the ``TreeMesh``, ``SimplexMesh``, and interpolation\nfunctions. Moving away from a ``setup.py`` file allows us to reliably control the build environment\nseparate from the install environment, as the build requirements are not the same as the runtime\nrequirements.\n\nWe will also begin distributing many more pre-compiled wheels on pypi for Windows, MacOS, and Linux\nsystems from python 3.8 to 3.12. Our goal is to provide a pre-compiled wheel for every system that\nscipy provides wheels for.\n\nTensor Mesh\n-----------\nYou can now directly index a ``TensorMesh`` and return a ``TensorCell`` object.\nThis functionality mimics what is currently available in ``TreeMesh``.\n\n``TensorMesh`` also has a ``cell_nodes`` property that list the indices of each node of every\ncell (again similar to the ``TreeMesh``).\n\nFace Properties\n---------------\nAll meshes now have new functionality to integrate properties that are defined on cell faces\nand cell meshes within the finite volume formulation.\n\nStyle updates\n-------------\nThe pre-commit config files for discretize have been updated to more recent versions of ``black``\nand ``flake8``.\n\n\nContributors\n============\n\n* @jcapriot\n* @santisoler\n* @dccowan\n* @munahaf\n\nPull requests\n=============\n\n* `#327 <https://github.com/simpeg/discretize/pull/327>`__: Add black, flake8 and flake8 plugins to environment file\n* `#328 <https://github.com/simpeg/discretize/pull/328>`__: Use any Python 3 in pre-commit\n* `#329 <https://github.com/simpeg/discretize/pull/329>`__: Simplex stashing\n* `#325 <https://github.com/simpeg/discretize/pull/325>`__: Add new TensorCell class\n* `#330 <https://github.com/simpeg/discretize/pull/330>`__: Configure pyvista for doc builds\n* `#331 <https://github.com/simpeg/discretize/pull/331>`__: Add a noexcept clause to the wrapper function\n* `#326 <https://github.com/simpeg/discretize/pull/326>`__: Face props mass matrices\n* `#335 <https://github.com/simpeg/discretize/pull/335>`__: Pin flake8\n* `#333 <https://github.com/simpeg/discretize/pull/333>`__: Add cell_nodes property to TensorMesh\n* `#339 <https://github.com/simpeg/discretize/pull/339>`__: Update a test expression to fix a logical short circuit\n* `#340 <https://github.com/simpeg/discretize/pull/340>`__: Add export config for git archives\n* `#338 <https://github.com/simpeg/discretize/pull/338>`__: Pyproject.toml\n* `#342 <https://github.com/simpeg/discretize/pull/342>`__: CIbuildwheel\n* `#342 <https://github.com/simpeg/discretize/pull/343>`__: 0.10.0 Release Notes\n"
  },
  {
    "path": "docs/release/0.11.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.11.0_notes:\n\n===================================\n``discretize`` 0.11.0 Release Notes\n===================================\n\nOctober 24, 2024\n\nThis minor release contains many bugfixes and updates related to new package builds.\n\nNumpy 2\n-------\n`discretize` now fully supports `numpy` 2! It is both built against and tested against `numpy` 2.0. It still\nhas a minimum required runtime of `numpy` 1.22 though, as building against numpy 2.0 emits ABI compatible calls for\nolder numpy versions.\n\nOf note to developers, we now require `numpy` 2.0 for building as it makes use of the `numpy-config` tool to locate\nthe `numpy` include directory.\n\nPython versions\n---------------\n`discretize` has bumped its minimum supported `python` version to 3.10, and is tested against versions 3.10-3.13. In the\nfuture we intended to stay in line with the minimum `python` version supported by the most recent `numpy` release.\n\n\nRandom Generators\n-----------------\n`discretize` and its testing utilities now make use of ``numpy.random.RandomGenerator`` to make draws from random\nnumber generators instead of the deprecated ``numpy.random.rand`` functions. These functions now support a new keyword\nargument `random_seed` :\n\n* :func:``discretize.tests.setup_mesh`` (only used when ``\"random\" in mesh_type``)\n* :func:``discretize.tests.check_derivative``  (only used when ``dx=None``)\n* :func:``discretize.tests.assert_isadjoint``\n* :func:``discretize.tests.OrderTest.orderTest``  (only used when ``\"random\" in mesh_type``)\n* :func:``discretize.utils.random_model``\n\nMaintainers of downstream packages should explicitly set seeded generators when using these methods for testing\npurposess to ensure reproducibility.\n\n\nCell Bounds\n-----------\n:class:``discretize.TensorMesh`` and :class:``discretize.TreeMesh`` now have a ``cell_bounds`` property that returns the\n``(x_min, x_max, y_min, y_max, z_min, z_max)`` of every cell in the mesh at once. Also now the\n:class:``discretize.tree_mesh.TreeCell`` has a corresponding ``bounds`` property.\n\n\n``TreeMesh`` updates\n--------------------\nYou can now query a :class:``discretize.TreeMesh`` for cells contained in the same geometric primitives that are supported\nfor refining. In addition there is a new :func:``discretize.TreeMesh.refine_plane`` method for refining along a plane.\n\n\nContributors\n============\n\n* @jcapriot\n* @santisoler\n* @prisae\n* @xiaolongw1223\n* @lheagy\n* @omid-b\n\nPull requests\n=============\n\n* `#347 <https://github.com/simpeg/discretize/pull/347>`__: Replace deprecated Numpy's `product` by `prod`\n* `#351 <https://github.com/simpeg/discretize/pull/351>`__: Replace Slack links for Mattermost links\n* `#353 <https://github.com/simpeg/discretize/pull/353>`__: Fix typo in tutorials\n* `#354 <https://github.com/simpeg/discretize/pull/354>`__: Update year in LICENSE\n* `#356 <https://github.com/simpeg/discretize/pull/356>`__: Expose TreeMesh geometric intersections used for refine functions.\n* `#358 <https://github.com/simpeg/discretize/pull/358>`__: Replace hanging CurviMesh in docstring for CurvilinearMesh\n* `#360 <https://github.com/simpeg/discretize/pull/360>`__: Update use of `numpy`'s random number generators.\n* `#364 <https://github.com/simpeg/discretize/pull/364>`__: Fix slicer re #363\n* `#366 <https://github.com/simpeg/discretize/pull/366>`__: Add `TensorMesh.cell_bounds` property\n* `#367 <https://github.com/simpeg/discretize/pull/367>`__: Add `TreeCell.bounds` and `TreeMesh.cell_bounds` methods\n* `#368 <https://github.com/simpeg/discretize/pull/368>`__: Set minimum to Python 3.10 (and general CI Maintenance)\n* `#371 <https://github.com/simpeg/discretize/pull/371>`__: Add version switcher to discretize docs\n* `#372 <https://github.com/simpeg/discretize/pull/372>`__: Deploy docs to a new folder named after their tagged version\n* `#373 <https://github.com/simpeg/discretize/pull/373>`__: display dev doc banner\n* `#374 <https://github.com/simpeg/discretize/pull/374>`__: Bump pydata_sphinx_theme version to 0.15.4\n* `#375 <https://github.com/simpeg/discretize/pull/375>`__: Fix caching of internal projection matrices\n* `#376 <https://github.com/simpeg/discretize/pull/376>`__: Fix macos-latest build\n* `#379 <https://github.com/simpeg/discretize/pull/379>`__: Numpy2.0 updates\n* `#380 <https://github.com/simpeg/discretize/pull/380>`__: Create build_distributions.yml\n* `#381 <https://github.com/simpeg/discretize/pull/381>`__: 0.11.0 Release Notes\n"
  },
  {
    "path": "docs/release/0.11.1-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.11.1_notes:\n\n===================================\n``discretize`` 0.11.1 Release Notes\n===================================\n\nNovember 5, 2024\n\nThis is a bugfix release for issues found in the previous release, and adds some warning messages for users.\n\nUpdates\n=======\n\nWarning Messages\n----------------\n* Added a warning to the `TreeMesh` informing of incoming future behavoir changes regarding\n  diagonal balancing. This will default to ``True`` in `discretize` 1.0.\n* Added warning messages to test functions using uncontrolled randomized input that appear when run under\n  `pytest` or `nosetest`, alerting the user to non-repeatable tests.\n* Changed the default for ``plotIt`` argument to ``False`` for testing functions.\n\nFixed Bugs\n----------\n* `TreeMesh.point2index` now refers to the correction function.\n\n\nContributors\n============\n* @jcapriot\n\nPull requests\n=============\n\n* Outstanding bugfixes. by @jcapriot in `#382 <https://github.com/simpeg/discretize/pull/382>`__.\n* Warn for non-repeatable random tests in a testing environment by @jcapriot in `#384 <https://github.com/simpeg/discretize/pull/384>`__.\n* Staging for 0.11.1 by @jcapriot in `#385 <https://github.com/simpeg/discretize/pull/385>`__."
  },
  {
    "path": "docs/release/0.11.2-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.11.2_notes:\n\n===================================\n``discretize`` 0.11.2 Release Notes\n===================================\n\nJanuary 28, 2025\n\nThis is a bugfix release for discretize with some minor updates.\n\nUpdates\n=======\n\nFixed Bugs\n----------\n* ``is_scalar`` will now return true for any numpy array broadcastable as a scalar (any array with ``arr.size == 1``).\n* Explicitly set diagonal balancing on internal plotting and reading routines that build a ``TreeMesh``.\n* Fixes formatting across line breaks in new warnings.\n* the ``Zero`` and ``Identity`` classes now return expected truthiness values: ``bool(Zero()) == False`` and ``bool(Identity()) == True``.\n\n\nContributors\n============\n* @jcapriot\n* @santisoler\n* @prisae\n\nPull requests\n=============\n\n* improve scalar test to handle arbitrary dimensional ndarrays by @jcapriot in `#388 <https://github.com/simpeg/discretize/pull/388>`__.\n* Set no diagonal balance when reading UBC tree meshes by @santisoler in `#386 <https://github.com/simpeg/discretize/pull/386>`__\n* Fix small typos in diagonal_balance warning by @santisoler in `#387 <https://github.com/simpeg/discretize/pull/387>`__\n* Implement truthiness for Zero and Identity by @jcapriot in `#389 <https://github.com/simpeg/discretize/pull/389>`__\n* Fix formatting new warning by @prisae in `#390 <https://github.com/simpeg/discretize/pull/390>`__\n* v0.11.2 staging @jcapriot in `#391 <https://github.com/simpeg/discretize/pull/390>`__\n"
  },
  {
    "path": "docs/release/0.11.3-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.11.3_notes:\n\n===================================\n``discretize`` 0.11.3 Release Notes\n===================================\n\nJune 6, 2025\n\nThis is a bugfix release for discretize.\n\nUpdates\n=======\n\nFixed Bugs\n----------\n\n* Updates for Cython import deprecations.\n* Allows safely generating string representations of non-finalized `TreeMesh`.\n* Adds error guards when attempting to access a property or method that requires a finalized `TreeMesh`.\n\n\nContributors\n============\n* @jcapriot\n* @santisoler\n\nPull requests\n=============\n\n* Python3.13 by @jcapriot in `#377 <https://github.com/simpeg/discretize/pull/377>`__\n* Allow `TreeMesh.__repr__` to run when non finalized by @santisoler in `#393 <https://github.com/simpeg/discretize/pull/393>`__\n* Add safeguards to TreeMesh properties by @santisoler in `#394 <https://github.com/simpeg/discretize/pull/394>`__\n* Switches to libc math import by @jcapriot in `#396 <https://github.com/simpeg/discretize/pull/396>`__\n* 0.11.3 staging by @jcapriot in `#3967 <https://github.com/simpeg/discretize/pull/397>`__\n"
  },
  {
    "path": "docs/release/0.12.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.12.0_notes:\n\n===================================\n``discretize`` 0.12.0 Release Notes\n===================================\n\nOctober 8, 2025\n\nThis minor release contains many bugfixes and updates related to new package builds.\n\nPython versions\n---------------\n`discretize` has bumped its minimum supported `python` version to 3.11, and is tested against versions 3.11-3.14. Minimum scipy versions\nhave also been bumped to 1.12. Users on older python versions should continue to use `discretize` 0.11.x.\n\nWe have also added support for (and tests against) free-threaded python builds for python 3.13 and later, which should be available through\nthe normal python distribution channels (pypi, conda-forge).\n\n``TreeMesh`` updates\n--------------------\nTree meshes now support a `refine_image` method that allows users to refine a mesh based on an image (2D or 3D numpy array). See\n:func:``discretize.TreeMesh.refine_image`` for more details.\n\n``TensorMesh`` updates\n----------------------\nA new :func:``discretize.TensorMesh.point2index`` method has been added to convert a point location to the corresponding cell index in\na tensor mesh, similar to the existing :func:``discretize.TreeMesh.point2index`` method.\n\n\nContributors\n============\n\n* @jcapriot\n\nPull requests\n=============\n* bump sphinx and pydata-sphinx to more recent versions by @jcapriot in `#402 <https://github.com/simpeg/discretize/pull/402>`__\n* Small cleanups to the external TreeMesh code, no functionality changes by @jcapriot in `#400 <https://github.com/simpeg/discretize/pull/400>`__\n* Add point2index functionality for `tensor_mesh` by @jcapriot in `#401 <https://github.com/simpeg/discretize/pull/401>`__\n* Updates for python free threading by @jcapriot in `#403 <https://github.com/simpeg/discretize/pull/403>`__\n* Updates for cibuildwheel by @jcapriot in `#405 <https://github.com/simpeg/discretize/pull/405>`__\n* Add functionality to refine a `TreeMesh` using an \"image\" by @jcapriot in `#406 <https://github.com/simpeg/discretize/pull/406>`__\n"
  },
  {
    "path": "docs/release/0.4.12-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.4.12_notes:\n\n===================================\n``discretize`` 0.4.12 Release Notes\n===================================\n\nJune 6, 2020\n\nThis patch release is for a few small bugs and code speed improvements.\n\nThere is now a fast function for return active cell indexes for ``TensorMesh``-s,\n``TreeMesh``-s, and symmetric ``CylMesh``-s below a topography surface defined by\nscattered points, ``discretize.utils.active_from_xyz``.\n\nThere is also a bug fix of `#197 <https://github.com/simpeg/discretize/issues/197>`__\nfor the ``discretize.utils.refine_tree_xyz`` helper function on QuadTree meshes\nwhen attempting to extend the padding vertically using ``method=surface``. There\nwas also a bug when reading in a 2D UBC mesh that would only manifest if the UBC\nmesh's origin wasn't at the surface (`#194 <https://github.com/simpeg/discretize/issues/194>`__).\n\nWe have updated links as we are now using discourse over google groups as a means\nfor users to ask for general help. A few links to the SimPEG documentation needed to be updated for the re-organization of\nthe examples folders.\n\nWe are removing ``pymatsolver`` from the list of explicit dependancies for ``discretize``.\nIt is **highly** recommended, but it isn't actually required to run anything\nwithin the ``discretize`` package.\n\n\nContributors\n============\n\n* @domfournier/@fourndo\n* @jcapriot\n* @lheagy\n* @prisae\n\nBug Fixes\n=========\n\n* `#194 <https://github.com/simpeg/discretize/issues/194>`__: z-dimension in write/readUBC\n* `#197 <https://github.com/simpeg/discretize/issues/197>`__: Amount of cells not changing when changing Octree levels in utils.refine_tree_xyz\n\nPull requests\n=============\n\n* `#193 <https://github.com/simpeg/discretize/pull/193>`__: Remove pymatsolver\n* `#198 <https://github.com/simpeg/discretize/pull/198>`__: Refine tree xyz\n* `#201 <https://github.com/simpeg/discretize/pull/201>`__: Update README.rst\n* `#203 <https://github.com/simpeg/discretize/pull/203>`__: consolidating tests on travis\n* `#205 <https://github.com/simpeg/discretize/pull/205>`__: Update requirements\n* `#206 <https://github.com/simpeg/discretize/pull/206>`__: Bug fix for 2D mesh read in.\n* `#207 <https://github.com/simpeg/discretize/pull/207>`__: update release notes\n"
  },
  {
    "path": "docs/release/0.4.13-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.4.13_notes:\n\n===================================\n``discretize`` 0.4.13 Release Notes\n===================================\n\nJune 22, 2020\n\nThis release contains some bug fixes:\n\nFirst, we have squashed a few bugs related to serializing the TreeMesh to a json\nobject. ``tree_mesh.save`` should now work. We have also added the ability to\nwrite out a 2D ``TreeMesh`` to a UBC-GIF-like format. There is no official QuadTree\nmesh from UBC-GIF however, so this just mimics the 3D format for a 2D mesh.\n\nThere was also a small bug in reading in a 2D UBC TensorMesh model when the values\nwere split over multiple lines. We have altered the logic here so as to be\nindependant of the structure of that file, as the values should always appear in a\nspecific order.\n\n\nContributors\n============\n\n* @dccowan\n* @jcapriot\n* @prisae\n\n\nPull requests\n=============\n\n* `#204 <https://github.com/simpeg/discretize/pull/204>`__: Remove restriction of sphinx < 2\n* `#208 <https://github.com/simpeg/discretize/pull/208>`__: Create IO for 2D Tree mesh in UBC-like format\n* `#209 <https://github.com/simpeg/discretize/pull/209>`__: small bug fix for 2D UBC TensorMesh model readin\n"
  },
  {
    "path": "docs/release/0.4.14-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.4.14_notes:\n\n===================================\n``discretize`` 0.4.14 Release Notes\n===================================\n\nJuly 4, 2020\n\nThis release is renaming a few of the options to the plotting routines to more\npep8 friendly names. The older name will still function, but throw a deprecation warning\n\n* ``vType`` → ``v_type``\n* ``pcolorOpts`` → ``pcolor_opts``\n* ``streamOpts`` → ``stream_opts``\n* ``gridOpts`` → ``grid_opts``\n* ``showIt`` → ``show_it``\n* ``annotationColor`` → ``annotation_color``\n\nA small release adding the ability to specify the ``norm`` option for\nthe ``pcolor_opts`` dictionary argument to ``TreeMesh.plotImage``.\n\nContributors\n============\n\n* @jcapriot\n\n\nPull requests\n=============\n\n* `#211 <https://github.com/simpeg/discretize/pull/211>`__: Add ability to handle \"norm\" keyword in pcolor_opts on the TreeMesh\n"
  },
  {
    "path": "docs/release/0.4.15-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.4.15_notes:\n\n===================================\n``discretize`` 0.4.15 Release Notes\n===================================\n\nJuly 23, 2020\n\nThis is a minor release to fix a small error in the deprecated vType argument\n\nContributors\n============\n\n* @jcapriot\n\n\nPull requests\n=============\n\n* `#213 <https://github.com/simpeg/discretize/pull/213>`__: Fix bug in deprecated vType\n"
  },
  {
    "path": "docs/release/0.5.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.5.0_notes:\n\n===================================\n``discretize`` 0.5.0 Release Notes\n===================================\n\nSeptember 2, 2020\n\nThis minor release has a few small bug fixes as well as a new volume averaging\noperator.\n\nThe Volume Averaging operator has been implemented for arbitrary `TensorMesh`,\n`TreeMesh`, and combinations of them. It is defined as being a mass conserving\noperation. More details can be found in its documentation, :func:`discretize.utils.volume_average`.\n\nThere are also some updates for the new deprecations in ``matplotlib`` to hopefully\nthrow less deprecation warnings internally. There are still a few left which are\non our radar to fix in the next patch.\n\nWe are also dropping support for python 3.5 which will reach end-of-life within\na few weeks.\n\nContributors\n============\n\n* @jcapriot\n* @prisae\n* @bluetyson\n\n\nPull requests\n=============\n\n* `#212 <https://github.com/simpeg/discretize/pull/212>`__: Volume average\n* `#216 <https://github.com/simpeg/discretize/pull/216>`__: Update 2_tensor_mesh.py\n* `#217 <https://github.com/simpeg/discretize/pull/217>`__: Fix Slicer matplotlib-warning.\n* `#220 <https://github.com/simpeg/discretize/pull/220>`__: 0.5.0 release notes and requirements update\n"
  },
  {
    "path": "docs/release/0.5.1-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.5.1_notes:\n\n===================================\n``discretize`` 0.5.1 Release Notes\n===================================\n\nSeptember 4, 2020\n\nA small patch to fix a bug in the volume averaging operator when using\nmeshes with different shaped bases, to address `#222 <https://github.com/simpeg/discretize/issues/222>`__.\n\nThere are also a few deprecated syntax updates for comparisons with literals.\n`#221 <https://github.com/simpeg/discretize/issues/221>`__.\n\nContributors\n============\n\n* @jcapriot\n\n\nPull requests\n=============\n\n* `#223 <https://github.com/simpeg/discretize/pull/223>`__: Syntax and bug fixes\n"
  },
  {
    "path": "docs/release/0.6.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.6.0_notes:\n\n===================================\n``discretize`` 0.6.0 Release Notes\n===================================\n\nNovember 16, 2020\n\nThis minor release is intended to bring consistent pep8 style naming across all\nof discretize's classes and functions.\n\nThere are two major types of renaming, aliases and deprecations. We have chosen\nto move to more descriptive property names for classes, generally.\nFor example, ``mesh.area`` is deprecated and now is ``mesh.face_area``. Also\nproperties like ``mesh.vnC`` are now officially ``mesh.shape_cells`` due to the\nmore descriptive name, but can also be accessed as ``mesh.vnC`` to speed up some\ncode writing for users. We have included a full list of aliases and deprecations\nbelow. In PR `#227 <https://github.com/simpeg/discretize/pull/227>`__ we have detailed\nour reasonings behind individual name choices.\n\nThe other big change that will likely cause previous code to break is that all of\nthese ``mesh.shape_*`` type properties are now explicitly ``tuple``-s, making\nthem immutable. These properties could previously be modified which would result\nin undefined and unsafe behavoir. A side effect of this, is that any code that\nrelied on these properties being ``numpy.ndarray``-s, will break. This is\nintentional.\n\nThere's a few internal changes as well, to reorganize the file structure. importing\nitems in ``discretize.utils`` from their individual module files is not recommended\nand might result in future broken code. Please only import these items from the\n``discretize.utils`` module.\n\nWe have also separated the ``matplotlib`` plotting code into a separate module:\n``discretize.utils.mixins.mpl_mod``. At the same time we have further improved\nthe plotting speed of ``discretize.TreeMesh`` and ``discretize.CurvilinearMesh``.\nThis also allows all of these functions to have a unified calling convention.\n\nFinally, we have removed assert errors in favor of throwing the proper exception\nwhen checking inputs. We have removed all references to ``__future__`` and ``six``\nto clean up and drop python 2 compatibility.\n\n.. note::\n\n\tTesting now uses Azure CI\n\nChanges\n=======\nThis is a full list of the aliases and deprecations for this ``discretize`` release.\n\nAliases\n-------\nOn ``discretize.base.BaseMesh``:\n\n* ``origin=x0``\n* ``n_cells=nC``\n* ``n_nodes=nN``\n* ``n_edges=nE``\n* ``n_faces=nF``\n* ``n_edges_x=nEx``, ``n_edges_y=nEy``, ``n_edges_z=nEz``\n* ``n_faces_x=nFx``, ``n_faces_y=nFy``, ``n_faces_z=nFz``\n* ``n_edges_per_direction=vnE``, ``n_faces_per_direction=vnF``\n\nOn ``discretize.base.BaseRectangularMesh``:\n\n* ``shape_cells=vnC``\n* ``shape_nodes=vnN``\n* ``shape_edges_x=vnEx``, ``shape_edges_y=vnEy``, ``shape_edges_z=vnEz``\n* ``shape_faces_x=vnFx``, ``shape_faces_y=vnFy``, ``shape_faces_z=vnFz``\n\nOn ``discretize.base.BaseTensorMesh``:\n\n* ``cell_centers=gridCC``\n* ``nodes=gridN``\n* ``edges_x=gridEx``, ``edges_y=gridEy``, ``edges_z=gridEz``\n* ``faces_x=gridFx``, ``faces_y=gridFy``, ``faces_z=gridFz``\n\nOn ``discretize.operators.DiffOperators``:\n\n* ``average_face_to_cell=aveF2CC``, ``average_face_to_cell_vector=aveF2CCV``\n* ``average_face_x_to_cell=aveFx2CC``, ``average_face_y_to_cell=aveFy2CC``, ``average_face_z_to_cell=aveFz2CC``\n* ``average_cell_to_face=aveCC2F``, ``average_cell_vector_to_face=aveCCV2F``\n* ``average_edge_to_cell=aveE2CC``, ``average_edge_to_cell_vector=aveE2CCV``\n* ``average_edge_x_to_cell=aveEx2CC``, ``average_edge_y_to_cell=aveEy2CC``, ``average_edge_z_to_cell=aveEz2CC``\n* ``average_node_to_cell=aveN2CC``, ``average_node_to_edge=aveN2E``, ``average_node_to_face=aveN2F``\n\nOn ``TreeMesh``:\n\n* Similar to above, all ``n_hanging_XXX=nhX``, and ``n_total_XXX=ntX``, for each ``node=N``, ``face_x,y,z=Fx,y,z``, and ``edge_x,y,z=Ex,y,z``\n* Also, similar to above: ``hanging_XXX=gridhX``, for each hanging ``node=N``, ``face_x,y,z=Fx,y,z`` and ``edge_x,y,z=Ex,y,z``\n\nThese aliases are consistent across all meshes that inherit from these classes:\n``discretize.TensorMesh``, ``discretize.CurvilinearMesh``, ``discretize.CylindricalMesh``,\nand ``discretize.TreeMesh``.\n\nDeprecations\n------------\nThese deprecations will give ``FutureWarning`` when called indicating that they\nwill be removed in version 1.0.0 of ``discretize``.\n\nBase Classes\n************\n\nOn ``discretize.base.BaseMesh``:\n\n* ``normals`` → ``face_normals``\n* ``tangents`` → ``edge_tangents``\n* ``projectEdgeVector`` → ``project_edge_vector``\n* ``projectFaceVector`` → ``project_face_vector``\n\nOn  ``discretize.base.BaseRectangularMesh``:\n\n* ``r`` → ``reshape``\n* ``nCx`` → ``shape_cells[0]``, ``nCy`` → ``shape_cells[1]``, ``nCz`` → ``shape_cells[2]``\n* ``nNx`` → ``shape_nodes[0]``, ``nNy`` → ``shape_nodes[1]``, ``nNz`` → ``shape_nodes[2]``\n* ``hx``→``h[0]``, ``hy`` → ``h[1]``, ``hz``→ ``h[2]``\n\nOn ``discretize.base.BaseTensorMesh``:\n\n* ``vectorNx`` → ``nodes_x``, ``vectorNy`` → ``nodes_y``, ``vectorNz`` → ``nodes_z``\n* ``vectorCCx`` → ``cell_centers_x``, ``vectorCCy`` → ``cell_centers_y``, ``vectorCCz`` → ``cell_centers_z``\n* ``getInterpolationMat`` → ``get_interpolation_matrix``\n* ``isInside`` → ``is_inside``\n* ``getTensor`` → ``get_tensor``\n\nOn ``discretize.base.MeshIO``:\n\n* ``readUBC`` → ``read_UBC``\n* ``readModelUBC`` → ``read_model_UBC``\n* ``writeUBC`` → ``write_UBC``\n* ``writeModelUBC`` → ``write_model_UBC``\n\nOn ``discretize.operators.DiffOperators``:\n\n* ``cellGrad`` → ``cell_gradient``\n* ``cellGradBC`` → ``cell_gradient_BC``\n* ``cellGradx`` → ``cell_gradient_x``, ``cellGrady`` → ``cell_gradient_y``, ``cellGradz`` → ``cell_gradient_z``\n* ``nodalGrad`` → ``nodal_gradient``\n* ``nodalLaplacian`` → ``nodal_laplacian``\n* ``faceDiv`` → ``face_divergence``\n* ``faceDivx`` → ``face_x_divergence``, ``faceDivy`` → ``face_y_divergence``, ``faceDivz`` →``face_z_divergence``\n* ``edgeCurl`` → ``edge_curl``\n* ``setCellGradBC`` → ``set_cell_gradient_BC``\n* ``getBCProjWF`` → ``get_BC_projections``\n* ``getBCProjWF_simple`` → ``get_BC_projections_simple``\n\nOn ``discretize.operators.InnerProducts``:\n\n* ``getFaceInnerProduct`` → ``get_face_inner_product``\n* ``getEdgeInnerProduct`` → ``get_edge_inner_product``\n* ``getFaceInnerProductDeriv`` → ``get_face_inner_product_deriv``\n* ``getEdgeInnerProductDeriv`` → ``get_edge_inner_product_deriv``\n\nMain Meshes\n***********\n\n``CylMesh`` → ``CylindricalMesh``\n\nOn ``discretize.TensorMesh``, ``discretize.CylindricalMesh``, ``discretize.TreeMesh``, ``discretize.CurvilinearMesh``:\n\n* ``vol`` → ``cell_volumes``\n* ``area`` → ``face_areas``\n* ``edge`` → ``edge_lengths``\n\nOn ``discretize.TensorMesh``, ``discretize.CylindricalMesh``, ``discretize.TreeMesh``:\n\n*  ``areaFx`` → ``face_x_areas``, ``areaFy`` → ``face_y_areas``, ``areaFz`` →``face_z_areas``\n*  ``edgeEx`` → ``edge_x_lengths``, ``edgeEy`` → ``edge_y_lengths``, ``edgeEz`` →``edge_z_lengths``\n\nOn ``discretize.TensorMesh``, ``discretize.TreeMesh``:\n\n* ``faceBoundaryInd`` → ``face_boundary_indices``\n* ``cellBoundaryInd`` → ``cell_boundary_indices``\n\nOn ``discretize.CurvilinearMesh``:\n* The ``nodes`` property is now ``node_list`` to avoid the name clash with the ``nodes`` location property\n\nOn ``discretize.CylindricalMesh``:\n\n* ``isSymmetric`` → ``is_symmetric``\n* ``cartesianOrigin`` → ``cartesian_origin``\n* ``getInterpolationMatCartMesh`` → ``get_interpolation_matrix_cartesian_mesh``\n* ``cartesianGrid`` → ``cartesian_grid``\n\nOn ``discretize.TreeMesh``:\n\n* ``maxLevel`` → ``max_used_level``\n* ``permuteCC`` → ``permute_cells``\n* ``permuteF`` → ``permute_faces``\n* ``permuteE`` → ``permute_edges``\n\nAnd for plotting with ``matplotlib``:\n\n* ``plotGrid`` → ``plot_grid``\n* ``plotImage`` → ``plot_image``\n* ``plotSlice`` → ``plot_slice``\n\n\nUtilities deprecations\n**********************\nDeprecations inside ``discretize.utils``:\n\n* ``isScalar`` → ``is_scalar``\n* ``asArray_N_x_Dim`` → ``as_array_n_by_dim``\n* ``sdInv`` → ``sdinv``\n* ``getSubArray`` → ``get_subarray``\n* ``inv3X3BlockDiagonal`` → ``inverse_3x3_block_diagonal``\n* ``inv2X2BlockDiagonal`` → ``inverse_2x2_block_diagonal``\n* ``makePropertyTensor`` → ``make_property_tensor``\n* ``invPropertyTensor`` → ``inverse_property_tensor``\n* ``exampleLrmGrid`` → ``example_curvilinear_grid``\n* ``meshTensor`` → ``unpack_widths``\n* ``closestPoints`` → ``closest_points_index``\n* ``ExtractCoreMesh`` → ``extract_core_mesh``\n* ``volTetra`` → ``volume_tetrahedron``\n* ``indexCube`` → ``index_cube``\n* ``faceInfo`` → ``face_info``\n* ``interpmat`` → ``interpolation_matrix``\n* ``rotationMatrixFromNormals`` → ``rotate_points_from_normals``\n* ``rotatePointsFromNormals`` → ``rotation_matrix_from_normals``\n\nContributors\n============\n\n* @jcapriot\n\nWith reviews from:\n\n* @prisae\n* @lheagy\n\nAlso, input on function names were given by many of the ``discretize`` developers.\n\nPull requests\n=============\n\n* `#227 <https://github.com/simpeg/discretize/pull/227>`__: Restructure.\n"
  },
  {
    "path": "docs/release/0.6.1-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.6.1_notes:\n\n===================================\n``discretize`` 0.6.1 Release Notes\n===================================\n\nNovember 17, 2020\n\nThis patch release adds deprecation for 3 of the hidden properties on the TreeMesh.\nThese properties are now publicly accessible.\n\n* ``_aveCC2FxStencil`` → ``average_cell_to_total_face_x``\n* ``_aveCC2FyStencil`` → ``average_cell_to_total_face_y``\n* ``_aveCC2FzStencil`` → ``average_cell_to_total_face_z``\n\nIn addition, we have promoted the cell gradient stencils to publicly accessible as:\n\n* ``_cellGradStencil`` → ``stencil_cell_gradient``\n* ``_cellGradxStencil`` → ``stencil_cell_gradient_x``\n* ``_cellGradyStencil`` → ``stencil_cell_gradient_y``\n* ``_cellGradzStencil`` → ``stencil_cell_gradient_z``\n\nWe continue to discourage against accessing hidden variables as there is no guarantee\nthat their name will stay the same.\n\nContributors\n============\n\n* @jcapriot\n\nPull requests\n=============\n\n* `#229 <https://github.com/simpeg/discretize/pull/229>`__: Quick Patch for stencil matrices.\n"
  },
  {
    "path": "docs/release/0.6.2-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.6.2_notes:\n\n===================================\n``discretize`` 0.6.2 Release Notes\n===================================\n\nNovember 26, 2020\n\nThis patch release changes ``FutureWarning`` to ``DeprecationWarning`` to temporarily\nhide them from end users. Our current plan is to switch them back to ``FutureWarning`` on\nthe next minor release. We encourage developers to update their code prior to this.\n\nContributors\n============\n\n* @jcapriot\n\nPull requests\n=============\n\n* `#231 <https://github.com/simpeg/discretize/pull/231>`__: Deprecate warnings\n"
  },
  {
    "path": "docs/release/0.6.3-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.6.3_notes:\n\n===================================\n``discretize`` 0.6.3 Release Notes\n===================================\n\nMarch 1, 2021\n\nThis patch contains a bug-fix related to plotting on a :class:`TreeMesh`.\nIt also contains a few additions which caused a minor functionality change in\n:func:`utils.refine_tree_xyz`.\n\nThe first convenience is the addition of a `slice_loc` parameter to the\n:func:`mixins.mpl_mod.InterfaceMPL.plot_slice`. This allows for slightly more\nintuitive use over the old `ind` keyword, by allowing the user to directly specify the\nlocation parameter of the slice.\n\nThe second addition, which caused the minor functionality change in `refine_tree_xyz`,\nis the addition of two specialized refine functions for the `TreeMesh`. We can now directly\nrefine all cells that intersect a box (or list of boxes) with :func:`TreeMesh.refine_box`,\nand similarly we can refine all cells that intersect a ball (or list of balls) with\n:func:`TreeMesh.refine_ball`.\n\n\nContributors\n============\n\n* @jcapriot\n* @ckohnke\n* @Rockpointgeo\n* @thast\n* @domfournier\n\nPull requests\n=============\n\n* `#200 <https://github.com/simpeg/discretize/pull/200>`__: Refine tree xyz box\n* `#235 <https://github.com/simpeg/discretize/pull/235>`__: add xyzslice keyword to mesh.plot_slice\n* `#236 <https://github.com/simpeg/discretize/pull/236>`__: Correct the range_y in Tree plot_image\n* `#237 <https://github.com/simpeg/discretize/pull/235>`__: Tree refine c\n"
  },
  {
    "path": "docs/release/0.7.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.7.0_notes:\n\n===================================\n``discretize`` 0.7.0 Release Notes\n===================================\n\nApril 30, 2021\n\nThis Minor release has several new features, including support for boundary conditions,\nquiver vector plotting for :func:`TreeMesh.plot_slice`, and a rather large change in\nremoving the ``properties`` requirement from ``discretize``.\n\n\nRemoval of ``properties``\n-------------------------\nStarting with the largest change, we have made the decision to remove the properties\nbackend of `discretize`. Our main motivation for this was to give ourselves more control\nover the documentation of classes. Our goal was to recreate the functionality without\nany adverse effects to our users, and have taken many steps to hopefully address any\npossible issues with the conversion. That being said, please raise an issue on the\ngithub page if you run into any unexpected issues.\n\nThe next step here is to finalize the documentation..\n\nBoundary Conditions\n-------------------\nWe have added an improved support for implementing boundary conditions for the finite\nvolume formulation that underlies ``discretize`` on :class:`TreeMesh`, :class:`TensorMesh`\n, and :class:`CurvilinearMesh`.\n\nIn general, ``discretize`` often makes use of the following two identities for the weak\nform of the finite volume:\n\n.. math::\n    \\int_\\Omega \\nabla u \\cdot \\vec{v} dV = -\\int_\\Omega u \\nabla \\cdot \\vec{v} dV\n    + \\int_{\\partial\\Omega} u \\vec{v}\\cdot \\hat{n} dA\n\n.. math::\n    \\int_\\Omega (\\nabla \\times \\vec{w}) \\cdot \\vec{v} dV = \\int_\\Omega \\vec{w} \\cdot (\\nabla \\times \\vec{v}) dV\n    - \\int_{\\partial\\omega} (\\vec{w} \\times \\hat{n}) \\cdot \\vec{v} dA\n\nPreviously we focused on approximating the volume integrals in the above equations,\nhowever to implement boundary conditions, we use the boundary face integrals!\n\nAs part of this, meshes now have a few new properties:\n\n* ``boundary_faces``\n* ``boundary_face_outward_normals``\n* ``boundary_edges``\n* ``boundary_nodes``\n* ``average_node_to_face``\n* ``average_edge_to_face_vector``\n* ``project_edge_to_boundary_edge``\n* ``project_face_to_boundary_face``\n* ``project_node_to_boundary_node``\n\nTogether these are used in a few items that are correspond to the mass matrices,\n:func:`operators.InnerProducts.get_edge_inner_product` and\n:func:`operators.InnerProducts.get_face_inner_product`. These relate to the item that\nthey are operating on, and return the necessary matrix to integrate that quantity on\nthe default boundaries of the meshes.\n\n* ``boundary_face_scalar_integral``\n* ``boundary_node_vector_integral``\n* ``boundary_edge_vector_integral``\n\nYou can investigate the source code of these functions to see how they are built if you\nneed to design your own customized boundary.\n\nAll together we have also implemented two helper operators that can be used to reproduce\ncommon types of boundary conditions for a few PDE's. These are based on Robin type\nconditions that can flexibly support multiple types of boundary conditions depending on\nthe discrete value's locations.\n\n* :func:`operators.DiffOperators.cell_gradient_weak_form_robin`\n* :func:`operators.DiffOperators.edge_divergence_weak_form_robin`\n\nFuture Work\n===========\nWith the removal of ``properties`` we will be updating the documentation to be more\nexplicit, but in the meantime this will make some items look less clear.\nWe will also be pushing out some tutorials on how to use the boundary conditions\nto solve the boundary value PDE's. In the meantime though, you can look at the boundary\ncondition test codes where we form a few PDE's for convergence tests.\n\nContributors\n============\n\n* @jcapriot\n\nWith reviews from:\n\n* @prisae\n* @domfournier/@fourndo\n* @lheagy\n\nPull requests\n=============\n* `#232 <https://github.com/simpeg/discretize/pull/232>`__: Exorcise Properties\n* `#234 <https://github.com/simpeg/discretize/pull/234>`__: Boundary conditions\n* `#240 <https://github.com/simpeg/discretize/pull/240>`__: Tree quiver plot\n* `#241 <https://github.com/simpeg/discretize/pull/241>`__: Update azure-pipelines.yml\n"
  },
  {
    "path": "docs/release/0.7.1-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.7.1_notes:\n\n===================================\n``discretize`` 0.7.1 Release Notes\n===================================\n\nOctober 7, 2021\n\nThis patch release is a big step, but with minimal functional changes. There are a few\nsmall bug fixes, but the largest news is the updated documentation!\n\nNew Documentation\n-----------------\nThe documentation for every module, class, function, etc. has all been unified to\nnumpy styled documentation. Many functions and methods now have small examples\nwithin their docstrings that show simple usage. We have also update the layout of the\ndocumentation to match the new ``pydata`` community layout.\n\nBug Fixes\n---------\nWe previously re-intoduced a small bug in the ``mixins.mpl.plot_3d_slicer``\nfunctionality that would caused the colorscales to not match for a homogenous model.\nThis has be re-fixed.\n\nWe also now additionally return the quiver plot object when vector plotting using the\n```plot_slice`` on a ``TreeMesh``\n\nContributors\n============\n\n* @jcapriot\n* @dcowan\n* @lheagy\n* @prisae\n\nPull requests\n=============\n\n* `#253 <https://github.com/simpeg/discretize/pull/253>`__: Numpy docstrings api\n* `#256 <https://github.com/simpeg/discretize/pull/256>`__: Update mpl_mod.py\n* `#258 <https://github.com/simpeg/discretize/pull/258>`__: Numpy docstrings api review\n* `#262 <https://github.com/simpeg/discretize/pull/262>`__: Fix wrong colour for fullspaces - again /\n* `#264 <https://github.com/simpeg/discretize/pull/264>`__: patch for fullspace slicer colorscales\n"
  },
  {
    "path": "docs/release/0.7.2-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.7.2_notes:\n\n===================================\n``discretize`` 0.7.2 Release Notes\n===================================\n\nDecember 6, 2021\n\nThis patch release is for a minor improvements to a few functions\n\nSearching\n---------\nThe function ``closest_points_index`` now exists on the `base.BaseMesh`, with a default\nimplementation based on a KDTree lookup. This allows for much faster repeated calls.\nThe previous function one off function will now point to this method on the mesh.\n\nNegative Levels\n---------------\nTo referring to levels on ``TreeMesh`` refine functions, you can now pass negative\nintegers to refer to the maximum refine level (similar to negative indexing on arrays)\n\nContributors\n============\n\n* @jcapriot\n* @ngodber\n\nPull requests\n=============\n\n* `#251 <https://github.com/simpeg/discretize/pull/251>`__: refactor closest_points_index to use cKDTree.\n* `#267 <https://github.com/simpeg/discretize/pull/267>`__: TreeMesh negative levels\n* `#270 <https://github.com/simpeg/discretize/pull/270>`__: ensure the grid locations are (N x dim) happens in 1D\n"
  },
  {
    "path": "docs/release/0.7.3-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.7.3_notes:\n\n===================================\n``discretize`` 0.7.3 Release Notes\n===================================\n\nFebruary 28, 2022\n\nThis patch release fixes a few minor bugs related to the edge curl operator in 2D for\n```TensorMesh`` which was not properly indexed as edges. There is also minor additions\nto the functionality of cylindrical meshes, allowing the user to set an origin point\nfor the rotational component. Finally, there is an added dot product test to simplify\nfuture development and testing.\n\nContributors\n============\n\n* @jcapriot\n* @prisae\n\nPull requests\n=============\n\n* `#268 <https://github.com/simpeg/discretize/pull/268>`__: ENH: Actually use origin[1] for cylindrical mesh\n* `#271 <https://github.com/simpeg/discretize/pull/271>`__: update 2D edge curl operator to properly go from 2D edges to Z-faces\n* `#272 <https://github.com/simpeg/discretize/pull/272>`__: Add dot product test.\n"
  },
  {
    "path": "docs/release/0.7.4-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.7.4_notes:\n\n===================================\n``discretize`` 0.7.4 Release Notes\n===================================\n\nApril 20, 2022\n\nThis bug fix patch addressed an issue accessing the face indices of a ``TreeCell``.\nWe are also testing a new versioning system that is based off of git tags. If you are\na developer ``setuptools_scm`` is now a requirement along with an installation of\n``git`` that is discoverable from the command line.\n\nContributors\n============\n\n* @jcapriot\n* @prisae\n* @thibaut-kobold\n\nPull requests\n=============\n\n* `#273 <https://github.com/simpeg/discretize/pull/273>`__: Move from bumpversion to setuptools_scm\n* `#276 <https://github.com/simpeg/discretize/pull/276>`__: fix faces property of TreeCell\n"
  },
  {
    "path": "docs/release/0.8.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.8.0_notes:\n\n===================================\n``discretize`` 0.8.0 Release Notes\n===================================\n\nMay 12, 2022\n\nThis minor release introduces a new mesh type to discretize, :class:`SimplexMesh`!\n\n``SimplexMesh``\n---------------\n``discretize`` now has support for triangular (2D) and tetrahedral (3D) meshes. These meshes\nsupport most of the operations that you would expect of a discretize mesh that is used\nto solve PDEs with the finite volume method. You have access to :func:`SimplexMesh.face_divergence`,\n:func:`SimplexMesh.nodal_gradient`, and :func:`SimplexMesh.edge_curl` operators, along\nwith the expected inner product operators and their derivatives: :func:`SimplexMesh.get_edge_inner_product`,\n:func:`SimplexMesh.get_face_inner_product`, :func:`SimplexMesh.get_edge_inner_product_deriv`,\nand :func:`SimplexMesh.get_face_inner_product_deriv`. They contain the expected average\noperators that move between nodes, cell centers, faces, and edges. The interpolation\noperator can interpolate scalar values from nodes and cell centers, and interpolate vector\nquantities from edges and faces. The mesh also has operators to handle the boundary\nconditions in manners similar to the previous implementation.\n\nThe basic format of input into a :class:`SimplexMesh` is an array of node locations,\nand an array of the simplex indices, enabling for simple interaction with many different\nmesh generation libraries. There are readers for unstructured VTK files which contain\nall of either triangular or tetrahedral elements.\n\n``TreeMesh``\n------------\nInterpolation on :class:`TreeMesh` is now linear between cells of the same level for the\ncell centered, edge, and face interpolators.\n\n\nFuture Warnings\n===============\nAll of the previous Deprecation Warnings from the refactor of ``discretize`` to pep8\nfriendly names in v0.6.0 have been changed to Future Warnings in preparation for the\n1.0.0 release of discretize.\n\nContributors\n============\n\n* @jcapriot\n\nPull requests\n=============\n* `#263 <https://github.com/simpeg/discretize/pull/263>`__: Unstructured Triangular/Tetrahedral meshes\n* `#277 <https://github.com/simpeg/discretize/pull/277>`__: Updating TreeMesh Interpolation\n* `#278 <https://github.com/simpeg/discretize/pull/278>`__: 0.8.0 Release\n"
  },
  {
    "path": "docs/release/0.8.1-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.8.1_notes:\n\n===================================\n``discretize`` 0.8.1 Release Notes\n===================================\n\nAugust 16, 2022\n\nThis patch release, in addition to some small bug fixes and runtime improvements implements,\nalso implements some missing functionality for cylindrical meshes.\n\n``CylindricalMesh``\n-------------------\nThe big news is that 3D cylindrical meshes can now be output to a vtk format, which\nrepresents the wedges and arced cells as rational bezier curves in an unstructured vtk\nmesh.\n\nThere is now full interpolation functionality when using 3D meshes, that includes\nappropriate interpolation that wraps around the angular component across the cylindrical\nmesh. The 3D nodal gradient operator is also implemented now.\n\nOn the backend, several internal matrices should now build much quicker than before.\n\nStartup\n------------\nSince ``matplotlib``, ``vtk`` and ``omf`` libraries have been made optional, they are now\nonly imported when the corresponding functionality is actually used. These means a\nreasonable improvement in the speed on the first import of ``discretize``.\n\n\nBug Fixes\n---------\n\n - 3D ``TreeMesh`` now generates the correct list of nodes in each cell\n - A missed internal deprecation for `zerosOutside` has been cleaned up\n - We've added python 3.10 to the testing sweet.\n - Bumped the minimum version of python to 3.7 to match the tests.\n\n\nContributors\n============\n\n* @jcapriot\n* @prisae\n* @lheagy\n\nPull requests\n=============\n* `#280 <https://github.com/simpeg/discretize/pull/280>`__: Nodal_Gradient for the CylindricalMesh\n* `#281 <https://github.com/simpeg/discretize/pull/281>`__: Cyl vtk\n* `#283 <https://github.com/simpeg/discretize/pull/283>`__: add python 3.10 to testing suite\n* `#284 <https://github.com/simpeg/discretize/pull/284>`__: Improve load time\n* `#285 <https://github.com/simpeg/discretize/pull/285>`__: zeros_outside\n* `#286 <https://github.com/simpeg/discretize/pull/286>`__: Cell node tree\n* `#288 <https://github.com/simpeg/discretize/pull/288>`__: Allow ``np.int_``\n* `#289 <https://github.com/simpeg/discretize/pull/289>`__: 0.8.1 Release\n"
  },
  {
    "path": "docs/release/0.8.2-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.8.2_notes:\n\n===================================\n``discretize`` 0.8.2 Release Notes\n===================================\n\nAugust 17, 2022\n\nThis patch release fixes a small bug in importing the ``importlib.util`` library\n\nBug Fixes\n---------\n\n - ``importlib.util`` import should now work\n\n\nContributors\n============\n\n* @jcapriot\n\nPull requests\n=============\n* `#291 <https://github.com/simpeg/discretize/pull/291>`__: Importlib import fix\n"
  },
  {
    "path": "docs/release/0.8.3-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.8.3_notes:\n\n===================================\n``discretize`` 0.8.3 Release Notes\n===================================\n\nJanuary 27, 2023\n\nThis is a patch release with several bugfixes, as well as additions to the\n``TreeMesh``.\n\n``TreeMesh`` improvements\n-------------------------\n\nWe have added several methods to the :class:`discretize.TreeMesh` that\nprovide specialize geometric refinement entities including:\n\n- Refine along a line segment path, :meth:`~discretize.TreeMesh.refine_line`\n- Refine on triangle intersection, :meth:`~discretize.TreeMesh.refine_triangle`\n- Refine on tetrahedron interseciton, :meth:`~discretize.TreeMesh.refine_tetrahedron`\n- Refine on vertical triangular prism, :meth:`~discretize.TreeMesh.refine_vertical_trianglular_prism`\n\nThese enabled more accurate functions for refining along surfaces. Therefore we\nhave deprecated the :meth:`discretize.utils.refine_tree_xyz` which was essentially\nthree different functions, in favor of separate and distinct methods on the ``TreeMesh``\nitself which all allow for padding at different levels.:\n\n- Refine on surface, :meth:`~discretize.TreeMesh.refine_surface`\n- Refine on bounding box of points, :meth:`~discretize.TreeMesh.refine_bounding_box`\n- Refine around points, :meth:`~discretize.TreeMesh.refine_points`\n\nWe have also added optional diagonal balancing to the `TreeMesh` which will ensure\nonly a single level change across diagonally adjacent cells. This can be set at the\ninstantiation of the object, or on a each separate call to a refinement function.\nWe plan to make this option `True` by default in a future version, but this will\nnever change how any mesh is read in from a file, ensuring only newly created\nmeshes are affected.\n\nStyle Updates\n-------------\nAll new PR's for ``discretize`` are now checked for style consistency by ``black`` and\n``flake8`` before running the testing sweet.\n\nBug Fixes\n---------\n\n- Interpolation on the :class:`~discretize.SimplexMesh` now respects the `zeros_outside`\n  keyword argument\n- Documentation inheritance for the functions inherited from :class:`~discretize.operators.DiffOperators`\n  and :class:`~discretize.operators.InnerProducts` have been fixed by making\n  these two classes subclass :class:`~discretize.base.BaseMesh`.\n\n\nContributors\n============\n\n* @jcapriot\n\nPull requests\n=============\n* `#292 <https://github.com/simpeg/discretize/pull/292>`__: Dark mode theme for documentation\n* `#294 <https://github.com/simpeg/discretize/pull/294>`__: Testing environment updates\n* `#295 <https://github.com/simpeg/discretize/pull/295>`__: Diagonal tree balance\n* `#296 <https://github.com/simpeg/discretize/pull/296>`__: More tree refine functions\n* `#297 <https://github.com/simpeg/discretize/pull/297>`__: Add new refine surface, bounding box, and point refine methods.\n* `#298 <https://github.com/simpeg/discretize/pull/298>`__: implement zeros outside for interpolation function\n* `#299 <https://github.com/simpeg/discretize/pull/299>`__: Build maintenance\n* `#300 <https://github.com/simpeg/discretize/pull/300>`__: Add style testing\n* `#301 <https://github.com/simpeg/discretize/pull/301>`__: Documentation updates\n* `#302 <https://github.com/simpeg/discretize/pull/302>`__: 0.8.3 release"
  },
  {
    "path": "docs/release/0.9.0-notes.rst",
    "content": ".. currentmodule:: discretize\n\n.. _0.9.0_notes:\n\n===================================\n``discretize`` 0.9.0 Release Notes\n===================================\n\nMay 31, 2023\n\nThis is a minor release with several pieces of additional functionality, and some\nsmall bug fixes. It also stages the updates for the `1.0.0` release by changing the\n``FutureWarnings`` to actual errors that describe to the user of how to update\ntheir code. All of these errors and messages will be removed on `1.0.0`.\n\n``CylindricalMesh``\n-------------------\nCylindrical meshes have now become more flexible. They can be created without discretizing\nthe full azimuthal space (think a slice of pizza, or a half-cylinder), and they can\nbe created without starting the radial dimension at 0 (think a ring).\n\nCylindrical meshes now also support averaging edges to faces, and thus now allow for\nboundary conditions to be imposed on PDEs involving curls.\n\nBoundary Conditions\n-------------------\nThe curvilinear and simplex meshes now better support boundary conditions with\nimproved (and corrected) handling of the inner products in the boundary condition\nsurface integral.\n\nThere is also now a detailed example on how to solve a nodal gradient problem with\na dirichlet boundary condition.\n\nTests\n-----\nDiscretize tests now better support a ``pytest`` environment with new assertion tests.\nPrevious tests now use these internally. Thhe new tests are more flexible and should\ngive better messages when they fail.\n\n``TreeMesh``\n------------\nThe external ``c++`` code now uses a ``try_emplace`` function for determining if an item\nalready exists when constructing the mesh. This should speedup mesh refinement.\n\n\nBug Fixes\n---------\n\n- Scrolling through the ``Slicer`` object within modern notebook enviroments should now\n  be working correctly.\n\n\nContributors\n============\n\n* @jcapriot\n* @prisae\n\nPull requests\n=============\n* `#308 <https://github.com/simpeg/discretize/pull/308>`__: Switch to try_emplace\n* `#309 <https://github.com/simpeg/discretize/pull/309>`__: Add stacklevel to warnings\n* `#310 <https://github.com/simpeg/discretize/pull/310>`__: Assert tests\n* `#312 <https://github.com/simpeg/discretize/pull/312>`__: Remove calls with deprecated dir argument\n* `#314 <https://github.com/simpeg/discretize/pull/314>`__: change name and description of average edge to face\n* `#315 <https://github.com/simpeg/discretize/pull/315>`__: update doc page style sheet\n* `#316 <https://github.com/simpeg/discretize/pull/316>`__: Curvilinear vector boundary integral\n* `#317 <https://github.com/simpeg/discretize/pull/317>`__: Cyl average edge\n* `#318 <https://github.com/simpeg/discretize/pull/318>`__: Feat/simp boundary\n* `#319 <https://github.com/simpeg/discretize/pull/319>`__: Add message to assertion error in tests.\n* `#320 <https://github.com/simpeg/discretize/pull/320>`__: Nodal boundary example\n* `#321 <https://github.com/simpeg/discretize/pull/321>`__: Cyl mesh generalization\n* `#322 <https://github.com/simpeg/discretize/pull/322>`__: Capture scroll\n* `#323 <https://github.com/simpeg/discretize/pull/323>`__: 0.9.0 release\n* `#324 <https://github.com/simpeg/discretize/pull/324>`__: Add total_nodes method to TreeMeshes\n"
  },
  {
    "path": "docs/release/index.rst",
    "content": "Release Notes\n=============\n\n.. toctree::\n   :maxdepth: 2\n\n   0.12.0  <0.12.0-notes>\n   0.11.3  <0.11.3-notes>\n   0.11.2  <0.11.2-notes>\n   0.11.1  <0.11.1-notes>\n   0.11.0  <0.11.0-notes>\n   0.10.0  <0.10.0-notes>\n   0.9.0  <0.9.0-notes>\n   0.8.3  <0.8.3-notes>\n   0.8.2  <0.8.2-notes>\n   0.8.1  <0.8.1-notes>\n   0.8.0  <0.8.0-notes>\n   0.7.4  <0.7.4-notes>\n   0.7.3  <0.7.3-notes>\n   0.7.2  <0.7.2-notes>\n   0.7.1  <0.7.1-notes>\n   0.7.0  <0.7.0-notes>\n   0.6.3  <0.6.3-notes>\n   0.6.2  <0.6.2-notes>\n   0.6.1  <0.6.1-notes>\n   0.6.0  <0.6.0-notes>\n   0.5.1  <0.5.1-notes>\n   0.5.0  <0.5.0-notes>\n   0.4.15 <0.4.15-notes>\n   0.4.14 <0.4.14-notes>\n   0.4.13 <0.4.13-notes>\n   0.4.12 <0.4.12-notes>\n"
  },
  {
    "path": "examples/README.txt",
    "content": ".. _sphx_glr_examples:\n\nExamples\n********"
  },
  {
    "path": "examples/plot_cahn_hilliard.py",
    "content": "r\"\"\"\nOperators: Cahn Hilliard\n========================\n\nThis example is based on the example in the FiPy_ library.\nPlease see their documentation for more information about the\nCahn-Hilliard equation.\n\nThe \"Cahn-Hilliard\" equation separates a field :math:`\\phi`\ninto 0 and 1 with smooth transitions.\n\n.. math::\n\n    \\frac{\\partial \\phi}{\\partial t} = \\nabla \\cdot D \\nabla \\left( \\frac{\\partial f}{\\partial \\phi} - \\epsilon^2 \\nabla^2 \\phi \\right)\n\nWhere :math:`f` is the energy function :math:`f = ( a^2 / 2 )\\phi^2(1 - \\phi)^2`\nwhich drives :math:`\\phi` towards either 0 or 1, this competes with the term\n:math:`\\epsilon^2 \\nabla^2 \\phi` which is a diffusion term that creates smooth changes in :math:`\\phi`.\nThe equation can be factored:\n\n.. math::\n\n    \\frac{\\partial \\phi}{\\partial t} = \\nabla \\cdot D \\nabla \\psi \\\\\n    \\psi = \\frac{\\partial^2 f}{\\partial \\phi^2} (\\phi - \\phi^{\\text{old}}) + \\frac{\\partial f}{\\partial \\phi} - \\epsilon^2 \\nabla^2 \\phi\n\nHere we will need the derivatives of :math:`f`:\n\n.. math::\n\n    \\frac{\\partial f}{\\partial \\phi} = (a^2/2)2\\phi(1-\\phi)(1-2\\phi)\n    \\frac{\\partial^2 f}{\\partial \\phi^2} = (a^2/2)2[1-6\\phi(1-\\phi)]\n\nThe implementation below uses backwards Euler in time with an\nexponentially increasing time step. The initial :math:`\\phi`\nis a normally distributed field with a standard deviation of 0.1 and\nmean of 0.5. The grid is 60x60 and takes a few seconds to solve ~130\ntimes. The results are seen below, and you can see the field separating\nas the time increases.\n\n.. _FiPy: https://github.com/usnistgov/fipy\n\n.. http://www.ctcms.nist.gov/fipy/examples/cahnHilliard/generated/examples.cahnHilliard.mesh2DCoupled.html\n\n\"\"\"\n\nimport discretize\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom scipy.sparse.linalg import spsolve\n\n\ndef run(plotIt=True, n=60):\n    np.random.seed(5)\n\n    # Here we are going to rearrange the equations:\n\n    # (phi_ - phi)/dt = A*(d2fdphi2*(phi_ - phi) + dfdphi - L*phi_)\n    # (phi_ - phi)/dt = A*(d2fdphi2*phi_ - d2fdphi2*phi + dfdphi - L*phi_)\n    # (phi_ - phi)/dt = A*d2fdphi2*phi_ + A*( - d2fdphi2*phi + dfdphi - L*phi_)\n    # phi_ - phi = dt*A*d2fdphi2*phi_ + dt*A*(- d2fdphi2*phi + dfdphi - L*phi_)\n    # phi_ - dt*A*d2fdphi2 * phi_ =  dt*A*(- d2fdphi2*phi + dfdphi - L*phi_) + phi\n    # (I - dt*A*d2fdphi2) * phi_ =  dt*A*(- d2fdphi2*phi + dfdphi - L*phi_) + phi\n    # (I - dt*A*d2fdphi2) * phi_ =  dt*A*dfdphi - dt*A*d2fdphi2*phi - dt*A*L*phi_ + phi\n    # (dt*A*d2fdphi2 - I) * phi_ =  dt*A*d2fdphi2*phi + dt*A*L*phi_ - phi - dt*A*dfdphi\n    # (dt*A*d2fdphi2 - I - dt*A*L) * phi_ =  (dt*A*d2fdphi2 - I)*phi - dt*A*dfdphi\n\n    h = [(0.25, n)]\n    M = discretize.TensorMesh([h, h])\n\n    # Constants\n    D = a = epsilon = 1.0\n    I = discretize.utils.speye(M.nC)\n\n    # Operators\n    A = D * M.face_divergence * M.cell_gradient\n    L = epsilon**2 * M.face_divergence * M.cell_gradient\n\n    duration = 75\n    elapsed = 0.0\n    dexp = -5\n    phi = np.random.normal(loc=0.5, scale=0.01, size=M.nC)\n    ii, jj = 0, 0\n    PHIS = []\n    capture = np.logspace(-1, np.log10(duration), 8)\n    while elapsed < duration:\n        dt = min(100, np.exp(dexp))\n        elapsed += dt\n        dexp += 0.05\n\n        dfdphi = a**2 * 2 * phi * (1 - phi) * (1 - 2 * phi)\n        d2fdphi2 = discretize.utils.sdiag(a**2 * 2 * (1 - 6 * phi * (1 - phi)))\n\n        MAT = dt * A * d2fdphi2 - I - dt * A * L\n        rhs = (dt * A * d2fdphi2 - I) * phi - dt * A * dfdphi\n        phi = spsolve(MAT, rhs)\n\n        if elapsed > capture[jj]:\n            PHIS += [(elapsed, phi.copy())]\n            jj += 1\n        if ii % 10 == 0:\n            print(ii, elapsed)\n        ii += 1\n\n    if plotIt:\n        fig, axes = plt.subplots(2, 4, figsize=(14, 6))\n        axes = np.array(axes).flatten().tolist()\n        for ii, ax in zip(np.linspace(0, len(PHIS) - 1, len(axes)), axes):\n            ii = int(ii)\n            M.plot_image(PHIS[ii][1], ax=ax)\n            ax.axis(\"off\")\n            ax.set_title(\"Elapsed Time: {0:4.1f}\".format(PHIS[ii][0]))\n\n\nif __name__ == \"__main__\":\n    run()\n    plt.show()\n"
  },
  {
    "path": "examples/plot_cyl_mirror.py",
    "content": "\"\"\"\nPlot Mirrored Cylindrically Symmetric Model\n===========================================\n\nHere, we demonstrate plotting a model on a cylindrically\nsymmetric mesh with the plotting symmetric about x=0.\n\"\"\"\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport discretize\n\n\ndef run(plotIt=True):\n    sig_halfspace = 1e-6\n    sig_sphere = 1e0\n    sig_air = 1e-8\n\n    sphere_z = -50.0\n    sphere_radius = 30.0\n\n    # x-direction\n    cs = 1\n    nc = np.ceil(2.5 * (-(sphere_z - sphere_radius)) / cs)\n\n    # define a mesh\n    mesh = discretize.CylindricalMesh([[(cs, nc)], 1, [(cs, nc)]], x0=\"00C\")\n\n    # Put the model on the mesh\n    sigma = sig_air * np.ones(mesh.nC)  # start with air cells\n    sigma[mesh.gridCC[:, 2] < 0.0] = sig_halfspace  # cells below the earth\n\n    # indices of the sphere\n    sphere_ind = (\n        mesh.gridCC[:, 0] ** 2 + (mesh.gridCC[:, 2] - sphere_z) ** 2\n    ) <= sphere_radius**2\n    sigma[sphere_ind] = sig_sphere  # sphere\n\n    if not plotIt:\n        return\n\n    # Plot a cross section through the mesh\n    fig, ax = plt.subplots(2, 1)\n    # Set a nice colormap!\n    plt.set_cmap(plt.get_cmap(\"viridis\"))\n    plt.colorbar(mesh.plot_image(np.log10(sigma), ax=ax[0])[0], ax=ax[0])\n    ax[0].set_title(\"mirror = False\")\n    ax[0].axis(\"equal\")\n    ax[0].set_xlim([-200.0, 200.0])\n\n    plt.colorbar(mesh.plot_image(np.log10(sigma), ax=ax[1], mirror=True)[0], ax=ax[1])\n    ax[1].set_title(\"mirror = True\")\n    ax[1].axis(\"equal\")\n    ax[1].set_xlim([-200.0, 200.0])\n\n    plt.tight_layout()\n\n\nif __name__ == \"__main__\":\n    run()\n    plt.show()\n"
  },
  {
    "path": "examples/plot_dc_resistivity.py",
    "content": "\"\"\"\nBasic Forward 2D DC Resistivity\n===============================\n\n2D DC forward modeling example with Tensor and Curvilinear Meshes\n\"\"\"\n\nimport discretize\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom scipy.sparse.linalg import spsolve\n\n\ndef run(plotIt=True):\n    # Step1: Generate Tensor and Curvilinear Mesh\n    sz = [40, 40]\n    tM = discretize.TensorMesh(sz)\n    rM = discretize.CurvilinearMesh(\n        discretize.utils.example_curvilinear_grid(sz, \"rotate\")\n    )\n\n    # Step2: Direct Current (DC) operator\n    def DCfun(mesh, pts):\n        D = mesh.face_divergence\n        sigma = 1e-2 * np.ones(mesh.nC)\n        MsigI = mesh.get_face_inner_product(\n            sigma, invert_model=True, invert_matrix=True\n        )\n        A = -D * MsigI * D.T\n        A[-1, -1] /= mesh.cell_volumes[-1]  # Remove null space\n        rhs = np.zeros(mesh.nC)\n        txind = discretize.utils.closest_points_index(mesh, pts)\n        rhs[txind] = np.r_[1, -1]\n        return A, rhs\n\n    pts = np.vstack((np.r_[0.25, 0.5], np.r_[0.75, 0.5]))\n\n    # Step3: Solve DC problem (LU solver)\n    AtM, rhstM = DCfun(tM, pts)\n    phitM = spsolve(AtM, rhstM)\n\n    ArM, rhsrM = DCfun(rM, pts)\n    phirM = spsolve(ArM, rhsrM)\n\n    if not plotIt:\n        return\n\n    # Step4: Making Figure\n    fig, axes = plt.subplots(1, 2, figsize=(12 * 1.2, 4 * 1.2))\n    vmin, vmax = phitM.min(), phitM.max()\n\n    dat = tM.plot_image(phitM, ax=axes[0], clim=(vmin, vmax), grid=True)\n    cb0 = plt.colorbar(dat[0], ax=axes[0])\n    cb0.set_label(\"Voltage (V)\")\n    axes[0].set_title(\"TensorMesh\")\n\n    dat = rM.plot_image(phirM, ax=axes[1], clim=(vmin, vmax), grid=True)\n    cb1 = plt.colorbar(dat[0], ax=axes[1])\n    cb1.set_label(\"Voltage (V)\")\n    axes[1].set_title(\"CurvilinearMesh\")\n\n\nif __name__ == \"__main__\":\n    run()\n    plt.show()\n"
  },
  {
    "path": "examples/plot_image.py",
    "content": "\"\"\"\nBasic: PlotImage\n================\n\nYou can use M.PlotImage to plot images on all of the Meshes.\n\"\"\"\n\nimport discretize\nimport matplotlib.pyplot as plt\n\n\ndef run(plotIt=True):\n    M = discretize.TensorMesh([32, 32])\n    v = discretize.utils.random_model(M.vnC, random_seed=789)\n    v = discretize.utils.mkvc(v)\n\n    O = discretize.TreeMesh([32, 32])\n\n    def function(cell):\n        if (\n            cell.center[0] < 0.75\n            and cell.center[0] > 0.25\n            and cell.center[1] < 0.75\n            and cell.center[1] > 0.25\n        ):\n            return 5\n        if (\n            cell.center[0] < 0.9\n            and cell.center[0] > 0.1\n            and cell.center[1] < 0.9\n            and cell.center[1] > 0.1\n        ):\n            return 4\n        return 3\n\n    O.refine(function)\n\n    P = M.get_interpolation_matrix(O.gridCC, \"CC\")\n\n    ov = P * v\n\n    if not plotIt:\n        return\n\n    fig, axes = plt.subplots(1, 2, figsize=(10, 5))\n\n    out = M.plot_image(v, grid=True, ax=axes[0])\n    cb = plt.colorbar(out[0], ax=axes[0])\n    cb.set_label(\"Random Field\")\n    axes[0].set_title(\"TensorMesh\")\n\n    out = O.plot_image(ov, grid=True, ax=axes[1], clim=[0, 1])\n    cb = plt.colorbar(out[0], ax=axes[1])\n    cb.set_label(\"Random Field\")\n    axes[1].set_title(\"TreeMesh\")\n\n\nif __name__ == \"__main__\":\n    run()\n    plt.show()\n"
  },
  {
    "path": "examples/plot_pyvista_laguna.py",
    "content": "\"\"\"\n.. _pyvista_demo_ref:\n\n3D Visualization with PyVista\n=============================\n\nThe example demonstrates the how to use the VTK interface via the\n`pyvista library <http://docs.pyvista.org>`__ .\nTo run this example, you will need to `install pyvista <http://docs.pyvista.org/getting-started/installation.html>`__ .\n\n- contributed by `@banesullivan <https://github.com/banesullivan>`_\n\nUsing the inversion result from the example notebook\n`plot_laguna_del_maule_inversion.ipynb <http://docs.simpeg.xyz/content/examples/20-published/plot_laguna_del_maule_inversion.html>`_\n\n\"\"\"\n\n# sphinx_gallery_thumbnail_number = 2\nimport discretize\nimport pyvista as pv\nimport numpy as np\nimport pooch\n\n# Set a documentation friendly plotting theme\npv.set_plot_theme(\"document\")\n\nprint(\"PyVista Version: {}\".format(pv.__version__))\n\n###############################################################################\n# Download and load data\n# ----------------------\n#\n# In the following we load the :code:`mesh` and :code:`Lpout` that you would\n# get from running the laguna-del-maule inversion notebook as well as some of\n# the raw data for the topography surface and gravity observations.\n\n# Download Topography and Observed gravity data\ndata_url = \"https://storage.googleapis.com/simpeg/Chile_GRAV_4_Miller/Chile_GRAV_4_Miller.tar.gz\"\ndownloaded_items = pooch.retrieve(\n    data_url,\n    known_hash=\"28022bf8802eeb4892cac6c3efd1eb4275c84003a6723c047fe5e1738a66ea04\",\n    processor=pooch.Untar(),\n)\ndata_path = next(filter(lambda f: f.endswith(\"LdM_grav_obs.grv\"), downloaded_items))\ntopo_path = next(filter(lambda f: f.endswith(\"LdM_topo.topo\"), downloaded_items))\n\nmodel_url = \"https://storage.googleapis.com/simpeg/laguna_del_maule_slicer.tar.gz\"\ndownloaded_items = pooch.retrieve(\n    model_url,\n    known_hash=\"107293bfdeb77b314f4cb451a24c2c93a55aae40da28f43cf3c075d71acfb957\",\n    processor=pooch.Untar(),\n)\nmesh_path = next(filter(lambda f: f.endswith(\"mesh.json\"), downloaded_items))\nmodel_path = next(filter(lambda f: f.endswith(\"Lpout.npy\"), downloaded_items))\n\n# # Load the mesh/data\nmesh = discretize.load_mesh(mesh_path)\nmodels = {\"Lpout\": np.load(model_path)}\n\n\n###############################################################################\n# Create PyVista data objects\n# ---------------------------\n#\n# Here we start making PyVista data objects of all the spatially referenced\n# data.\n\n# Get the PyVista dataset of the inverted model\ndataset = mesh.to_vtk(models)\ndataset.set_active_scalars(\"Lpout\")\n\n###############################################################################\n\n# Load topography points from text file as XYZ numpy array\ntopo_pts = np.loadtxt(topo_path, skiprows=1)\n# Create the topography points and apply an elevation filter\ntopo = pv.PolyData(topo_pts).delaunay_2d().elevation()\n\n###############################################################################\n\n# Load the gravity data from text file as XYZ+attributes numpy array\ngrav_data = np.loadtxt(data_path, skiprows=1)\nprint(\"gravity file shape: \", grav_data.shape)\n# Use the points to create PolyData\ngrav = pv.PolyData(grav_data[:, 0:3])\n# Add the data arrays\ngrav.point_data[\"comp-1\"] = grav_data[:, 3]\ngrav.point_data[\"comp-2\"] = grav_data[:, 4]\ngrav.set_active_scalars(\"comp-1\")\n\n###############################################################################\n# Plot the topographic surface and the gravity data\n\np = pv.Plotter()\np.add_mesh(topo, color=\"grey\")\np.add_mesh(\n    grav,\n    point_size=15,\n    render_points_as_spheres=True,\n    scalar_bar_args={\"title\": \"Observed Gravtiy Data\"},\n)\n# Use a non-phot-realistic shading technique to show topographic relief\np.enable_eye_dome_lighting()\np.show(window_size=[1024, 768])\n\n\n###############################################################################\n# Visualize Using PyVista\n# -----------------------\n#\n# Here we visualize all the data in 3D!\n\n# Create display parameters for inverted model\ndparams = dict(\n    show_edges=False,\n    cmap=\"bwr\",\n    clim=[-0.6, 0.6],\n)\n\n# Apply a threshold filter to remove topography\n#  no arguments will remove the NaN values\ndataset_t = dataset.threshold()\n\n# Extract volumetric threshold\nthreshed = dataset_t.threshold(-0.2, invert=True)\n\n# Create the rendering scene\np = pv.Plotter()\n# add a grid axes\np.show_grid()\n\n# Add spatially referenced data to the scene\np.add_mesh(dataset_t.slice(\"x\"), **dparams)\np.add_mesh(dataset_t.slice(\"y\"), **dparams)\np.add_mesh(threshed, **dparams)\np.add_mesh(\n    topo,\n    opacity=0.75,\n    color=\"grey\",\n    # cmap='gist_earth', clim=[1.7e+03, 3.104e+03],\n)\np.add_mesh(grav, cmap=\"viridis\", point_size=15, render_points_as_spheres=True)\n\n# Here is a nice camera position we manually found:\ncpos = [\n    (395020.7332989303, 6039949.0452080015, 20387.583125699253),\n    (364528.3152860675, 6008839.363092581, -3776.318305935185),\n    (-0.3423732500124074, -0.34364514928896667, 0.8744647328772646),\n]\np.camera_position = cpos\n\n\n# Render the scene!\np.show(window_size=[1024, 768])\n"
  },
  {
    "path": "examples/plot_quadtree_divergence.py",
    "content": "\"\"\"\nQuadTree: FaceDiv\n=================\n\nFor a tree mesh, there needs to be special attention taken for the hanging\nfaces to achieve second order convergence for the divergence operator.\nAlthough the divergence cannot be constructed through Kronecker product\noperations, the initial steps are exactly the same for calculating the\nstencil, volumes, and areas. This yields a divergence defined for every\ncell in the mesh using all faces. There is, however, redundant information\nwhen hanging faces are included.\n\"\"\"\n\nimport discretize\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\ndef run(plotIt=True, n=60):\n    M = discretize.TreeMesh([[(1, 16)], [(1, 16)]], levels=4)\n    M.insert_cells(np.array([5.0, 5.0]), np.array([3]))\n    M.number()\n\n    if plotIt:\n        fig, axes = plt.subplots(2, 1, figsize=(10, 10))\n\n        M.plot_grid(centers=True, nodes=False, ax=axes[0])\n        axes[0].axis(\"off\")\n        axes[0].set_title(\"Simple QuadTree Mesh\")\n        axes[0].set_xlim([-1, 17])\n        axes[0].set_ylim([-1, 17])\n\n        for ii, loc in zip(range(M.nC), M.gridCC):\n            axes[0].text(loc[0] + 0.2, loc[1], \"{0:d}\".format(ii), color=\"r\")\n\n        axes[0].plot(M.gridFx[:, 0], M.gridFx[:, 1], \"g>\")\n        for ii, loc in zip(range(M.nFx), M.gridFx):\n            axes[0].text(loc[0] + 0.2, loc[1], \"{0:d}\".format(ii), color=\"g\")\n\n        axes[0].plot(M.gridFy[:, 0], M.gridFy[:, 1], \"m^\")\n        for ii, loc in zip(range(M.nFy), M.gridFy):\n            axes[0].text(\n                loc[0] + 0.2, loc[1] + 0.2, \"{0:d}\".format((ii + M.nFx)), color=\"m\"\n            )\n\n        axes[1].spy(M.face_divergence)\n        axes[1].set_title(\"Face Divergence\")\n        axes[1].set_ylabel(\"Cell Number\")\n        axes[1].set_xlabel(\"Face Number\")\n\n\nif __name__ == \"__main__\":\n    run()\n    plt.show()\n"
  },
  {
    "path": "examples/plot_quadtree_hanging.py",
    "content": "\"\"\"\nQuadTree: Hanging Nodes\n=======================\n\nYou can give the refine method a function, which is evaluated on every\ncell of the TreeMesh.\n\nOccasionally it is useful to initially refine to a constant level\n(e.g. 3 in this 32x32 mesh). This means the function is first evaluated\non an 8x8 mesh (2^3).\n\n\"\"\"\n\nimport discretize\nimport matplotlib.pyplot as plt\n\n\ndef run(plotIt=True):\n    M = discretize.TreeMesh([8, 8])\n\n    def refine(cell):\n        xyz = cell.center\n        dist = ((xyz - [0.25, 0.25]) ** 2).sum() ** 0.5\n        if dist < 0.25:\n            return 3\n        return 2\n\n    M.refine(refine)\n    if plotIt:\n        M.plot_grid(nodes=True, centers=True, faces_x=True)\n        plt.legend(\n            (\n                \"Nodes\",\n                \"Hanging Nodes\",\n                \"Cell Centers\",\n                \"X faces\",\n                \"Hanging X faces\",\n                \"Grid\",\n            )\n        )\n\n\nif __name__ == \"__main__\":\n    run()\n    plt.show()\n"
  },
  {
    "path": "examples/plot_slicer_demo.py",
    "content": "\"\"\"\nSlicer demo\n===========\n\nThe example demonstrates the `plot_3d_slicer`\n\n- contributed by `@prisae <https://github.com/prisae>`_\n\nUsing the inversion result from the example notebook\n`plot_laguna_del_maule_inversion.ipynb <http://docs.simpeg.xyz/content/examples/20-published/plot_laguna_del_maule_inversion.html>`_\n\nYou have to use :code:`%matplotlib notebook` in Jupyter Notebook, and\n:code:`%matplotlib widget` in Jupyter Lab (latter requires the package\n``ipympl``).\n\"\"\"\n\n# %matplotlib notebook\n# %matplotlib widget\nimport discretize\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.colors import SymLogNorm\nimport pooch\n\n###############################################################################\n# Download and load data\n# ----------------------\n#\n# In the following we load the :code:`mesh` and :code:`Lpout` that you would\n# get from running the laguna-del-maule inversion notebook.\n\nmodel_url = \"https://storage.googleapis.com/simpeg/laguna_del_maule_slicer.tar.gz\"\ndownloaded_items = pooch.retrieve(\n    model_url,\n    known_hash=\"107293bfdeb77b314f4cb451a24c2c93a55aae40da28f43cf3c075d71acfb957\",\n    processor=pooch.Untar(),\n)\nmesh_path = next(filter(lambda f: f.endswith(\"mesh.json\"), downloaded_items))\nmodel_path = next(filter(lambda f: f.endswith(\"Lpout.npy\"), downloaded_items))\n\n# Load the mesh and model\nmesh = discretize.load_mesh(mesh_path)\nLpout = np.load(model_path)\n\n###############################################################################\n# Case 1: Using the intrinsinc functionality\n# ------------------------------------------\n#\n# 1.1 Default options\n# ^^^^^^^^^^^^^^^^^^^\n\nmesh.plot_3d_slicer(Lpout)\nplt.show()\n\n###############################################################################\n# 1.2 Create a function to improve plots, labeling after creation\n# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n#\n# Depending on your data the default option might look a bit odd. The look\n# of the figure can be improved by getting its handle and adjust it.\n\n\ndef beautify(title, fig=None):\n    \"\"\"Beautify the 3D Slicer result.\"\"\"\n\n    # Get figure handle if not provided\n    if fig is None:\n        fig = plt.gcf()\n\n    # Get principal figure axes\n    axs = fig.get_children()\n\n    # Set figure title\n    fig.suptitle(title, y=0.95, va=\"center\")\n\n    # Adjust the y-labels on the first subplot (XY)\n    plt.setp(axs[1].yaxis.get_majorticklabels(), rotation=90)\n    for label in axs[1].yaxis.get_ticklabels():\n        label.set_visible(False)\n    for label in axs[1].yaxis.get_ticklabels()[::3]:\n        label.set_visible(True)\n    axs[1].set_ylabel(\"Northing (m)\")\n\n    # Adjust x- and y-labels on the second subplot (XZ)\n    axs[2].set_xticks([357500, 362500, 367500])\n    axs[2].set_xlabel(\"Easting (m)\")\n\n    plt.setp(axs[2].yaxis.get_majorticklabels(), rotation=90)\n    axs[2].set_yticks([2500, 0, -2500, -5000])\n    axs[2].set_yticklabels([\"$2.5$\", \"0.0\", \"-2.5\", \"-5.0\"])\n    axs[2].set_ylabel(\"Elevation (km)\")\n\n    # Adjust x-labels on the third subplot (ZY)\n    axs[3].set_xticks([2500, 0, -2500, -5000])\n    axs[3].set_xticklabels([\"\", \"0.0\", \"-2.5\", \"-5.0\"])\n\n    # Adjust colorbar\n    axs[4].set_ylabel(\"Density (g/cc$^3$)\")\n\n    # Ensure sufficient margins so nothing is clipped\n    plt.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.9)\n\n\n###############################################################################\n#\nmesh.plot_3d_slicer(Lpout)\nbeautify(\"mesh.plot_3d_slicer(Lpout)\")\nplt.show()\n\n###############################################################################\n# 1.3 Set `xslice`, `yslice`, and `zslice`; transparent region\n# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n#\n# The 2nd-4th input arguments are the initial x-, y-, and z-slice location\n# (they default to the middle of the volume). The transparency-parameter can\n# be used to define transparent regions.\n\nmesh.plot_3d_slicer(Lpout, 370000, 6002500, -2500, transparent=[[-0.02, 0.1]])\nbeautify(\n    \"mesh.plot_3d_slicer(\"\n    \"\\nLpout, 370000, 6002500, -2500, transparent=[[-0.02, 0.1]])\"\n)\nplt.show()\n\n###############################################################################\n# 1.4 Set `clim`, use `pcolor_opts` to show grid lines\n# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmesh.plot_3d_slicer(\n    Lpout, clim=[-0.4, 0.2], pcolor_opts={\"edgecolor\": \"k\", \"linewidth\": 0.1}\n)\nbeautify(\n    \"mesh.plot_3d_slicer(\\nLpout, clim=[-0.4, 0.2], \"\n    \"pcolor_opts={'edgecolor': 'k', 'linewidth': 0.1})\"\n)\nplt.show()\n\n###############################################################################\n# 1.5 Use `pcolor_opts` to set `SymLogNorm`, and another `cmap`\n# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmesh.plot_3d_slicer(\n    Lpout, pcolor_opts={\"norm\": SymLogNorm(linthresh=0.01), \"cmap\": \"RdBu_r\"}\n)\nbeautify(\n    \"mesh.plot_3d_slicer(Lpout,\"\n    \"\\npcolor_opts={'norm': SymLogNorm(linthresh=0.01),'cmap': 'RdBu_r'})`\"\n)\nplt.show()\n\n###############################################################################\n# 1.6 Use :code:`aspect` and :code:`grid`\n# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n#\n# By default, :code:`aspect='auto'` and :code:`grid=[2, 2, 1]`. This means that\n# the figure is on a 3x3 grid, where the `xy`-slice occupies 2x2 cells of the\n# subplot-grid, `xz`-slice 2x1, and the `zy`-silce 1x2. So the\n# :code:`grid=[x, y, z]`-parameter takes the number of cells for `x`, `y`, and\n# `z`-dimension.\n#\n# :code:`grid` can be used to improve the probable weired subplot-arrangement\n# if :code:`aspect` is anything else than :code:`auto`. However, if you zoom\n# then it won't help. Expect the unexpected.\n\nmesh.plot_3d_slicer(Lpout, aspect=[\"equal\", 1.5], grid=[4, 4, 3])\nbeautify(\"mesh.plot_3d_slicer(Lpout, aspect=['equal', 1.5], grid=[4, 4, 3])\")\nplt.show()\n\n###############################################################################\n# 1.7 Transparency-slider\n# ^^^^^^^^^^^^^^^^^^^^^^^\n#\n# Setting the transparent-parameter to 'slider' will create interactive sliders\n# to change which range of values of the data is visible.\n\nmesh.plot_3d_slicer(Lpout, transparent=\"slider\")\nbeautify(\"mesh.plot_3d_slicer(Lpout, transparent='slider')\")\nplt.show()\n\n\n###############################################################################\n# Case 2: Just using the Slicer class\n# ------------------------------------------\n#\n# This way you get the figure-handle, and can do further stuff with the figure.\n\n# You have to initialize a figure\nfig = plt.figure()\n\n# Then you have to get the tracker from the Slicer\ntracker = discretize.mixins.Slicer(mesh, Lpout)\n\n# Finally you have to connect the tracker to the figure\nfig.canvas.mpl_connect(\"scroll_event\", tracker.onscroll)\n\n# Run it through beautify\nbeautify(\"'discretize.mixins.Slicer' together with\\n'fig.canvas.mpl_connect'\", fig)\n\nplt.show()\n"
  },
  {
    "path": "examples/plot_streamThickness.py",
    "content": "\"\"\"\nPlotting: Streamline thickness\n==============================\n\nA simple example to vary streamline thickness based on the vector amplitudes\n\nAuthor: `@micmitch <https://github.com/micmitch>`\n\"\"\"\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom discretize import TensorMesh\n\n###############################################################################\n# Create mesh\n# -----------\n#\n# Minimum cell size in each direction\ndx = 1.0\ndy = 1.0\ndz = 1.0\n\n# Number of core cells in each direction\nnCoreX = 43.0\nnCoreY = 43.0\nnCoreZ = 43.0\n\n# Cell widths\nhx = [(dx, nCoreX)]\nhy = [(dy, nCoreY)]\nhz = [(dz, nCoreZ)]\n\n# Desired Core mesh origin (Bottom SW corner)\nx0 = -21.5\ny0 = -21.5\nz0 = -21.5\n\nmesh = TensorMesh([hx, hy, hz], [x0, y0, z0])\nprint(mesh.nC)\n\n###############################################################################\n# Define arbitrary function to plot\n# ---------------------------------\n#\n\nX = mesh.gridCC[:, 0]\nY = mesh.gridCC[:, 1]\nZ = mesh.gridCC[:, 2]\n\nU = -1 - X**2 + Y + Z\nV = 1 + X - Y**2 + Z\nW = 1 + X + Y - Z**2\n\n###############################################################################\n# Plot streamlines\n# ----------------\n#\n# Create figure\nfig = plt.figure()\nax = plt.subplot(111)\nfig.set_figheight(15)\nfig.set_figwidth(15)\nlabelsize = 30.0\nticksize = 30.0\n\n# Create data vector\ndataVec = np.hstack([U, V, W])\nprint(dataVec.shape)\n\n# Set streamline plotting options\nstream_opts = {\"color\": \"w\", \"density\": 2.0}\npcolor_opts = {\"cmap\": \"viridis\"}\n\ndat = mesh.plot_slice(\n    dataVec,\n    ax=ax,\n    normal=\"Z\",\n    ind=5,\n    v_type=\"CCv\",\n    view=\"vec\",\n    stream_opts=stream_opts,\n    grid_opts={\"color\": \"k\", \"alpha\": 0.1},\n    grid=True,\n    clim=None,\n    stream_thickness=3,\n)\n\n###############################################################################\n# Moving Forward\n# --------------\n#\n# If you have suggestions for improving this example, please create a\n# pull request on the example in discretize\n"
  },
  {
    "path": "meson.build",
    "content": "project(\n  'discretize',\n  'c', 'cpp', 'cython',\n  # Note that the git commit hash cannot be added dynamically here\n  # (it is dynamically generated though setuptools_scm)\n  version: run_command('python',\n    [\n      '-c',\n      '''\nfrom setuptools_scm import get_version\nprint(get_version())'''\n    ],\n    check: true\n).stdout().strip(),\n\n  license: 'MIT',\n  meson_version: '>= 1.4.0',\n  default_options: [\n    'buildtype=debugoptimized',\n    'b_ndebug=if-release',\n    'cpp_std=c++17',\n  ],\n)\n\n# https://mesonbuild.com/Python-module.html\npy_mod = import('python')\npy = py_mod.find_installation(pure: false)\npy_dep = py.dependency()\n\ncc = meson.get_compiler('c')\ncpp = meson.get_compiler('cpp')\ncy = meson.get_compiler('cython')\n# generator() doesn't accept compilers, only found programs - cast it.\ncython = find_program(cy.cmd_array()[0])\n\n_global_c_args = cc.get_supported_arguments(\n  '-Wno-unused-but-set-variable',\n  '-Wno-unused-function',\n  '-Wno-conversion',\n  '-Wno-misleading-indentation',\n)\nadd_project_arguments(_global_c_args, language : 'c')\n\n# We need -lm for all C code (assuming it uses math functions, which is safe to\n# assume for SciPy). For C++ it isn't needed, because libstdc++/libc++ is\n# guaranteed to depend on it.\nm_dep = cc.find_library('m', required : false)\nif m_dep.found()\n  add_project_link_arguments('-lm', language : 'c')\nendif\n\nsubdir('discretize')"
  },
  {
    "path": "meson.options",
    "content": "option('cy_line_trace', type : 'boolean', value : false)"
  },
  {
    "path": "pyproject.toml",
    "content": "\n[build-system]\nbuild-backend = 'mesonpy'\nrequires = [\n    \"meson-python>=0.15.0\",\n    \"Cython>=3.1.0\",\n    \"setuptools_scm[toml]>=6.2\",\n\n    # numpy requirement for wheel builds for distribution on PyPI - building\n    # against 2.x yields wheels that are also compatible with numpy 1.x at\n    # runtime.\n    \"numpy>=2.0.0rc1\",\n]\n\n[project]\nname = 'discretize'\ndynamic = [\"version\"]\ndescription = 'Discretization tools for finite volume and inverse problems'\nreadme = 'README.rst'\nrequires-python = '>=3.11'\nauthors = [\n  {name = 'SimPEG developers', email = 'rowanc1@gmail.com'},\n]\nkeywords = [\n    'finite volume', 'discretization', 'pde', 'ode'\n]\n\n# Note: Python and NumPy upper version bounds should be set correctly in\n# release branches, see:\n#     https://scipy.github.io/devdocs/dev/core-dev/index.html#version-ranges-for-numpy-and-other-dependencies\ndependencies = [\n    # TODO: update to \"pin-compatible\" once possible, see\n    # https://github.com/mesonbuild/meson-python/issues/29\n    \"numpy>=1.22.4\",\n    \"scipy>=1.12\",\n]\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Cython\",\n    \"Programming Language :: C++\",\n    \"Topic :: Scientific/Engineering\",\n    \"Topic :: Scientific/Engineering :: Mathematics\",\n    \"Topic :: Scientific/Engineering :: Physics\",\n    \"Operating System :: Microsoft :: Windows\",\n    \"Operating System :: POSIX\",\n    \"Operating System :: Unix\",\n    \"Operating System :: MacOS\",\n    \"Natural Language :: English\",\n]\n\n[project.license]\nfile = 'LICENSE'\n\n[project.optional-dependencies]\nplot = [\"matplotlib\"]\nviz = [\"vtk>=6\", \"pyvista\"]\nomf = [\"omf\"]\nall = [\"discretize[plot,viz,omf]\"]\ndoc = [\n    \"sphinx==8.1.3\",\n    \"pydata-sphinx-theme==0.16.1\",\n    \"sphinx-gallery==0.19.0\",\n    \"numpydoc==1.9.0\",\n    \"jupyter\",\n    \"graphviz\",\n    \"pillow\",\n    \"pooch\",\n    \"discretize[all]\",\n]\ntest = [\n    \"pytest\",\n    \"pytest-cov\",\n    \"sympy\",\n    \"discretize[doc,all]\",\n]\n# when changing these, make sure to keep it consistent with .pre-commit-config.\nstyle = [\n    \"black==24.3.0\",\n    \"flake8==7.0.0\",\n    \"flake8-bugbear==23.12.2\",\n    \"flake8-builtins==2.2.0\",\n    \"flake8-mutable==1.2.0\",\n    \"flake8-rst-docstrings==0.3.0\",\n    \"flake8-docstrings==1.7.0\",\n    \"flake8-pyproject==1.2.3\",\n]\nbuild = [\n    \"meson-python>=0.15.0\",\n    \"meson\",\n    \"ninja\",\n    \"numpy>=2.0.0rc1\",\n    \"cython>=3.1.0\",\n    \"setuptools_scm\",\n]\n\n[project.urls]\nHomepage = 'https://simpeg.xyz'\nDocumentation = 'https://discretize.simpeg.xyz'\nRepository = 'http://github.com/simpeg/discretize.git'\n\n[tool.setuptools_scm]\n\n[tool.cibuildwheel]\n# skip building wheels for python 3.6, 3.7, 3.8, 3.9, all pypy versions, and specialty linux\n# processors (still does arm builds though).\n# skip windows 32bit\nskip = \"cp38-* cp39-* cp310-* *_ppc64le *_i686 *_s390x *-win32 cp310-win_arm64\"\nbuild-verbosity = 3\nenable = [\"cpython-freethreading\"]\n\n# test importing discretize to make sure externals are loadable.\ntest-command = 'python -c \"import discretize; print(discretize.__version__)\"'\n\n\n# use the visual studio compilers\n[tool.cibuildwheel.windows.config-settings]\nsetup-args = [\n    '--vsenv'\n]\n\n[tool.coverage.run]\nbranch = true\nsource = [\"discretize\", \"tests\", \"examples\", \"tutorials\"]\n# plugins = [\n#     \"Cython.Coverage\",\n# ]\n\n[tool.coverage.report]\nignore_errors = false\nshow_missing = true\n# Regexes for lines to exclude from consideration\nexclude_also = [\n    # Don't complain about missing debug-only code:\n    \"def __repr__\",\n    \"if self\\\\.debug\",\n\n    # Don't complain if tests don't hit defensive assertion code:\n    \"raise AssertionError\",\n    \"raise NotImplementedError\",\n    \"AbstractMethodError\",\n\n    # Don't complain if non-runnable code isn't run:\n    \"if 0:\",\n    \"if __name__ == .__main__.:\",\n\n    # Don't complain about abstract methods, they aren't run:\n    \"@(abc\\\\.)?abstractmethod\",\n]\n\n[tool.black]\nrequired-version = '24.3.0'\ntarget-version = ['py38', 'py39', 'py310', 'py311']\n\n[tool.flake8]\nextend-ignore = [\n    # Too many leading '#' for block comment\n    'E266',\n    # Line too long (82 > 79 characters)\n    'E501',\n    # Do not use variables named 'I', 'O', or 'l'\n    'E741',\n    # Line break before binary operator (conflicts with black)\n    'W503',\n    # Ignore spaces before a colon (Black handles it)\n    'E203',\n    # Ignore spaces around an operator (Black handles it)\n    'E225',\n    # Ignore rst warnings for start and end, due to *args and **kwargs being invalid rst, but good for numpydoc\n    'RST210',\n    'RST213',\n    # ignore undocced __init__\n    'D107',\n]\nexclude = [\n    '.git',\n    '.eggs',\n    '__pycache__',\n    '.ipynb_checkpoints',\n    'docs/examples/*',\n    'docs/tutorials/*',\n    'docs/*',\n    'discretize/_extensions/*.py',\n    '.ci/*'\n]\nper-file-ignores = [\n    # disable unused-imports errors on __init__.py\n    # Automodule used for __init__ scripts' description\n    '__init__.py: F401, D204, D205, D400',\n    # do not check for assigned lambdas in tests\n    # do not check for missing docstrings in tests\n    'tests/*: E731, D',\n    'tutorials/*: D',\n    'examples/*: D',\n]\nexclude-from-doctest = [\n    # Only check discretize for docstring style\n    'tests',\n    'tutorials',\n    'examples',\n]\n\nrst-roles = [\n    'class',\n    'func',\n    'mod',\n    'meth',\n    'attr',\n    'ref',\n    'data',\n    # Python programming language:\n    'py:func','py:mod','py:attr','py:meth',\n]\n\nrst-directives = [\n    # These are sorted alphabetically - but that does not matter\n    'autosummary',\n    'currentmodule',\n    'deprecated',\n]\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "if __name__ == \"__main__\":\n    import glob\n    import unittest\n\n    test_file_strings = glob.glob(\"test_*.py\")\n    module_strings = [strng[0 : len(strng) - 3] for strng in test_file_strings]\n    suites = [\n        unittest.defaultTestLoader.loadTestsFromName(strng) for strng in module_strings\n    ]\n    testSuite = unittest.TestSuite(suites)\n\n    unittest.TextTestRunner(verbosity=2).run(testSuite)\n"
  },
  {
    "path": "tests/base/__init__.py",
    "content": "if __name__ == \"__main__\":\n    import glob\n    import unittest\n\n    test_file_strings = glob.glob(\"test_*.py\")\n    module_strings = [strng[0 : len(strng) - 3] for strng in test_file_strings]\n    suites = [\n        unittest.defaultTestLoader.loadTestsFromName(strng) for strng in module_strings\n    ]\n    testSuite = unittest.TestSuite(suites)\n\n    unittest.TextTestRunner(verbosity=2).run(testSuite)\n"
  },
  {
    "path": "tests/base/test_basemesh.py",
    "content": "import unittest\nfrom discretize.base import BaseRectangularMesh, BaseMesh\nimport numpy as np\nimport inspect\n\n\nclass TestBaseMesh(unittest.TestCase):\n    not_implemented_attributes = [\n        \"dim\",\n        \"n_cells\",\n        \"n_nodes\",\n        \"n_edges\",\n        \"n_faces\",\n        \"cell_centers\",\n        \"nodes\",\n        \"boundary_nodes\",\n        \"faces\",\n        \"boundary_faces\",\n        \"edges\",\n        \"boundary_edges\",\n        \"face_normals\",\n        \"edge_tangents\",\n        \"boundary_face_outward_normals\",\n        \"cell_volumes\",\n        \"face_areas\",\n        \"edge_lengths\",\n        \"face_divergence\",\n        \"nodal_gradient\",\n        \"edge_curl\",\n        \"boundary_face_scalar_integral\",\n        \"boundary_edge_vector_integral\",\n        \"boundary_node_vector_integral\",\n        \"nodal_laplacian\",\n        \"stencil_cell_gradient\",\n        \"average_face_to_cell\",\n        \"average_face_to_cell_vector\",\n        \"average_cell_to_face\",\n        \"average_cell_vector_to_face\",\n        \"average_cell_to_edge\",\n        \"average_edge_to_cell\",\n        \"average_edge_to_cell_vector\",\n        \"average_edge_to_face\",\n        \"average_node_to_cell\",\n        \"average_node_to_edge\",\n        \"average_node_to_face\",\n        \"project_face_to_boundary_face\",\n        \"project_edge_to_boundary_edge\",\n        \"project_node_to_boundary_node\",\n    ]\n    not_implemented_functions = [\n        \"get_face_inner_product\",\n        \"get_edge_inner_product\",\n        \"get_face_inner_product_surface\",\n        \"get_edge_inner_product_surface\",\n        \"get_edge_inner_product_line\",\n        \"get_face_inner_product_deriv\",\n        \"get_edge_inner_product_deriv\",\n        \"get_face_inner_product_surface_deriv\",\n        \"get_edge_inner_product_surface_deriv\",\n        \"get_edge_inner_product_line_deriv\",\n        \"point2index\",\n        \"get_interpolation_matrix\",\n    ]\n\n    def setUp(self):\n        self.base_mesh = BaseMesh()\n\n    def test_not_impl_attr(self):\n        for item in self.not_implemented_attributes:\n            with self.assertRaises(NotImplementedError):\n                getattr(self.base_mesh, item)\n\n    def test_not_impl_func(self):\n        for item in self.not_implemented_functions:\n            with self.assertRaises(NotImplementedError):\n                func = getattr(self.base_mesh, item)\n                params = inspect.signature(func).parameters\n\n                n_params = sum(\n                    [\n                        (\n                            1\n                            if (\n                                param.default is param.empty\n                                and param.kind != param.VAR_KEYWORD\n                            )\n                            else 0\n                        )\n                        for param in params.values()\n                    ]\n                )\n                # pass empty parameters to the function\n                func(*([None] * n_params))\n\n\nclass TestBaseRectangularMesh(unittest.TestCase):\n    def setUp(self):\n        self.mesh = BaseRectangularMesh([6, 2, 3])\n\n    def test_meshDimensions(self):\n        self.assertEqual(self.mesh.dim, 3)\n\n    def test_mesh_nc(self):\n        self.assertEqual(self.mesh.nC, 36)\n        self.assertEqual(self.mesh.vnC, (6, 2, 3))\n\n    def test_mesh_nc_xyz(self):\n        self.assertEqual(self.mesh.shape_cells[0], 6)\n        self.assertEqual(self.mesh.shape_cells[1], 2)\n        self.assertEqual(self.mesh.shape_cells[2], 3)\n\n    def test_mesh_nf(self):\n        self.assertEqual(self.mesh.vnFx, (7, 2, 3))\n        self.assertEqual(self.mesh.vnFy, (6, 3, 3))\n        self.assertEqual(self.mesh.vnFz, (6, 2, 4))\n\n    def test_mesh_ne(self):\n        self.assertEqual(self.mesh.vnEx, (6, 3, 4))\n        self.assertEqual(self.mesh.vnEy, (7, 2, 4))\n        self.assertEqual(self.mesh.vnEz, (7, 3, 3))\n\n    def test_mesh_numbers(self):\n        self.assertEqual(self.mesh.nC, 36)\n        self.assertEqual(self.mesh.vnF, (42, 54, 48))\n        self.assertEqual(self.mesh.vnE, (72, 56, 63))\n        self.assertEqual(self.mesh.nF, np.sum((42, 54, 48)))\n        self.assertEqual(self.mesh.nE, np.sum((72, 56, 63)))\n\n    def test_mesh_r_E_V(self):\n        ex = np.ones(self.mesh.nEx)\n        ey = np.ones(self.mesh.nEy) * 2\n        ez = np.ones(self.mesh.nEz) * 3\n        e = np.r_[ex, ey, ez]\n        tex = self.mesh.reshape(e, \"E\", \"Ex\", \"V\")\n        tey = self.mesh.reshape(e, \"E\", \"Ey\", \"V\")\n        tez = self.mesh.reshape(e, \"E\", \"Ez\", \"V\")\n        self.assertTrue(np.all(tex == ex))\n        self.assertTrue(np.all(tey == ey))\n        self.assertTrue(np.all(tez == ez))\n        tex, tey, tez = self.mesh.reshape(e, \"E\", \"E\", \"V\")\n        self.assertTrue(np.all(tex == ex))\n        self.assertTrue(np.all(tey == ey))\n        self.assertTrue(np.all(tez == ez))\n\n    def test_mesh_r_F_V(self):\n        fx = np.ones(self.mesh.nFx)\n        fy = np.ones(self.mesh.nFy) * 2\n        fz = np.ones(self.mesh.nFz) * 3\n        f = np.r_[fx, fy, fz]\n        tfx = self.mesh.reshape(f, \"F\", \"Fx\", \"V\")\n        tfy = self.mesh.reshape(f, \"F\", \"Fy\", \"V\")\n        tfz = self.mesh.reshape(f, \"F\", \"Fz\", \"V\")\n        self.assertTrue(np.all(tfx == fx))\n        self.assertTrue(np.all(tfy == fy))\n        self.assertTrue(np.all(tfz == fz))\n        tfx, tfy, tfz = self.mesh.reshape(f, \"F\", \"F\", \"V\")\n        self.assertTrue(np.all(tfx == fx))\n        self.assertTrue(np.all(tfy == fy))\n        self.assertTrue(np.all(tfz == fz))\n\n    def test_mesh_r_E_M(self):\n        g = np.ones((np.prod(self.mesh.vnEx), 3))\n        g[:, 1] = 2\n        g[:, 2] = 3\n        Xex, Yex, Zex = self.mesh.reshape(g, \"Ex\", \"Ex\", \"M\")\n        self.assertEqual(Xex.shape, self.mesh.vnEx)\n        self.assertEqual(Yex.shape, self.mesh.vnEx)\n        self.assertEqual(Zex.shape, self.mesh.vnEx)\n        self.assertTrue(np.all(Xex == 1))\n        self.assertTrue(np.all(Yex == 2))\n        self.assertTrue(np.all(Zex == 3))\n\n    def test_mesh_r_F_M(self):\n        g = np.ones((np.prod(self.mesh.vnFx), 3))\n        g[:, 1] = 2\n        g[:, 2] = 3\n        Xfx, Yfx, Zfx = self.mesh.reshape(g, \"Fx\", \"Fx\", \"M\")\n        self.assertEqual(Xfx.shape, self.mesh.vnFx)\n        self.assertEqual(Yfx.shape, self.mesh.vnFx)\n        self.assertEqual(Zfx.shape, self.mesh.vnFx)\n        self.assertTrue(np.all(Xfx == 1))\n        self.assertTrue(np.all(Yfx == 2))\n        self.assertTrue(np.all(Zfx == 3))\n\n    def test_mesh_r_CC_M(self):\n        g = np.ones((self.mesh.nC, 3))\n        g[:, 1] = 2\n        g[:, 2] = 3\n        Xc, Yc, Zc = self.mesh.reshape(g, \"CC\", \"CC\", \"M\")\n        self.assertEqual(Xc.shape, self.mesh.vnC)\n        self.assertEqual(Yc.shape, self.mesh.vnC)\n        self.assertEqual(Zc.shape, self.mesh.vnC)\n        self.assertTrue(np.all(Xc == 1))\n        self.assertTrue(np.all(Yc == 2))\n        self.assertTrue(np.all(Zc == 3))\n\n    def test_serialization(self):\n        self.mesh.x0 = np.r_[-1.0, -2.0, 1.0]\n        mesh2 = BaseRectangularMesh.deserialize(self.mesh.serialize())\n        self.assertTrue(np.all(self.mesh.x0 == mesh2.x0))\n        self.assertTrue(np.all(self.mesh.shape_cells == mesh2.shape_cells))\n\n\nclass TestMeshNumbers2D(unittest.TestCase):\n    def setUp(self):\n        self.mesh = BaseRectangularMesh([6, 2])\n\n    def test_meshDimensions(self):\n        self.assertTrue(self.mesh.dim, 2)\n\n    def test_mesh_nc(self):\n        self.assertEqual(self.mesh.vnC, (6, 2))\n\n    def test_mesh_nc_xyz(self):\n        self.assertEqual(self.mesh.shape_cells[0], 6)\n        self.assertEqual(self.mesh.shape_cells[1], 2)\n\n    def test_mesh_nf(self):\n        self.assertEqual(self.mesh.vnFx, (7, 2))\n        self.assertEqual(self.mesh.vnFy, (6, 3))\n        self.assertTrue(self.mesh.vnFz is None)\n\n    def test_mesh_ne(self):\n        self.assertEqual(self.mesh.vnEx, (6, 3))\n        self.assertEqual(self.mesh.vnEy, (7, 2))\n        self.assertTrue(self.mesh.vnEz is None)\n\n    def test_mesh_numbers(self):\n        self.assertEqual(self.mesh.nC, 12)\n        self.assertEqual(self.mesh.vnF, (14, 18))\n        self.assertEqual(self.mesh.nFx, 14)\n        self.assertEqual(self.mesh.nFy, 18)\n        self.assertEqual(self.mesh.nEx, 18)\n        self.assertEqual(self.mesh.nEy, 14)\n        self.assertEqual(self.mesh.vnE, (18, 14))\n        self.assertEqual(self.mesh.nF, sum([14, 18]))\n        self.assertEqual(self.mesh.nE, sum([18, 14]))\n\n    def test_mesh_r_E_V(self):\n        ex = np.ones(self.mesh.nEx)\n        ey = np.ones(self.mesh.nEy) * 2\n        e = np.r_[ex, ey]\n        tex = self.mesh.reshape(e, \"E\", \"Ex\", \"V\")\n        tey = self.mesh.reshape(e, \"E\", \"Ey\", \"V\")\n        self.assertTrue(np.all(tex == ex))\n        self.assertTrue(np.all(tey == ey))\n        tex, tey = self.mesh.reshape(e, \"E\", \"E\", \"V\")\n        self.assertTrue(np.all(tex == ex))\n        self.assertTrue(np.all(tey == ey))\n        with self.assertRaises(ValueError):\n            self.mesh.reshape(e, \"E\", \"Ez\", \"V\")\n\n    def test_mesh_r_F_V(self):\n        fx = np.ones(self.mesh.nFx)\n        fy = np.ones(self.mesh.nFy) * 2\n        f = np.r_[fx, fy]\n        tfx = self.mesh.reshape(f, \"F\", \"Fx\", \"V\")\n        tfy = self.mesh.reshape(f, \"F\", \"Fy\", \"V\")\n        self.assertTrue(np.all(tfx == fx))\n        self.assertTrue(np.all(tfy == fy))\n        tfx, tfy = self.mesh.reshape(f, \"F\", \"F\", \"V\")\n        self.assertTrue(np.all(tfx == fx))\n        self.assertTrue(np.all(tfy == fy))\n        with self.assertRaises(ValueError):\n            self.mesh.reshape(f, \"F\", \"Fz\", \"V\")\n\n    def test_mesh_r_E_M(self):\n        g = np.ones((np.prod(self.mesh.vnEx), 2))\n        g[:, 1] = 2\n        Xex, Yex = self.mesh.reshape(g, \"Ex\", \"Ex\", \"M\")\n        self.assertEqual(Xex.shape, self.mesh.vnEx)\n        self.assertEqual(Yex.shape, self.mesh.vnEx)\n        self.assertTrue(np.all(Xex == 1))\n        self.assertTrue(np.all(Yex == 2))\n\n    def test_mesh_r_F_M(self):\n        g = np.ones((np.prod(self.mesh.vnFx), 2))\n        g[:, 1] = 2\n        Xfx, Yfx = self.mesh.reshape(g, \"Fx\", \"Fx\", \"M\")\n        self.assertEqual(Xfx.shape, self.mesh.vnFx)\n        self.assertEqual(Yfx.shape, self.mesh.vnFx)\n        self.assertTrue(np.all(Xfx == 1))\n        self.assertTrue(np.all(Yfx == 2))\n\n    def test_mesh_r_CC_M(self):\n        g = np.ones((self.mesh.nC, 2))\n        g[:, 1] = 2\n        Xc, Yc = self.mesh.reshape(g, \"CC\", \"CC\", \"M\")\n        self.assertEqual(Xc.shape, self.mesh.vnC)\n        self.assertEqual(Yc.shape, self.mesh.vnC)\n        self.assertTrue(np.all(Xc == 1))\n        self.assertTrue(np.all(Yc == 2))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_coordutils.py",
    "content": "import unittest\nimport numpy as np\nfrom discretize import utils\n\ntol = 1e-15\n\nrng = np.random.default_rng(523)\n\n\nclass coorutilsTest(unittest.TestCase):\n    def test_rotation_matrix_from_normals(self):\n        v0 = rng.random(3)\n        v0 *= 1.0 / np.linalg.norm(v0)\n\n        np.random.seed(5)\n        v1 = rng.random(3)\n        v1 *= 1.0 / np.linalg.norm(v1)\n\n        Rf = utils.rotation_matrix_from_normals(v0, v1)\n        Ri = utils.rotation_matrix_from_normals(v1, v0)\n\n        self.assertTrue(np.linalg.norm(utils.mkvc(Rf.dot(v0) - v1)) < tol)\n        self.assertTrue(np.linalg.norm(utils.mkvc(Ri.dot(v1) - v0)) < tol)\n\n    def test_rotate_points_from_normals(self):\n        v0 = rng.random(3)\n        v0 *= 1.0 / np.linalg.norm(v0)\n\n        np.random.seed(15)\n        v1 = rng.random(3)\n        v1 *= 1.0 / np.linalg.norm(v1)\n\n        v2 = utils.mkvc(utils.rotate_points_from_normals(utils.mkvc(v0, 2).T, v0, v1))\n\n        self.assertTrue(np.linalg.norm(v2 - v1) < tol)\n\n    def test_rotateMatrixFromNormals(self):\n        n0 = rng.random(3)\n        n0 *= 1.0 / np.linalg.norm(n0)\n\n        np.random.seed(25)\n        n1 = rng.random(3)\n        n1 *= 1.0 / np.linalg.norm(n1)\n\n        np.random.seed(30)\n        scale = rng.random((100, 1))\n        XYZ0 = scale * n0\n        XYZ1 = scale * n1\n\n        XYZ2 = utils.rotate_points_from_normals(XYZ0, n0, n1)\n        self.assertTrue(\n            np.linalg.norm(utils.mkvc(XYZ1) - utils.mkvc(XYZ2))\n            / np.linalg.norm(utils.mkvc(XYZ1))\n            < tol\n        )\n\n    def test_rotate_vec_cyl2cart(self):\n        vec = np.r_[1.0, 0, 0].reshape(1, 3)\n        grid = np.r_[1.0, np.pi / 4, 0].reshape(1, 3)\n        self.assertTrue(\n            np.allclose(utils.cyl2cart(grid, vec), np.sqrt(2) / 2 * np.r_[1, 1, 0])\n        )\n        self.assertTrue(\n            np.allclose(utils.cyl2cart(grid), np.sqrt(2) / 2 * np.r_[1, 1, 0])\n        )\n\n        vec = np.r_[0, 1, 2].reshape(1, 3)\n        grid = np.r_[1, np.pi / 4, 0].reshape(1, 3)\n        self.assertTrue(\n            np.allclose(\n                utils.cyl2cart(grid, vec), np.r_[-np.sqrt(2) / 2, np.sqrt(2) / 2, 2]\n            )\n        )\n\n        vec = np.r_[1.0, 0]\n        grid = np.r_[1.0, np.pi / 4].reshape(1, 2)\n        self.assertTrue(\n            np.allclose(utils.cyl2cart(grid, vec), np.sqrt(2) / 2 * np.r_[1, 1])\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_curvilinear.py",
    "content": "import numpy as np\nimport unittest\nfrom discretize import TensorMesh, CurvilinearMesh\nfrom discretize.utils import ndgrid\n\n\nclass BasicCurvTests(unittest.TestCase):\n    def setUp(self):\n        a = np.array([1, 1, 1])\n        b = np.array([1, 2])\n        c = np.array([1, 4])\n\n        def gridIt(h):\n            return [np.cumsum(np.r_[0, x]) for x in h]\n\n        X, Y = ndgrid(gridIt([a, b]), vector=False)\n        self.TM2 = TensorMesh([a, b])\n        self.Curv2 = CurvilinearMesh([X, Y])\n        X, Y, Z = ndgrid(gridIt([a, b, c]), vector=False)\n        self.TM3 = TensorMesh([a, b, c])\n        self.Curv3 = CurvilinearMesh([X, Y, Z])\n\n    def test_area_3D(self):\n        test_area = np.array(\n            [\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                4,\n                4,\n                4,\n                4,\n                8,\n                8,\n                8,\n                8,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n            ]\n        )\n        self.assertTrue(np.all(self.Curv3.face_areas == test_area))\n\n    def test_vol_3D(self):\n        test_vol = np.array([1, 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8])\n        np.testing.assert_almost_equal(self.Curv3.cell_volumes, test_vol)\n        self.assertTrue(True)  # Pass if you get past the assertion.\n\n    def test_vol_2D(self):\n        test_vol = np.array([1, 1, 1, 2, 2, 2])\n        t1 = np.all(self.Curv2.cell_volumes == test_vol)\n        self.assertTrue(t1)\n\n    def test_edge_3D(self):\n        test_edge = np.array(\n            [\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n            ]\n        )\n        t1 = np.all(self.Curv3.edge_lengths == test_edge)\n        self.assertTrue(t1)\n\n    def test_edge_2D(self):\n        test_edge = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])\n        t1 = np.all(self.Curv2.edge_lengths == test_edge)\n        self.assertTrue(t1)\n\n    def test_tangents(self):\n        T = self.Curv2.edge_tangents\n        self.assertTrue(\n            np.all(self.Curv2.reshape(T, \"E\", \"Ex\", \"V\")[0] == np.ones(self.Curv2.nEx))\n        )\n        self.assertTrue(\n            np.all(self.Curv2.reshape(T, \"E\", \"Ex\", \"V\")[1] == np.zeros(self.Curv2.nEx))\n        )\n        self.assertTrue(\n            np.all(self.Curv2.reshape(T, \"E\", \"Ey\", \"V\")[0] == np.zeros(self.Curv2.nEy))\n        )\n        self.assertTrue(\n            np.all(self.Curv2.reshape(T, \"E\", \"Ey\", \"V\")[1] == np.ones(self.Curv2.nEy))\n        )\n\n        T = self.Curv3.edge_tangents\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ex\", \"V\")[0] == np.ones(self.Curv3.nEx))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ex\", \"V\")[1] == np.zeros(self.Curv3.nEx))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ex\", \"V\")[2] == np.zeros(self.Curv3.nEx))\n        )\n\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ey\", \"V\")[0] == np.zeros(self.Curv3.nEy))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ey\", \"V\")[1] == np.ones(self.Curv3.nEy))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ey\", \"V\")[2] == np.zeros(self.Curv3.nEy))\n        )\n\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ez\", \"V\")[0] == np.zeros(self.Curv3.nEz))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ez\", \"V\")[1] == np.zeros(self.Curv3.nEz))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(T, \"E\", \"Ez\", \"V\")[2] == np.ones(self.Curv3.nEz))\n        )\n\n    def test_normals(self):\n        N = self.Curv2.face_normals\n        self.assertTrue(\n            np.all(self.Curv2.reshape(N, \"F\", \"Fx\", \"V\")[0] == np.ones(self.Curv2.nFx))\n        )\n        self.assertTrue(\n            np.all(self.Curv2.reshape(N, \"F\", \"Fx\", \"V\")[1] == np.zeros(self.Curv2.nFx))\n        )\n        self.assertTrue(\n            np.all(self.Curv2.reshape(N, \"F\", \"Fy\", \"V\")[0] == np.zeros(self.Curv2.nFy))\n        )\n        self.assertTrue(\n            np.all(self.Curv2.reshape(N, \"F\", \"Fy\", \"V\")[1] == np.ones(self.Curv2.nFy))\n        )\n\n        N = self.Curv3.face_normals\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fx\", \"V\")[0] == np.ones(self.Curv3.nFx))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fx\", \"V\")[1] == np.zeros(self.Curv3.nFx))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fx\", \"V\")[2] == np.zeros(self.Curv3.nFx))\n        )\n\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fy\", \"V\")[0] == np.zeros(self.Curv3.nFy))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fy\", \"V\")[1] == np.ones(self.Curv3.nFy))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fy\", \"V\")[2] == np.zeros(self.Curv3.nFy))\n        )\n\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fz\", \"V\")[0] == np.zeros(self.Curv3.nFz))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fz\", \"V\")[1] == np.zeros(self.Curv3.nFz))\n        )\n        self.assertTrue(\n            np.all(self.Curv3.reshape(N, \"F\", \"Fz\", \"V\")[2] == np.ones(self.Curv3.nFz))\n        )\n\n    def test_grid(self):\n        self.assertTrue(np.all(self.Curv2.gridCC == self.TM2.gridCC))\n        self.assertTrue(np.all(self.Curv2.gridN == self.TM2.gridN))\n        self.assertTrue(np.all(self.Curv2.gridFx == self.TM2.gridFx))\n        self.assertTrue(np.all(self.Curv2.gridFy == self.TM2.gridFy))\n        self.assertTrue(np.all(self.Curv2.gridEx == self.TM2.gridEx))\n        self.assertTrue(np.all(self.Curv2.gridEy == self.TM2.gridEy))\n\n        self.assertTrue(np.all(self.Curv3.gridCC == self.TM3.gridCC))\n        self.assertTrue(np.all(self.Curv3.gridN == self.TM3.gridN))\n        self.assertTrue(np.all(self.Curv3.gridFx == self.TM3.gridFx))\n        self.assertTrue(np.all(self.Curv3.gridFy == self.TM3.gridFy))\n        self.assertTrue(np.all(self.Curv3.gridFz == self.TM3.gridFz))\n        self.assertTrue(np.all(self.Curv3.gridEx == self.TM3.gridEx))\n        self.assertTrue(np.all(self.Curv3.gridEy == self.TM3.gridEy))\n        self.assertTrue(np.all(self.Curv3.gridEz == self.TM3.gridEz))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_curvilinear_vtk.py",
    "content": "import numpy as np\nimport unittest\nimport os\nimport discretize\n\ntry:\n    import vtk.util.numpy_support as nps\nexcept ImportError:\n    has_vtk = False\nelse:\n    has_vtk = True\n\n\nif has_vtk:\n\n    class TestCurvilinearMeshVTK(unittest.TestCase):\n        def setUp(self):\n            sz = [16, 16, 16]\n            mesh = discretize.CurvilinearMesh(\n                discretize.utils.example_curvilinear_grid(sz, \"rotate\")\n            )\n            self.mesh = mesh\n\n        def test_VTK_object_conversion(self):\n            mesh = self.mesh\n            vec = np.arange(mesh.nC)\n            models = {\"arange\": vec}\n\n            vtkObj = mesh.to_vtk(models)\n\n            self.assertEqual(mesh.nC, vtkObj.GetNumberOfCells())\n            self.assertEqual(mesh.nN, vtkObj.GetNumberOfPoints())\n            self.assertEqual(\n                len(models.keys()), vtkObj.GetCellData().GetNumberOfArrays()\n            )\n            bnds = vtkObj.GetBounds()\n            self.assertEqual(mesh.x0[0], bnds[0])\n            self.assertEqual(mesh.x0[1], bnds[2])\n            self.assertEqual(mesh.x0[2], bnds[4])\n\n            names = list(models.keys())\n            for i in range(vtkObj.GetCellData().GetNumberOfArrays()):\n                name = names[i]\n                self.assertEqual(name, vtkObj.GetCellData().GetArrayName(i))\n                arr = nps.vtk_to_numpy(vtkObj.GetCellData().GetArray(i))\n                arr = arr.flatten(order=\"F\")\n                self.assertTrue(np.allclose(models[name], arr))\n\n        def test_VTK_file_IO(self):\n            mesh = self.mesh\n            vec = np.arange(mesh.nC)\n            models = {\"arange.txt\": vec}\n            mesh.write_vtk(\"temp.vts\", models)\n            print(\"Writing of VTK files is working\")\n            os.remove(\"temp.vts\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_interpolation.py",
    "content": "import numpy as np\nimport unittest\n\nimport discretize\n\nMESHTYPES = [\"uniformTensorMesh\", \"randomTensorMesh\"]\nTOLERANCES = [0.9, 0.5, 0.5]\ncall1 = lambda fun, xyz: fun(xyz)\ncall2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, -1])\ncall3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\ncart_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)]\ncart_row3 = lambda g, xfun, yfun, zfun: np.c_[\n    call3(xfun, g), call3(yfun, g), call3(zfun, g)\n]\ncartF2 = lambda M, fx, fy: np.vstack(\n    (cart_row2(M.gridFx, fx, fy), cart_row2(M.gridFy, fx, fy))\n)\ncartF2Cyl = lambda M, fx, fy: np.vstack(\n    (cart_row2(M.gridFx, fx, fy), cart_row2(M.gridFz, fx, fy))\n)\ncartE2 = lambda M, ex, ey: np.vstack(\n    (cart_row2(M.gridEx, ex, ey), cart_row2(M.gridEy, ex, ey))\n)\ncartE2Cyl = lambda M, ex, ey: cart_row2(M.gridEy, ex, ey)\ncartF3 = lambda M, fx, fy, fz: np.vstack(\n    (\n        cart_row3(M.gridFx, fx, fy, fz),\n        cart_row3(M.gridFy, fx, fy, fz),\n        cart_row3(M.gridFz, fx, fy, fz),\n    )\n)\ncartE3 = lambda M, ex, ey, ez: np.vstack(\n    (\n        cart_row3(M.gridEx, ex, ey, ez),\n        cart_row3(M.gridEy, ex, ey, ez),\n        cart_row3(M.gridEz, ex, ey, ez),\n    )\n)\n\nTOL = 1e-7\n\n\nclass TestInterpolation1D(discretize.tests.OrderTest):\n    name = \"Interpolation 1D\"\n    meshTypes = MESHTYPES\n    tolerance = TOLERANCES\n    meshDimension = 1\n    meshSizes = [8, 16, 32, 64, 128]\n    random_seed = np.random.default_rng(55124)\n    LOCS = random_seed.random(50) * 0.6 + 0.2\n\n    def getError(self):\n        funX = lambda x: np.cos(2 * np.pi * x)\n\n        ana = call1(funX, self.LOCS)\n\n        if \"CC\" == self.type:\n            grid = call1(funX, self.M.gridCC)\n        elif \"N\" == self.type:\n            grid = call1(funX, self.M.gridN)\n\n        comp = self.M.get_interpolation_matrix(self.LOCS, self.type) * grid\n\n        err = np.linalg.norm((comp - ana), 2)\n        return err\n\n    def test_orderCC(self):\n        self.type = \"CC\"\n        self.name = \"Interpolation 1D: CC\"\n        self.orderTest()\n\n    def test_orderN(self):\n        self.type = \"N\"\n        self.name = \"Interpolation 1D: N\"\n        self.orderTest()\n\n\nclass TestOutliersInterp1D(unittest.TestCase):\n    def setUp(self):\n        pass\n\n    def test_outliers(self):\n        M = discretize.TensorMesh([4])\n        Q = M.get_interpolation_matrix(\n            np.array([[0], [0.126], [0.127]]), \"CC\", zeros_outside=True\n        )\n        x = np.arange(4) + 1\n        self.assertTrue(np.linalg.norm(Q * x - np.r_[1, 1.004, 1.008]) < TOL)\n        Q = M.get_interpolation_matrix(\n            np.array([[-1], [0.126], [0.127]]), \"CC\", zeros_outside=True\n        )\n        self.assertTrue(np.linalg.norm(Q * x - np.r_[0, 1.004, 1.008]) < TOL)\n\n\nclass TestInterpolation2d(discretize.tests.OrderTest):\n    name = \"Interpolation 2D\"\n    meshTypes = MESHTYPES\n    tolerance = TOLERANCES\n    meshDimension = 2\n    meshSizes = [8, 16, 32, 64]\n    random_seed = np.random.default_rng(2457)\n    LOCS = random_seed.random((50, 2)) * 0.6 + 0.2\n\n    def getError(self):\n        funX = lambda x, y: np.cos(2 * np.pi * y)\n        funY = lambda x, y: np.cos(2 * np.pi * x)\n\n        if \"x\" in self.type:\n            ana = call2(funX, self.LOCS)\n        elif \"y\" in self.type:\n            ana = call2(funY, self.LOCS)\n        else:\n            ana = call2(funX, self.LOCS)\n\n        if \"F\" in self.type:\n            Fc = cartF2(self.M, funX, funY)\n            grid = self.M.project_face_vector(Fc)\n        elif \"E\" in self.type:\n            Ec = cartE2(self.M, funX, funY)\n            grid = self.M.project_edge_vector(Ec)\n        elif \"CC\" == self.type:\n            grid = call2(funX, self.M.gridCC)\n        elif \"N\" == self.type:\n            grid = call2(funX, self.M.gridN)\n\n        comp = self.M.get_interpolation_matrix(self.LOCS, self.type) * grid\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_orderCC(self):\n        self.type = \"CC\"\n        self.name = \"Interpolation 2D: CC\"\n        self.orderTest()\n\n    def test_orderN(self):\n        self.type = \"N\"\n        self.name = \"Interpolation 2D: N\"\n        self.orderTest()\n\n    def test_orderFx(self):\n        self.type = \"Fx\"\n        self.name = \"Interpolation 2D: Fx\"\n        self.orderTest()\n\n    def test_orderFy(self):\n        self.type = \"Fy\"\n        self.name = \"Interpolation 2D: Fy\"\n        self.orderTest()\n\n    def test_orderEx(self):\n        self.type = \"Ex\"\n        self.name = \"Interpolation 2D: Ex\"\n        self.orderTest()\n\n    def test_orderEy(self):\n        self.type = \"Ey\"\n        self.name = \"Interpolation 2D: Ey\"\n        self.orderTest()\n\n\nclass TestInterpolationSymmetricCyl_Simple(unittest.TestCase):\n    def test_simpleInter(self):\n        M = discretize.CylindricalMesh([4, 1, 1])\n        locs = np.r_[0, 0, 0.5]\n        fx = np.array([[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]])\n        self.assertTrue(np.all(fx == M.get_interpolation_matrix(locs, \"Fx\").todense()))\n        fz = np.array([[0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0]])\n        self.assertTrue(np.all(fz == M.get_interpolation_matrix(locs, \"Fz\").todense()))\n\n    def test_exceptions(self):\n        M = discretize.CylindricalMesh([4, 1, 1])\n        locs = np.r_[0, 0, 0.5]\n        self.assertRaises(Exception, lambda: M.get_interpolation_matrix(locs, \"Fy\"))\n        self.assertRaises(Exception, lambda: M.get_interpolation_matrix(locs, \"Ex\"))\n        self.assertRaises(Exception, lambda: M.get_interpolation_matrix(locs, \"Ez\"))\n\n\nclass TestInterpolationSymCyl(discretize.tests.OrderTest):\n    name = \"Interpolation Symmetric 3D\"\n    meshTypes = [\"uniform_symmetric_CylMesh\"]  # MESHTYPES +\n    tolerance = 0.6\n    meshDimension = 3\n    meshSizes = [32, 64, 128, 256]\n    random_seed = np.random.default_rng(81756234)\n    LOCS = np.c_[\n        random_seed.random(4) * 0.6 + 0.2,\n        np.zeros(4),\n        random_seed.random(4) * 0.6 + 0.2,\n    ]\n\n    def getError(self):\n        funX = lambda x, y: np.cos(2 * np.pi * y)\n        funY = lambda x, y: np.cos(2 * np.pi * x)\n\n        if \"x\" in self.type:\n            ana = call2(funX, self.LOCS)\n        elif \"y\" in self.type:\n            ana = call2(funY, self.LOCS)\n        elif \"z\" in self.type:\n            ana = call2(funY, self.LOCS)\n        else:\n            ana = call2(funX, self.LOCS)\n\n        if \"Fx\" == self.type:\n            Fc = cartF2Cyl(self.M, funX, funY)\n            Fc = np.c_[Fc[:, 0], np.zeros(self.M.nF), Fc[:, 1]]\n            grid = self.M.project_face_vector(Fc)\n        elif \"Fz\" == self.type:\n            Fc = cartF2Cyl(self.M, funX, funY)\n            Fc = np.c_[Fc[:, 0], np.zeros(self.M.nF), Fc[:, 1]]\n\n            grid = self.M.project_face_vector(Fc)\n        elif \"E\" in self.type:\n            Ec = cartE2Cyl(self.M, funX, funY)\n            grid = Ec[:, 1]\n        elif \"CC\" == self.type:\n            grid = call2(funX, self.M.gridCC)\n        elif \"N\" == self.type:\n            grid = call2(funX, self.M.gridN)\n\n        comp = self.M.get_interpolation_matrix(self.LOCS, self.type) * grid\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_orderCC(self):\n        self.type = \"CC\"\n        self.name = \"Interpolation 3D Symmetric CYLMESH: CC\"\n        self.orderTest()\n\n    def test_orderFx(self):\n        self.type = \"Fx\"\n        self.name = \"Interpolation 3D Symmetric CYLMESH: Fx\"\n        self.orderTest()\n\n    def test_orderFz(self):\n        self.type = \"Fz\"\n        self.name = \"Interpolation 3D Symmetric CYLMESH: Fz\"\n        self.orderTest()\n\n    def test_orderEy(self):\n        self.type = \"Ey\"\n        self.name = \"Interpolation 3D Symmetric CYLMESH: Ey\"\n        self.orderTest()\n\n\nclass TestInterpolationCyl(discretize.tests.OrderTest):\n    name = \"Interpolation Cylindrical 3D\"\n    meshTypes = [\"uniformCylMesh\", \"randomCylMesh\"]  # MESHTYPES +\n    meshDimension = 3\n    meshSizes = [8, 16, 32, 64]\n    random_seed = np.random.default_rng(876234)\n    LOCS = np.c_[\n        random_seed.random(20) * 0.6 + 0.2,\n        2 * np.pi * (random_seed.random(20) * 0.6 + 0.2),\n        random_seed.random(20) * 0.6 + 0.2,\n    ]\n\n    def getError(self):\n        func = lambda x, y, z: np.cos(2 * np.pi * x) + np.cos(y) + np.cos(2 * np.pi * z)\n        ana = func(*self.LOCS.T)\n        mesh = self.M\n\n        if \"F\" in self.type:\n            v = func(*mesh.faces.T)\n        elif \"E\" in self.type:\n            v = func(*mesh.edges.T)\n        elif \"CC\" == self.type:\n            v = func(*mesh.cell_centers.T)\n        elif \"N\" == self.type:\n            v = func(*mesh.nodes.T)\n\n        comp = mesh.get_interpolation_matrix(self.LOCS, self.type) * v\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_orderCC(self):\n        self.type = \"CC\"\n        self.name = \"Interpolation 3D CYLMESH: CC\"\n        self.orderTest()\n\n    def test_orderN(self):\n        self.type = \"N\"\n        self.name = \"Interpolation 3D CYLMESH: N\"\n        self.orderTest()\n\n    def test_orderFx(self):\n        self.type = \"Fx\"\n        self.name = \"Interpolation 3D CYLMESH: Fx\"\n        self.orderTest()\n\n    def test_orderFy(self):\n        self.type = \"Fy\"\n        self.name = \"Interpolation 3D CYLMESH: Fy\"\n        self.orderTest()\n\n    def test_orderFz(self):\n        self.type = \"Fz\"\n        self.name = \"Interpolation 3D CYLMESH: Fz\"\n        self.orderTest()\n\n    def test_orderEx(self):\n        self.type = \"Ex\"\n        self.name = \"Interpolation 3D CYLMESH: Ex\"\n        self.orderTest()\n\n    def test_orderEy(self):\n        self.type = \"Ey\"\n        self.name = \"Interpolation 3D CYLMESH: Ey\"\n        self.orderTest()\n\n    def test_orderEz(self):\n        self.type = \"Ez\"\n        self.name = \"Interpolation 3D CYLMESH: Ez\"\n        self.orderTest()\n\n\nclass TestInterpolation3D(discretize.tests.OrderTest):\n    random_seed = np.random.default_rng(234)\n    name = \"Interpolation\"\n    LOCS = random_seed.random((50, 3)) * 0.6 + 0.2\n    meshTypes = MESHTYPES\n    tolerance = TOLERANCES\n    meshDimension = 3\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        funX = lambda x, y, z: np.cos(2 * np.pi * y)\n        funY = lambda x, y, z: np.cos(2 * np.pi * z)\n        funZ = lambda x, y, z: np.cos(2 * np.pi * x)\n\n        if \"x\" in self.type:\n            ana = call3(funX, self.LOCS)\n        elif \"y\" in self.type:\n            ana = call3(funY, self.LOCS)\n        elif \"z\" in self.type:\n            ana = call3(funZ, self.LOCS)\n        else:\n            ana = call3(funX, self.LOCS)\n\n        if \"F\" in self.type:\n            Fc = cartF3(self.M, funX, funY, funZ)\n            grid = self.M.project_face_vector(Fc)\n        elif \"E\" in self.type:\n            Ec = cartE3(self.M, funX, funY, funZ)\n            grid = self.M.project_edge_vector(Ec)\n        elif \"CC\" == self.type:\n            grid = call3(funX, self.M.gridCC)\n        elif \"N\" == self.type:\n            grid = call3(funX, self.M.gridN)\n\n        comp = self.M.get_interpolation_matrix(self.LOCS, self.type) * grid\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_orderCC(self):\n        self.type = \"CC\"\n        self.name = \"Interpolation 3D: CC\"\n        self.orderTest()\n\n    def test_orderN(self):\n        self.type = \"N\"\n        self.name = \"Interpolation 3D: N\"\n        self.orderTest()\n\n    def test_orderFx(self):\n        self.type = \"Fx\"\n        self.name = \"Interpolation 3D: Fx\"\n        self.orderTest()\n\n    def test_orderFy(self):\n        self.type = \"Fy\"\n        self.name = \"Interpolation 3D: Fy\"\n        self.orderTest()\n\n    def test_orderFz(self):\n        self.type = \"Fz\"\n        self.name = \"Interpolation 3D: Fz\"\n        self.orderTest()\n\n    def test_orderEx(self):\n        self.type = \"Ex\"\n        self.name = \"Interpolation 3D: Ex\"\n        self.orderTest()\n\n    def test_orderEy(self):\n        self.type = \"Ey\"\n        self.name = \"Interpolation 3D: Ey\"\n        self.orderTest()\n\n    def test_orderEz(self):\n        self.type = \"Ez\"\n        self.name = \"Interpolation 3D: Ez\"\n        self.orderTest()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_operators.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\n\n# Tolerance\nTOL = 1e-14\n\ngen = np.random.default_rng(1)\n\nMESHTYPES = [\n    \"uniformTensorMesh\",\n    # 'randomTensorMesh',\n    \"uniformCurv\",\n    \"rotateCurv\",\n]\nORDERS = np.r_[2.0, 2.0, 2.0]\ncall2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1])\ncall3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\ncart_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)]\ncart_row3 = lambda g, xfun, yfun, zfun: np.c_[\n    call3(xfun, g), call3(yfun, g), call3(zfun, g)\n]\ncartF2 = lambda M, fx, fy: np.vstack(\n    (cart_row2(M.gridFx, fx, fy), cart_row2(M.gridFy, fx, fy))\n)\ncartE2 = lambda M, ex, ey: np.vstack(\n    (cart_row2(M.gridEx, ex, ey), cart_row2(M.gridEy, ex, ey))\n)\ncartF3 = lambda M, fx, fy, fz: np.vstack(\n    (\n        cart_row3(M.gridFx, fx, fy, fz),\n        cart_row3(M.gridFy, fx, fy, fz),\n        cart_row3(M.gridFz, fx, fy, fz),\n    )\n)\ncartE3 = lambda M, ex, ey, ez: np.vstack(\n    (\n        cart_row3(M.gridEx, ex, ey, ez),\n        cart_row3(M.gridEy, ex, ey, ez),\n        cart_row3(M.gridEz, ex, ey, ez),\n    )\n)\n\n\nclass TestCurl(discretize.tests.OrderTest):\n    name = \"Curl\"\n    meshTypes = MESHTYPES\n\n    def getError(self):\n        # fun: i (cos(y)) + j (cos(z)) + k (cos(x))\n        # sol: i (sin(z)) + j (sin(x)) + k (sin(y))\n\n        funX = lambda x, y, z: np.cos(2 * np.pi * y)\n        funY = lambda x, y, z: np.cos(2 * np.pi * z)\n        funZ = lambda x, y, z: np.cos(2 * np.pi * x)\n\n        solX = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * z)\n        solY = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * x)\n        solZ = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * y)\n\n        Ec = cartE3(self.M, funX, funY, funZ)\n        E = self.M.project_edge_vector(Ec)\n\n        Fc = cartF3(self.M, solX, solY, solZ)\n        curlE_ana = self.M.project_face_vector(Fc)\n\n        curlE = self.M.edge_curl.dot(E)\n        if self._meshType == \"rotateCurv\":\n            # Really it is the integration we should be caring about:\n            # So, let us look at the l2 norm.\n            err = np.linalg.norm(self.M.face_areas * (curlE - curlE_ana), 2)\n        else:\n            err = np.linalg.norm((curlE - curlE_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCurl2D(discretize.tests.OrderTest):\n    name = \"Cell Grad 2D - Dirichlet\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 2\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        # Test function\n        ex = lambda x, y: np.cos(y)\n        ey = lambda x, y: np.cos(x)\n        sol = lambda x, y: -np.sin(x) + np.sin(y)\n\n        sol_curl2d = call2(sol, self.M.gridCC)\n        Ec = cartE2(self.M, ex, ey)\n        sol_ana = self.M.edge_curl * self.M.project_face_vector(Ec)\n        err = np.linalg.norm((sol_curl2d - sol_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\n# class TestCellGrad1D_InhomogeneousDirichlet(discretize.tests.OrderTest):\n#     name = \"Cell Grad 1D - Dirichlet\"\n#     meshTypes = [\"uniformTensorMesh\"]\n#     meshDimension = 1\n#     expectedOrders = (\n#         1  # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2\n#     )\n#     meshSizes = [8, 16, 32, 64]\n#\n#     def getError(self):\n#         # Test function\n#         fx = lambda x: -2 * np.pi * np.sin(2 * np.pi * x)\n#         sol = lambda x: np.cos(2 * np.pi * x)\n#\n#         xc = sol(self.M.gridCC)\n#\n#         gradX_ana = fx(self.M.gridFx)\n#\n#         bc = np.array([1, 1])\n#         self.M.set_cell_gradient_BC(\"dirichlet\")\n#         gradX = self.M.cell_gradient.dot(xc) + self.M.cellGradBC * bc\n#\n#         err = np.linalg.norm((gradX - gradX_ana), np.inf)\n#\n#         return err\n#\n#     def test_order(self):\n#         self.orderTest()\n\n\nclass TestCellGrad2D_Dirichlet(discretize.tests.OrderTest):\n    name = \"Cell Grad 2D - Dirichlet\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 2\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        # Test function\n        fx = lambda x, y: 2 * np.pi * np.cos(2 * np.pi * x) * np.sin(2 * np.pi * y)\n        fy = lambda x, y: 2 * np.pi * np.cos(2 * np.pi * y) * np.sin(2 * np.pi * x)\n        sol = lambda x, y: np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y)\n\n        xc = call2(sol, self.M.gridCC)\n\n        Fc = cartF2(self.M, fx, fy)\n        gradX_ana = self.M.project_face_vector(Fc)\n\n        self.M.set_cell_gradient_BC(\"dirichlet\")\n        gradX = self.M.cell_gradient.dot(xc)\n\n        err = np.linalg.norm((gradX - gradX_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCellGrad3D_Dirichlet(discretize.tests.OrderTest):\n    name = \"Cell Grad 3D - Dirichlet\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 3\n    meshSizes = [8, 16, 32]\n\n    def getError(self):\n        # Test function\n        fx = (\n            lambda x, y, z: 2\n            * np.pi\n            * np.cos(2 * np.pi * x)\n            * np.sin(2 * np.pi * y)\n            * np.sin(2 * np.pi * z)\n        )\n        fy = (\n            lambda x, y, z: 2\n            * np.pi\n            * np.sin(2 * np.pi * x)\n            * np.cos(2 * np.pi * y)\n            * np.sin(2 * np.pi * z)\n        )\n        fz = (\n            lambda x, y, z: 2\n            * np.pi\n            * np.sin(2 * np.pi * x)\n            * np.sin(2 * np.pi * y)\n            * np.cos(2 * np.pi * z)\n        )\n        sol = (\n            lambda x, y, z: np.sin(2 * np.pi * x)\n            * np.sin(2 * np.pi * y)\n            * np.sin(2 * np.pi * z)\n        )\n\n        xc = call3(sol, self.M.gridCC)\n\n        Fc = cartF3(self.M, fx, fy, fz)\n        gradX_ana = self.M.project_face_vector(Fc)\n\n        self.M.set_cell_gradient_BC(\"dirichlet\")\n        gradX = self.M.cell_gradient.dot(xc)\n\n        err = np.linalg.norm((gradX - gradX_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCellGrad2D_Neumann(discretize.tests.OrderTest):\n    name = \"Cell Grad 2D - Neumann\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 2\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        # Test function\n        fx = lambda x, y: -2 * np.pi * np.sin(2 * np.pi * x) * np.cos(2 * np.pi * y)\n        fy = lambda x, y: -2 * np.pi * np.sin(2 * np.pi * y) * np.cos(2 * np.pi * x)\n        sol = lambda x, y: np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y)\n\n        xc = call2(sol, self.M.gridCC)\n\n        Fc = cartF2(self.M, fx, fy)\n        gradX_ana = self.M.project_face_vector(Fc)\n\n        self.M.set_cell_gradient_BC(\"neumann\")\n        gradX = self.M.cell_gradient.dot(xc)\n\n        err = np.linalg.norm((gradX - gradX_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCellGrad3D_Neumann(discretize.tests.OrderTest):\n    name = \"Cell Grad 3D - Neumann\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 3\n    meshSizes = [8, 16, 32]\n\n    def getError(self):\n        # Test function\n        fx = (\n            lambda x, y, z: -2\n            * np.pi\n            * np.sin(2 * np.pi * x)\n            * np.cos(2 * np.pi * y)\n            * np.cos(2 * np.pi * z)\n        )\n        fy = (\n            lambda x, y, z: -2\n            * np.pi\n            * np.cos(2 * np.pi * x)\n            * np.sin(2 * np.pi * y)\n            * np.cos(2 * np.pi * z)\n        )\n        fz = (\n            lambda x, y, z: -2\n            * np.pi\n            * np.cos(2 * np.pi * x)\n            * np.cos(2 * np.pi * y)\n            * np.sin(2 * np.pi * z)\n        )\n        sol = (\n            lambda x, y, z: np.cos(2 * np.pi * x)\n            * np.cos(2 * np.pi * y)\n            * np.cos(2 * np.pi * z)\n        )\n\n        xc = call3(sol, self.M.gridCC)\n\n        Fc = cartF3(self.M, fx, fy, fz)\n        gradX_ana = self.M.project_face_vector(Fc)\n\n        self.M.set_cell_gradient_BC(\"neumann\")\n        gradX = self.M.cell_gradient.dot(xc)\n\n        err = np.linalg.norm((gradX - gradX_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestFaceDiv3D(discretize.tests.OrderTest):\n    name = \"Face Divergence 3D\"\n    meshTypes = MESHTYPES\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        # Test function\n        fx = lambda x, y, z: np.sin(2 * np.pi * x)\n        fy = lambda x, y, z: np.sin(2 * np.pi * y)\n        fz = lambda x, y, z: np.sin(2 * np.pi * z)\n        sol = lambda x, y, z: (\n            2 * np.pi * np.cos(2 * np.pi * x)\n            + 2 * np.pi * np.cos(2 * np.pi * y)\n            + 2 * np.pi * np.cos(2 * np.pi * z)\n        )\n\n        Fc = cartF3(self.M, fx, fy, fz)\n        F = self.M.project_face_vector(Fc)\n\n        divF = self.M.face_divergence.dot(F)\n        divF_ana = call3(sol, self.M.gridCC)\n\n        if self._meshType == \"rotateCurv\":\n            # Really it is the integration we should be caring about:\n            # So, let us look at the l2 norm.\n            err = np.linalg.norm(self.M.cell_volumes * (divF - divF_ana), 2)\n        else:\n            err = np.linalg.norm((divF - divF_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestFaceDiv2D(discretize.tests.OrderTest):\n    name = \"Face Divergence 2D\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        # Test function\n        fx = lambda x, y: np.sin(2 * np.pi * x)\n        fy = lambda x, y: np.sin(2 * np.pi * y)\n        sol = lambda x, y: 2 * np.pi * (np.cos(2 * np.pi * x) + np.cos(2 * np.pi * y))\n\n        Fc = cartF2(self.M, fx, fy)\n        F = self.M.project_face_vector(Fc)\n\n        divF = self.M.face_divergence.dot(F)\n        divF_ana = call2(sol, self.M.gridCC)\n\n        err = np.linalg.norm((divF - divF_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestNodalGrad(discretize.tests.OrderTest):\n    name = \"Nodal Gradient\"\n    meshTypes = MESHTYPES\n\n    def getError(self):\n        # Test function\n        fun = lambda x, y, z: (np.cos(x) + np.cos(y) + np.cos(z))\n        # i (sin(x)) + j (sin(y)) + k (sin(z))\n        solX = lambda x, y, z: -np.sin(x)\n        solY = lambda x, y, z: -np.sin(y)\n        solZ = lambda x, y, z: -np.sin(z)\n\n        phi = call3(fun, self.M.gridN)\n        gradE = self.M.nodal_gradient.dot(phi)\n\n        Ec = cartE3(self.M, solX, solY, solZ)\n        gradE_ana = self.M.project_edge_vector(Ec)\n\n        err = np.linalg.norm((gradE - gradE_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestNodalGrad2D(discretize.tests.OrderTest):\n    name = \"Nodal Gradient 2D\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n\n    def getError(self):\n        # Test function\n        fun = lambda x, y: (np.cos(x) + np.cos(y))\n        # i (sin(x)) + j (sin(y)) + k (sin(z))\n        solX = lambda x, y: -np.sin(x)\n        solY = lambda x, y: -np.sin(y)\n\n        phi = call2(fun, self.M.gridN)\n        gradE = self.M.nodal_gradient.dot(phi)\n\n        Ec = cartE2(self.M, solX, solY)\n        gradE_ana = self.M.project_edge_vector(Ec)\n\n        err = np.linalg.norm((gradE - gradE_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestAveraging1D(discretize.tests.OrderTest):\n    name = \"Averaging 1D\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 1\n    meshSizes = [16, 32, 64]\n\n    def getError(self):\n        num = self.getAve(self.M) * self.getHere(self.M)\n        err = np.linalg.norm((self.getThere(self.M) - num), np.inf)\n        return err\n\n    def test_orderN2CC(self):\n        self.name = \"Averaging 1D: N2CC\"\n        fun = lambda x: np.cos(x)\n        self.getHere = lambda M: fun(M.gridN)\n        self.getThere = lambda M: fun(M.gridCC)\n        self.getAve = lambda M: M.aveN2CC\n        self.orderTest()\n\n    def test_exactN2F(self):\n        self.name = \"Averaging 1D: N2F\"\n        fun = lambda x: np.cos(x)\n        M, _ = discretize.tests.setup_mesh(\"uniformTensorMesh\", 32, 1)\n        v1 = M.aveN2F * fun(M.gridN)\n        v2 = fun(M.faces)\n        np.testing.assert_allclose(v1, v2)\n\n    def test_orderN2E(self):\n        self.name = \"Averaging 1D: N2E\"\n        fun = lambda x: np.cos(x)\n        self.getHere = lambda M: fun(M.gridN)\n        self.getThere = lambda M: fun(M.edges)\n        self.getAve = lambda M: M.aveN2E\n        self.orderTest()\n\n    def test_orderF2CC(self):\n        self.name = \"Averaging 1D: F2CC\"\n        fun = lambda x: np.cos(x)\n        self.getHere = lambda M: fun(M.faces)\n        self.getThere = lambda M: fun(M.gridCC)\n        self.getAve = lambda M: M.aveF2CC\n        self.orderTest()\n\n    def test_orderF2CCV(self):\n        self.name = \"Averaging 1D: F2CCV\"\n        fun = lambda x: np.cos(x)\n        self.getHere = lambda M: fun(M.faces)\n        self.getThere = lambda M: fun(M.cell_centers)\n        self.getAve = lambda M: M.aveF2CCV\n        self.orderTest()\n\n    def test_orderCC2F(self):\n        self.name = \"Averaging 1D: CC2F\"\n        fun = lambda x: np.cos(x)\n        self.getHere = lambda M: fun(M.gridCC)\n        self.getThere = lambda M: fun(M.faces)\n        self.getAve = lambda M: M.aveCC2F\n        self.expectedOrders = 1\n        self.orderTest()\n        self.expectedOrders = 2\n\n    def test_exactE2CC(self):\n        self.name = \"Averaging 1D: E2CC\"\n        fun = lambda x: np.cos(x)\n        M, _ = discretize.tests.setup_mesh(\"uniformTensorMesh\", 32, 1)\n        v1 = M.aveE2CC @ fun(M.edges)\n        v2 = fun(M.gridCC)\n        np.testing.assert_allclose(v1, v2)\n\n    def test_exactE2CCV(self):\n        self.name = \"Averaging 1D: E2CCV\"\n        fun = lambda x: np.cos(x)\n        M, _ = discretize.tests.setup_mesh(\"uniformTensorMesh\", 32, 1)\n        v1 = M.aveE2CCV @ fun(M.edges)\n        v2 = fun(M.gridCC)\n        np.testing.assert_allclose(v1, v2)\n\n    def test_exactCC2E(self):\n        self.name = \"Averaging 1D: cell_centers_to_edges\"\n        fun = lambda x: np.cos(x)\n        M, _ = discretize.tests.setup_mesh(\"uniformTensorMesh\", 32, 1)\n        v1 = M.average_cell_to_edge @ fun(M.edges)\n        v2 = fun(M.gridCC)\n        np.testing.assert_allclose(v1, v2)\n\n    def test_orderCC2FV(self):\n        self.name = \"Averaging 1D: CC2FV\"\n        fun = lambda x: np.cos(x)\n        self.getHere = lambda M: fun(M.gridCC)\n        self.getThere = lambda M: fun(M.faces)\n        self.getAve = lambda M: M.aveCCV2F\n        self.expectedOrders = 1\n        self.orderTest()\n        self.expectedOrders = 2\n\n    def test_orderE2FV(self):\n        self.name = \"Averaging 1D: E2FV\"\n        fun = lambda x: np.cos(x)\n        self.getHere = lambda M: fun(M.edges)\n        self.getThere = lambda M: fun(M.faces)\n        self.getAve = lambda M: M.average_edge_to_face\n        self.orderTest()\n\n\nclass TestAverating2DSimple(unittest.TestCase):\n    def setUp(self):\n        hx = gen.random(10)\n        hy = gen.random(10)\n        self.mesh = discretize.TensorMesh([hx, hy])\n\n    def test_constantEdges(self):\n        edge_vec = np.ones(self.mesh.nE)\n        assert all(self.mesh.aveE2CC * edge_vec == 1.0)\n        assert all(self.mesh.aveE2CCV * edge_vec == 1.0)\n\n    def test_constantFaces(self):\n        face_vec = np.ones(self.mesh.nF)\n        assert all(self.mesh.aveF2CC * face_vec == 1.0)\n        assert all(self.mesh.aveF2CCV * face_vec == 1.0)\n\n\nclass TestAveraging2D(discretize.tests.OrderTest):\n    name = \"Averaging 2D\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    meshSizes = [16, 32, 64]\n\n    def getError(self):\n        num = self.getAve(self.M) * self.getHere(self.M)\n        err = np.linalg.norm((self.getThere(self.M) - num), np.inf)\n        return err\n\n    def test_orderN2CC(self):\n        self.name = \"Averaging 2D: N2CC\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridN)\n        self.getThere = lambda M: call2(fun, M.gridCC)\n        self.getAve = lambda M: M.aveN2CC\n        self.orderTest()\n\n    def test_orderN2F(self):\n        self.name = \"Averaging 2D: N2F\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridN)\n        self.getThere = lambda M: np.r_[call2(fun, M.gridFx), call2(fun, M.gridFy)]\n        self.getAve = lambda M: M.aveN2F\n        self.orderTest()\n\n    def test_orderN2E(self):\n        self.name = \"Averaging 2D: N2E\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridN)\n        self.getThere = lambda M: np.r_[call2(fun, M.gridEx), call2(fun, M.gridEy)]\n        self.getAve = lambda M: M.aveN2E\n        self.orderTest()\n\n    def test_orderF2CC(self):\n        self.name = \"Averaging 2D: F2CC\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: np.r_[call2(fun, M.gridFx), call2(fun, M.gridFy)]\n        self.getThere = lambda M: call2(fun, M.gridCC)\n        self.getAve = lambda M: M.aveF2CC\n        self.orderTest()\n\n    def test_orderF2CCV(self):\n        self.name = \"Averaging 2D: F2CCV\"\n        funX = lambda x, y: (np.cos(x) + np.sin(y))\n        funY = lambda x, y: (np.cos(y) * np.sin(x))\n        self.getHere = lambda M: np.r_[call2(funX, M.gridFx), call2(funY, M.gridFy)]\n        self.getThere = lambda M: np.r_[call2(funX, M.gridCC), call2(funY, M.gridCC)]\n        self.getAve = lambda M: M.aveF2CCV\n        self.orderTest()\n\n    def test_orderCC2F(self):\n        self.name = \"Averaging 2D: CC2F\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridCC)\n        self.getThere = lambda M: np.r_[call2(fun, M.gridFx), call2(fun, M.gridFy)]\n        self.getAve = lambda M: M.aveCC2F\n        self.expectedOrders = ORDERS / 2.0\n        self.orderTest()\n        self.expectedOrders = ORDERS\n\n    def test_orderE2CC(self):\n        self.name = \"Averaging 2D: E2CC\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: np.r_[call2(fun, M.gridEx), call2(fun, M.gridEy)]\n        self.getThere = lambda M: call2(fun, M.gridCC)\n        self.getAve = lambda M: M.aveE2CC\n        self.orderTest()\n\n    def test_orderE2CCV(self):\n        self.name = \"Averaging 2D: E2CCV\"\n        funX = lambda x, y: (np.cos(x) + np.sin(y))\n        funY = lambda x, y: (np.cos(y) * np.sin(x))\n        self.getHere = lambda M: np.r_[call2(funX, M.gridEx), call2(funY, M.gridEy)]\n        self.getThere = lambda M: np.r_[call2(funX, M.gridCC), call2(funY, M.gridCC)]\n        self.getAve = lambda M: M.aveE2CCV\n        self.orderTest()\n\n    def test_orderCC2E(self):\n        self.name = \"Averaging 2D: cell_centers_to_edges\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridCC)\n        self.getThere = lambda M: call2(fun, M.edges)\n        self.getAve = lambda M: M.average_cell_to_edge\n        self.expectedOrders = ORDERS / 2.0\n        self.orderTest()\n        self.expectedOrders = ORDERS\n\n    def test_orderCC2FV(self):\n        self.name = \"Averaging 2D: CC2FV\"\n        funX = lambda x, y: (np.cos(x) + np.sin(y))\n        funY = lambda x, y: (np.cos(y) * np.sin(x))\n        self.getHere = lambda M: np.r_[call2(funX, M.gridCC), call2(funY, M.gridCC)]\n        self.getThere = lambda M: np.r_[call2(funX, M.gridFx), call2(funY, M.gridFy)]\n        self.getAve = lambda M: M.aveCCV2F\n        self.expectedOrders = ORDERS / 2.0\n        self.orderTest()\n        self.expectedOrders = ORDERS\n\n\nclass TestAverating3DSimple(unittest.TestCase):\n    def setUp(self):\n        hx = gen.random(10)\n        hy = gen.random(10)\n        hz = gen.random(10)\n        self.mesh = discretize.TensorMesh([hx, hy, hz])\n\n    def test_constantEdges(self):\n        edge_vec = np.ones(self.mesh.nE)\n        assert all(np.absolute(self.mesh.aveE2CC * edge_vec - 1.0) < TOL)\n        assert all(np.absolute(self.mesh.aveE2CCV * edge_vec - 1.0) < TOL)\n\n    def test_constantFaces(self):\n        face_vec = np.ones(self.mesh.nF)\n        assert all(np.absolute(self.mesh.aveF2CC * face_vec - 1.0) < TOL)\n        assert all(np.absolute(self.mesh.aveF2CCV * face_vec - 1.0) < TOL)\n\n\nclass TestAveraging3D(discretize.tests.OrderTest):\n    name = \"Averaging 3D\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n    meshSizes = [16, 32, 64]\n\n    def getError(self):\n        num = self.getAve(self.M) * self.getHere(self.M)\n        err = np.linalg.norm((self.getThere(self.M) - num), np.inf)\n        return err\n\n    def test_orderN2CC(self):\n        self.name = \"Averaging 3D: N2CC\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridN)\n        self.getThere = lambda M: call3(fun, M.gridCC)\n        self.getAve = lambda M: M.aveN2CC\n        self.orderTest()\n\n    def test_orderN2F(self):\n        self.name = \"Averaging 3D: N2F\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridN)\n        self.getThere = lambda M: np.r_[\n            call3(fun, M.gridFx), call3(fun, M.gridFy), call3(fun, M.gridFz)\n        ]\n        self.getAve = lambda M: M.aveN2F\n        self.orderTest()\n\n    def test_orderN2E(self):\n        self.name = \"Averaging 3D: N2E\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridN)\n        self.getThere = lambda M: np.r_[\n            call3(fun, M.gridEx), call3(fun, M.gridEy), call3(fun, M.gridEz)\n        ]\n        self.getAve = lambda M: M.aveN2E\n        self.orderTest()\n\n    def test_orderF2CC(self):\n        self.name = \"Averaging 3D: F2CC\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(fun, M.gridFx), call3(fun, M.gridFy), call3(fun, M.gridFz)\n        ]\n        self.getThere = lambda M: call3(fun, M.gridCC)\n        self.getAve = lambda M: M.aveF2CC\n        self.orderTest()\n\n    def test_orderF2CCV(self):\n        self.name = \"Averaging 3D: F2CCV\"\n        funX = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        funY = lambda x, y, z: (np.cos(x) + np.sin(y) * np.exp(z))\n        funZ = lambda x, y, z: (np.cos(x) * np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(funX, M.gridFx), call3(funY, M.gridFy), call3(funZ, M.gridFz)\n        ]\n        self.getThere = lambda M: np.r_[\n            call3(funX, M.gridCC), call3(funY, M.gridCC), call3(funZ, M.gridCC)\n        ]\n        self.getAve = lambda M: M.aveF2CCV\n        self.orderTest()\n\n    def test_orderE2CC(self):\n        self.name = \"Averaging 3D: E2CC\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(fun, M.gridEx), call3(fun, M.gridEy), call3(fun, M.gridEz)\n        ]\n        self.getThere = lambda M: call3(fun, M.gridCC)\n        self.getAve = lambda M: M.aveE2CC\n        self.orderTest()\n\n    def test_orderE2CCV(self):\n        self.name = \"Averaging 3D: E2CCV\"\n        funX = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        funY = lambda x, y, z: (np.cos(x) + np.sin(y) * np.exp(z))\n        funZ = lambda x, y, z: (np.cos(x) * np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(funX, M.gridEx), call3(funY, M.gridEy), call3(funZ, M.gridEz)\n        ]\n        self.getThere = lambda M: np.r_[\n            call3(funX, M.gridCC), call3(funY, M.gridCC), call3(funZ, M.gridCC)\n        ]\n        self.getAve = lambda M: M.aveE2CCV\n        self.expectedOrders = ORDERS\n        self.orderTest()\n\n    def test_orderCC2F(self):\n        self.name = \"Averaging 3D: CC2F\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridCC)\n        self.getThere = lambda M: np.r_[\n            call3(fun, M.gridFx), call3(fun, M.gridFy), call3(fun, M.gridFz)\n        ]\n        self.getAve = lambda M: M.aveCC2F\n        self.expectedOrders = ORDERS / 2.0\n        self.orderTest()\n        self.expectedOrders = ORDERS\n\n    def test_orderCC2E(self):\n        self.name = \"Averaging 3D: CC2E\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridCC)\n        self.getThere = lambda M: call3(fun, M.edges)\n        self.getAve = lambda M: M.average_cell_to_edge\n        self.expectedOrders = ORDERS / 2.0\n        self.orderTest()\n        self.expectedOrders = ORDERS\n\n    def test_orderCCV2F(self):\n        self.name = \"Averaging 3D: CC2FV\"\n        funX = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        funY = lambda x, y, z: (np.cos(x) + np.sin(y) * np.exp(z))\n        funZ = lambda x, y, z: (np.cos(x) * np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(funX, M.gridCC), call3(funY, M.gridCC), call3(funZ, M.gridCC)\n        ]\n        self.getThere = lambda M: np.r_[\n            call3(funX, M.gridFx), call3(funY, M.gridFy), call3(funZ, M.gridFz)\n        ]\n        self.getAve = lambda M: M.aveCCV2F\n        self.expectedOrders = ORDERS / 2.0\n        self.orderTest()\n        self.expectedOrders = ORDERS\n\n\nclass MimeticProperties(unittest.TestCase):\n    meshTypes = MESHTYPES\n    meshDimension = 3\n    meshSize = 64\n    tol = 1e-11  # there is still some error due to rounding\n\n    def test_DivCurl(self):\n        for meshType in self.meshTypes:\n            mesh, _ = discretize.tests.setup_mesh(\n                meshType, self.meshSize, self.meshDimension\n            )\n            v = gen.random(mesh.nE)\n            divcurlv = mesh.face_divergence * (mesh.edge_curl * v)\n            rel_err = np.linalg.norm(divcurlv) / np.linalg.norm(v)\n            passed = rel_err < self.tol\n            print(\n                \"Testing Div * Curl on {} : |Div Curl v| / |v| = {} \"\n                \"... {}\".format(meshType, rel_err, \"FAIL\" if not passed else \"ok\")\n            )\n\n    def test_CurlGrad(self):\n        for meshType in self.meshTypes:\n            mesh, _ = discretize.tests.setup_mesh(\n                meshType, self.meshSize, self.meshDimension\n            )\n            v = gen.random(mesh.nN)\n            curlgradv = mesh.edge_curl * (mesh.nodal_gradient * v)\n            rel_err = np.linalg.norm(curlgradv) / np.linalg.norm(v)\n            passed = rel_err < self.tol\n            print(\n                \"Testing Curl * Grad on {} : |Curl Grad v| / |v|= {} \"\n                \"... {}\".format(meshType, rel_err, \"FAIL\" if not passed else \"ok\")\n            )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_properties.py",
    "content": "import unittest\nimport os\nimport numpy as np\nimport discretize\n\n\ndef compare_meshes(test, mesh0, mesh1):\n    # check some basic properties\n    test.assertEqual(\n        mesh0.nC,\n        mesh1.nC,\n        msg=(\"Number of cells not the same, {} != {}\".format(mesh0.nC, mesh1.nC)),\n    )\n\n    test.assertTrue(\n        (mesh0.x0 == mesh1.x0).all(),\n        msg=(\"x0 different. {} != {}\".format(mesh0.x0, mesh1.x0)),\n    )\n\n    test.assertEqual(mesh0.nE, mesh1.nE)\n    test.assertEqual(mesh0.nF, mesh1.nF)\n    test.assertEqual(mesh0.nN, mesh1.nN)\n\n    if hasattr(mesh0, \"vnC\"):\n        test.assertEqual(mesh0.vnC, mesh1.vnC)\n        test.assertEqual(mesh0.vnE, mesh1.vnE)\n        test.assertEqual(mesh0.vnF, mesh1.vnF)\n        test.assertEqual(mesh0.vnN, mesh1.vnN)\n\n    test.assertTrue((mesh0.face_normals == mesh1.face_normals).all())\n    test.assertTrue((mesh0.edge_tangents == mesh1.edge_tangents).all())\n\n    if hasattr(mesh0, \"h\"):\n        for i in range(len(mesh0.h)):\n            test.assertTrue(\n                (mesh0.h[i] == mesh1.h[i]).all(), (\"mesh h[{}] different\".format(i))\n            )\n\n    # check edges, faces, volumes\n    test.assertTrue((mesh0.edge_lengths == mesh1.edge_lengths).all())\n    test.assertTrue((mesh0.face_areas == mesh1.face_areas).all())\n    test.assertTrue((mesh0.cell_volumes == mesh1.cell_volumes).all())\n\n    # Tree mesh specific\n    # if hasattr(mesh0, '_cells'):\n    #    test.assertTrue(mesh0._cells == mesh1._cells)\n\n    # curvi-specific\n    if hasattr(mesh0, \"nodes\"):\n        for i in range(len(mesh0.nodes)):\n            test.assertTrue((mesh0.nodes[i] == mesh1.nodes[i]).all())\n\n\nclass TensorTest(unittest.TestCase):\n    n = [4, 5, 9]\n    x0 = [-0.5, -0.25, 0]\n\n    def setUp(self):\n        self.mesh = discretize.TensorMesh(self.n, x0=self.x0)\n\n    def test_save_load(self):\n        print(\"\\nTesting save / load of Tensor Mesh ...\")\n        mesh0 = self.mesh\n        f = mesh0.save()\n        mesh1 = discretize.load_mesh(f)\n        compare_meshes(self, mesh0, mesh1)\n        os.remove(f)\n\n    def test_copy(self):\n        print(\"\\nTesting copy of Tensor Mesh ...\")\n        mesh0 = self.mesh\n        mesh1 = mesh0.copy()\n        compare_meshes(self, mesh0, mesh1)\n\n    def test_base_updates(self):\n        with self.assertRaises(AttributeError):\n            self.mesh.shape_cells = None\n\n        # check that if h has been set, we can't mess up n\n        with self.assertRaises(AttributeError):\n            self.mesh.shape_cells = [6, 5, 9]\n\n        # can't change dimensionality of a mesh\n        with self.assertRaises(AttributeError):\n            self.mesh.shape_cells = [4, 5]\n\n\nclass CylTest(unittest.TestCase):\n    n = [4, 1, 9]\n\n    def setUp(self):\n        self.mesh = discretize.CylindricalMesh(self.n, x0=\"00C\")\n\n    def test_save_load(self):\n        print(\"\\nTesting save / load of Cyl Mesh ...\")\n        mesh0 = self.mesh\n        f = mesh0.save()\n        mesh1 = discretize.load_mesh(f)\n        compare_meshes(self, mesh0, mesh1)\n        os.remove(f)\n\n    def test_copy(self):\n        print(\"\\nTesting copy of Cyl Mesh ...\")\n        mesh0 = self.mesh\n        mesh1 = mesh0.copy()\n        compare_meshes(self, mesh0, mesh1)\n\n\n\"\"\"\nclass TreeTest(unittest.TestCase):\n\n    def setUp(self):\n        M = discretize.TreeMesh([8, 8])\n\n        def refine(cell):\n            xyz = cell.center\n            dist = ((xyz - [0.25, 0.25])**2).sum()**0.5\n            if dist < 0.25:\n                return 3\n            return 2\n\n        M.refine(refine)\n        M.number()\n\n        self.mesh = M\n\n    def test_save_load(self):\n        print('\\nTesting save / load of Tree Mesh ...')\n        mesh0 = self.mesh\n        f = mesh0.save()\n        mesh1 = discretize.load_mesh(f)\n        compare_meshes(self, mesh0, mesh1)\n        os.remove(f)\n\n    def test_copy(self):\n        print('\\nTesting copy of Tree Mesh ...')\n        mesh0 = self.mesh\n        mesh1 = mesh0.copy()\n        compare_meshes(self, mesh0, mesh1)\n\n    def test_base_updates(self):\n        with self.assertRaises(Exception):\n            self.mesh.shape_cells = None\n\n        # check that if h has been set, we can't mess up n\n        with self.assertRaises(Exception):\n            self.mesh.shape_cells = [6, 5, 9]\n\"\"\"\n\n\nclass CurviTest(unittest.TestCase):\n    def setUp(self):\n        a = np.array([1, 1, 1])\n        b = np.array([1, 2])\n        c = np.array([1, 4])\n\n        def gridIt(h):\n            return [np.cumsum(np.r_[0, x]) for x in h]\n\n        X, Y, Z = discretize.utils.ndgrid(gridIt([a, b, c]), vector=False)\n        self.mesh = discretize.CurvilinearMesh([X, Y, Z])\n\n    def test_save_load(self):\n        print(\"\\nTesting save / load of Curvi Mesh ...\")\n        mesh0 = self.mesh\n        f = mesh0.save()\n        mesh1 = discretize.load_mesh(f)\n        compare_meshes(self, mesh0, mesh1)\n        os.remove(f)\n\n    def test_copy(self):\n        print(\"\\nTesting copy of Curvi Mesh ...\")\n        mesh0 = self.mesh\n        mesh1 = mesh0.copy()\n        compare_meshes(self, mesh0, mesh1)\n\n    def test_base_updates(self):\n        with self.assertRaises(AttributeError):\n            self.mesh.shape_cells = None\n\n        # check that if h has been set, we can't mess up n\n        with self.assertRaises(AttributeError):\n            self.mesh.shape_cells = [6, 5, 9]\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_slicer.py",
    "content": "import numpy as np\nimport pytest\n\ntry:\n    from matplotlib.colors import Normalize\nexcept ImportError:\n    pytest.skip(\"Skipping Slicer tests due to no matplotlib\", allow_module_level=True)\nimport discretize\nfrom discretize.mixins.mpl_mod import Slicer\n\n\n@pytest.fixture()\ndef mesh():\n    return discretize.TensorMesh([9, 10, 11])\n\n\ndef test_slicer_errors(mesh):\n    model = np.ones(mesh.shape_cells)\n    with pytest.raises(\n        ValueError,\n        match=\"(Passing a Normalize instance simultaneously with clim is not supported).*\",\n    ):\n        Slicer(mesh, model, clim=[0, 1], pcolor_opts={\"norm\": Normalize()})\n\n\ndef test_slicer_default_clim(mesh):\n    model = np.ones(mesh.shape_cells)\n    model[0, 0, 0] = 0.5\n    slc = Slicer(mesh, model)\n    norm = slc.pc_props[\"norm\"]\n    assert (norm.vmin, norm.vmax) == (0.5, 1.0)\n\n\ndef test_slicer_set_clim(mesh):\n    model = np.ones(mesh.shape_cells)\n    slc = Slicer(mesh, model, clim=(0.5, 1.5))\n    norm = slc.pc_props[\"norm\"]\n    assert (norm.vmin, norm.vmax) == (0.5, 1.5)\n\n\ndef test_slicer_set_norm(mesh):\n    model = np.ones(mesh.shape_cells)\n    norm = Normalize(0.5, 1.5)\n    slc = Slicer(mesh, model, pcolor_opts={\"norm\": norm})\n    norm = slc.pc_props[\"norm\"]\n    assert (norm.vmin, norm.vmax) == (0.5, 1.5)\n\n\ndef test_slicer_ones_clim(mesh):\n    model = np.ones(mesh.shape_cells)\n    slc = Slicer(mesh, model)\n    norm = slc.pc_props[\"norm\"]\n    assert (norm.vmin, norm.vmax) == (0.9, 1.1)\n\n\ndef test_slicer_zeros_clim(mesh):\n    model = np.zeros(mesh.shape_cells)\n    slc = Slicer(mesh, model)\n    norm = slc.pc_props[\"norm\"]\n    assert (norm.vmin, norm.vmax) == (-0.1, 0.1)\n"
  },
  {
    "path": "tests/base/test_tensor.py",
    "content": "import pytest\nimport numpy as np\nimport numpy.testing as npt\nimport unittest\nimport discretize\nfrom scipy.sparse.linalg import spsolve\n\nTOL = 1e-10\n\ngen = np.random.default_rng(123)\n\n\nclass BasicTensorMeshTests(unittest.TestCase):\n    def setUp(self):\n        a = np.array([1, 1, 1])\n        b = np.array([1, 2])\n        c = np.array([1, 4])\n        self.mesh2 = discretize.TensorMesh([a, b], [3, 5])\n        self.mesh3 = discretize.TensorMesh([a, b, c])\n\n    def test_gridded_2D(self):\n        H = self.mesh2.h_gridded\n        test_hx = np.all(H[:, 0] == np.r_[1.0, 1.0, 1.0, 1.0, 1.0, 1.0])\n        test_hy = np.all(H[:, 1] == np.r_[1.0, 1.0, 1.0, 2.0, 2.0, 2.0])\n        self.assertTrue(test_hx and test_hy)\n\n    def test_gridded_3D(self):\n        H = self.mesh3.h_gridded\n        test_hx = np.all(\n            H[:, 0] == np.r_[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]\n        )\n        test_hy = np.all(\n            H[:, 1] == np.r_[1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]\n        )\n        test_hz = np.all(\n            H[:, 2] == np.r_[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0]\n        )\n        self.assertTrue(test_hx and test_hy and test_hz)\n\n    def test_vectorN_2D(self):\n        testNx = np.array([3, 4, 5, 6])\n        testNy = np.array([5, 6, 8])\n        xtest = np.all(self.mesh2.nodes_x == testNx)\n        ytest = np.all(self.mesh2.nodes_y == testNy)\n        self.assertTrue(xtest and ytest)\n\n    def test_vectorCC_2D(self):\n        testNx = np.array([3.5, 4.5, 5.5])\n        testNy = np.array([5.5, 7])\n\n        xtest = np.all(self.mesh2.cell_centers_x == testNx)\n        ytest = np.all(self.mesh2.cell_centers_y == testNy)\n        self.assertTrue(xtest and ytest)\n\n    def test_area_3D(self):\n        test_area = np.array(\n            [\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                4,\n                4,\n                4,\n                4,\n                8,\n                8,\n                8,\n                8,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n            ]\n        )\n        t1 = np.all(self.mesh3.face_areas == test_area)\n        self.assertTrue(t1)\n\n    def test_vol_3D(self):\n        test_vol = np.array([1, 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8])\n        t1 = np.all(self.mesh3.cell_volumes == test_vol)\n        self.assertTrue(t1)\n\n    def test_vol_2D(self):\n        test_vol = np.array([1, 1, 1, 2, 2, 2])\n        t1 = np.all(self.mesh2.cell_volumes == test_vol)\n        self.assertTrue(t1)\n\n    def test_edge_3D(self):\n        test_edge = np.array(\n            [\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                1,\n                2,\n                2,\n                2,\n                2,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                1,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n                4,\n            ]\n        )\n        t1 = np.all(self.mesh3.edge_lengths == test_edge)\n        self.assertTrue(t1)\n\n    def test_edge_2D(self):\n        test_edge = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])\n        t1 = np.all(self.mesh2.edge_lengths == test_edge)\n        self.assertTrue(t1)\n\n    def test_oneCell(self):\n        hx = np.array([1e-5])\n        M = discretize.TensorMesh([hx])\n        self.assertTrue(M.nC == 1)\n\n    def test_printing(self):\n        print(discretize.TensorMesh([10]))\n        print(discretize.TensorMesh([10, 10]))\n        print(discretize.TensorMesh([10, 10, 10]))\n\n    def test_centering(self):\n        M1d = discretize.TensorMesh([10], x0=\"C\")\n        M2d = discretize.TensorMesh([10, 10], x0=\"CC\")\n        M3d = discretize.TensorMesh([10, 10, 10], x0=\"CCC\")\n        self.assertLess(np.abs(M1d.x0 + 0.5).sum(), TOL)\n        self.assertLess(np.abs(M2d.x0 + 0.5).sum(), TOL)\n        self.assertLess(np.abs(M3d.x0 + 0.5).sum(), TOL)\n\n    def test_negative(self):\n        M1d = discretize.TensorMesh([10], x0=\"N\")\n        self.assertRaises(Exception, discretize.TensorMesh, [10], \"F\")\n        M2d = discretize.TensorMesh([10, 10], x0=\"NN\")\n        M3d = discretize.TensorMesh([10, 10, 10], x0=\"NNN\")\n        self.assertLess(np.abs(M1d.x0 + 1.0).sum(), TOL)\n        self.assertLess(np.abs(M2d.x0 + 1.0).sum(), TOL)\n        self.assertLess(np.abs(M3d.x0 + 1.0).sum(), TOL)\n\n    def test_cent_neg(self):\n        M3d = discretize.TensorMesh([10, 10, 10], x0=\"C0N\")\n        self.assertLess(np.abs(M3d.x0 + np.r_[0.5, 0, 1.0]).sum(), TOL)\n\n    def test_tensor(self):\n        M = discretize.TensorMesh([[(10.0, 2)]])\n        self.assertLess(np.abs(M.h[0] - np.r_[10.0, 10.0]).sum(), TOL)\n\n    def test_serialization(self):\n        mesh = discretize.TensorMesh.deserialize(self.mesh2.serialize())\n        self.assertTrue(np.all(self.mesh2.x0 == mesh.x0))\n        self.assertTrue(np.all(self.mesh2.shape_cells == mesh.shape_cells))\n        self.assertTrue(np.all(self.mesh2.h[0] == mesh.h[0]))\n        self.assertTrue(np.all(self.mesh2.h[1] == mesh.h[1]))\n        self.assertTrue(np.all(self.mesh2.gridCC == mesh.gridCC))\n\n\nclass TestTensorMeshProperties:\n    \"\"\"\n    Test some of the properties in TensorMesh\n    \"\"\"\n\n    @pytest.fixture(params=[1, 2, 3], ids=[\"dims-1\", \"dims-2\", \"dims-3\"])\n    def mesh(self, request):\n        \"\"\"Sample TensorMesh.\"\"\"\n        if request.param == 1:\n            h = [10]\n            origin = (-35.5,)\n        elif request.param == 2:\n            h = [10, 15]\n            origin = (-35.5, 105.3)\n        else:\n            h = [10, 15, 20]\n            origin = (-35.5, 105.3, -27.3)\n        return discretize.TensorMesh(h, origin=origin)\n\n    def test_cell_nodes(self, mesh):\n        \"\"\"Test TensorMesh.cell_nodes.\"\"\"\n        expected_cell_nodes = np.array([cell.nodes for cell in mesh])\n        np.testing.assert_equal(mesh.cell_nodes, expected_cell_nodes)\n\n    def test_cell_bounds(self, mesh):\n        \"\"\"Test TensorMesh.cell_bounds.\"\"\"\n        expected_cell_bounds = np.array([cell.bounds for cell in mesh])\n        np.testing.assert_equal(mesh.cell_bounds, expected_cell_bounds)\n\n\nclass TestPoissonEqn(discretize.tests.OrderTest):\n    name = \"Poisson Equation\"\n    meshSizes = [10, 16, 20]\n\n    def getError(self):\n        # Create some functions to integrate\n        fun = (\n            lambda x: np.sin(2 * np.pi * x[:, 0])\n            * np.sin(2 * np.pi * x[:, 1])\n            * np.sin(2 * np.pi * x[:, 2])\n        )\n        sol = lambda x: -3.0 * ((2 * np.pi) ** 2) * fun(x)\n\n        self.M.set_cell_gradient_BC(\"dirichlet\")\n\n        D = self.M.face_divergence\n        G = self.M.cell_gradient\n        if self.forward:\n            sA = sol(self.M.gridCC)\n            sN = D * G * fun(self.M.gridCC)\n            err = np.linalg.norm((sA - sN), np.inf)\n        else:\n            fA = fun(self.M.gridCC)\n            fN = spsolve(D * G, sol(self.M.gridCC))\n            err = np.linalg.norm((fA - fN), np.inf)\n        return err\n\n    def test_orderForward(self):\n        self.name = \"Poisson Equation - Forward\"\n        self.forward = True\n        self.orderTest()\n\n    def test_orderBackward(self):\n        self.name = \"Poisson Equation - Backward\"\n        self.forward = False\n        self.orderTest()\n\n\n@pytest.fixture(params=[1, 2, 3], ids=[\"dims-1\", \"dims-2\", \"dims-3\"])\ndef random_tensor_mesh(request):\n    dim = request.param\n    rng = np.random.default_rng(440122)\n    shape = rng.integers(5, 10, dim)\n    cell_widths = [rng.uniform(3.0, 872634.321, n) for n in shape]\n    origin = rng.uniform(-101.031, 33.2, dim)\n\n    return discretize.TensorMesh(cell_widths, origin)\n\n\ndef test_tensor_point2index_inside_points(random_tensor_mesh):\n    mesh = random_tensor_mesh\n    dim = mesh.dim\n    m_origin = mesh.origin\n    m_extent = np.atleast_1d(np.max(mesh.nodes, axis=0))\n\n    nd = 15\n    points = np.stack(np.meshgrid(*np.linspace(m_origin, m_extent, nd).T), axis=-1)\n    points = points.reshape((-1, dim))\n\n    npt.assert_array_equal(mesh.is_inside(points), True)\n\n    cell_inds = mesh.point2index(points)\n    for icell, p in zip(cell_inds, points):\n        cell = mesh[icell]\n        c_origin, c_extent = cell.bounds.reshape((dim, 2)).T\n        dim_test = (p >= c_origin) & (p <= c_extent)\n        npt.assert_equal(dim_test, True)\n\n\ndef test_tensor_point2index_outside_points(random_tensor_mesh):\n    mesh = random_tensor_mesh\n    dim = mesh.dim\n    m_origin = mesh.origin\n    m_extent = np.atleast_1d(np.max(mesh.nodes, axis=0))\n    m_width = m_extent - m_origin\n\n    nd = 15\n    points = np.stack(\n        np.meshgrid(*np.linspace(m_origin - m_width * 2, m_extent + m_width * 2, nd).T),\n        axis=-1,\n    )\n    points = points.reshape((-1, dim))\n    outside_points = points[~mesh.is_inside(points)]\n\n    npt.assert_array_equal(mesh.is_inside(outside_points), False)\n\n    # manually check each point that is outside\n    cell_inds = mesh.point2index(outside_points)\n    for icell, p in zip(cell_inds, outside_points):\n        cell = mesh[icell]\n        c_origin, c_extent = cell.bounds.reshape((dim, 2)).T\n        dim_test = np.zeros(dim, bool)\n        for i in range(dim):\n            p_d = p[i]\n            if p_d < m_origin[i]:\n                dim_test[i] = p_d < c_origin[i]\n            elif p_d > m_extent[i]:\n                dim_test[i] = p_d > c_extent[i]\n            else:\n                dim_test[i] = p_d >= c_origin[i] and p_d <= c_extent[i]\n        npt.assert_equal(dim_test, True)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_tensor_cell.py",
    "content": "\"\"\"Test TensorCell.\"\"\"\n\nimport pytest\nimport numpy as np\n\nfrom discretize import TensorCell, TensorMesh\nfrom discretize.tensor_mesh import _slice_to_index\n\n\n@pytest.mark.parametrize(\n    \"slice_indices, expected_result\",\n    [\n        (slice(None, None, None), range(8)),\n        (slice(0, None, None), range(8)),\n        (slice(1, None, None), range(1, 8)),\n        (slice(None, 4, None), range(4)),\n        (slice(None, 8, None), range(8)),\n        (slice(None, None, 1), range(8)),\n        (slice(None, None, 2), range(0, 8, 2)),\n        (slice(None, None, -1), reversed(range(0, 8, 1))),\n        (slice(1, 7, -2), reversed(range(1, 7, 2))),\n        (slice(1, -1, None), range(1, 7, 1)),\n        (slice(1, -2, 2), range(1, 6, 2)),\n    ],\n)\ndef test_slice_to_index(slice_indices, expected_result):\n    \"\"\"Test private _slice_to_index function.\"\"\"\n    end = 8\n    indices = tuple(i for i in _slice_to_index(slice_indices, end))\n    expected_result = tuple(i for i in expected_result)\n    assert indices == expected_result\n\n\nclass TestTensorCell:\n    \"\"\"Test attributes of TensorCell.\"\"\"\n\n    @pytest.fixture(params=(\"1D\", \"2D\", \"3D\"))\n    def cell(self, request):\n        \"\"\"Sample TensorCell.\"\"\"\n        dim = request.param\n        if dim == \"1D\":\n            h = np.array([4.0])\n            origin = np.array([-2.0])\n            index_unraveled = (1,)\n            mesh_shape = (8,)\n        elif dim == \"2D\":\n            h = np.array([4.0, 2.0])\n            origin = np.array([-2.0, 5.0])\n            index_unraveled = (1, 2)\n            mesh_shape = (8, 3)\n        elif dim == \"3D\":\n            h = np.array([4.0, 2.0, 10.0])\n            origin = np.array([-2.0, 5.0, -12.0])\n            index_unraveled = (1, 2, 3)\n            mesh_shape = (8, 3, 10)\n        return TensorCell(h, origin, index_unraveled, mesh_shape)\n\n    def test_center(self, cell):\n        \"\"\"Test center property.\"\"\"\n        if cell.dim == 1:\n            true_center = (0.0,)\n        elif cell.dim == 2:\n            true_center = (0.0, 6.0)\n        elif cell.dim == 3:\n            true_center = (0.0, 6.0, -7.0)\n        assert all(cell.center == true_center)\n\n    def test_index(self, cell):\n        \"\"\"Test index property.\"\"\"\n        if cell.dim == 1:\n            true_index = 1\n        elif cell.dim == 2:\n            true_index = 17\n        elif cell.dim == 3:\n            true_index = 89\n        assert cell.index == true_index\n\n    def test_index_unraveled(self, cell):\n        \"\"\"Test index_unraveled property.\"\"\"\n        if cell.dim == 1:\n            true_index_unraveled = (1,)\n        elif cell.dim == 2:\n            true_index_unraveled = (1, 2)\n        elif cell.dim == 3:\n            true_index_unraveled = (1, 2, 3)\n        assert cell.index_unraveled == true_index_unraveled\n\n    def test_bounds(self, cell):\n        \"\"\"Test bounds property.\"\"\"\n        if cell.dim == 1:\n            true_bounds = (-2.0, 2.0)\n        elif cell.dim == 2:\n            true_bounds = (-2.0, 2.0, 5.0, 7.0)\n        elif cell.dim == 3:\n            true_bounds = (-2.0, 2.0, 5.0, 7.0, -12.0, -2.0)\n        assert all(cell.bounds == true_bounds)\n\n    @pytest.mark.parametrize(\"change_h\", (True, False))\n    @pytest.mark.parametrize(\"change_origin\", (True, False))\n    @pytest.mark.parametrize(\"change_index\", (True, False))\n    @pytest.mark.parametrize(\"change_mesh_shape\", (True, False))\n    def test_eq(self, cell, change_h, change_origin, change_index, change_mesh_shape):\n        h, origin = cell.h, cell.origin\n        index_unraveled, mesh_shape = cell.index_unraveled, cell.mesh_shape\n        if change_h:\n            h = np.array([h_i + 0.1 for h_i in h])\n        if change_origin:\n            origin = np.array([origin_i + 0.1 for origin_i in origin])\n        if change_index:\n            index_unraveled = tuple(i - 1 for i in index_unraveled)\n        if change_mesh_shape:\n            mesh_shape = tuple(i + 1 for i in mesh_shape)\n        other_cell = TensorCell(h, origin, index_unraveled, mesh_shape)\n        if any((change_origin, change_h, change_index, change_mesh_shape)):\n            assert cell != other_cell\n        else:\n            assert cell == other_cell\n\n    def test_eq_invalid_type(self, cell):\n        \"\"\"Test if error is raised when comparing other class to a TensorCell.\"\"\"\n\n        class Dummy:\n            def __init__(self):\n                pass\n\n        other_object = Dummy()\n        msg = \"Cannot compare an object of type 'Dummy'\"\n        with pytest.raises(TypeError, match=msg):\n            cell == other_object  # noqa: B015\n\n\nclass TestTensorMeshCells:\n    \"\"\"Test TensorMesh iterator and its resulting cells.\"\"\"\n\n    @pytest.fixture(params=(\"1D\", \"2D\", \"3D\"))\n    def mesh(self, request):\n        \"\"\"Sample TensorMesh.\"\"\"\n        dim = request.param\n        if dim == \"1D\":\n            h = [5]\n            origin = [-2.0]\n        elif dim == \"2D\":\n            h = [5, 4]\n            origin = [-2.0, 5.0]\n        elif dim == \"3D\":\n            h = [5, 4, 10]\n            origin = [-2.0, 5.0, -12.0]\n        return TensorMesh(h, origin)\n\n    def test_cell_centers(self, mesh):\n        \"\"\"Test if cells in iterator are properly ordered by comparing cell centers.\"\"\"\n        cell_centers = np.array([cell.center for cell in mesh])\n        if mesh.dim == 1:\n            # Ravel cell_centers if mesh is 1D\n            cell_centers = cell_centers.ravel()\n        np.testing.assert_allclose(mesh.cell_centers, cell_centers)\n\n    def test_cell_bounds(self, mesh):\n        \"\"\"Test if cells in iterator are properly ordered by comparing cell bounds.\"\"\"\n        if mesh.dim == 1:\n            x1 = mesh.cell_centers - mesh.h_gridded.ravel() / 2\n            x2 = mesh.cell_centers + mesh.h_gridded.ravel() / 2\n            true_bounds = np.vstack([x1, x2]).T\n        elif mesh.dim == 2:\n            x1 = mesh.cell_centers[:, 0] - mesh.h_gridded[:, 0] / 2\n            x2 = mesh.cell_centers[:, 0] + mesh.h_gridded[:, 0] / 2\n            y1 = mesh.cell_centers[:, 1] - mesh.h_gridded[:, 1] / 2\n            y2 = mesh.cell_centers[:, 1] + mesh.h_gridded[:, 1] / 2\n            true_bounds = np.vstack([x1, x2, y1, y2]).T\n        elif mesh.dim == 3:\n            x1 = mesh.cell_centers[:, 0] - mesh.h_gridded[:, 0] / 2\n            x2 = mesh.cell_centers[:, 0] + mesh.h_gridded[:, 0] / 2\n            y1 = mesh.cell_centers[:, 1] - mesh.h_gridded[:, 1] / 2\n            y2 = mesh.cell_centers[:, 1] + mesh.h_gridded[:, 1] / 2\n            z1 = mesh.cell_centers[:, 2] - mesh.h_gridded[:, 2] / 2\n            z2 = mesh.cell_centers[:, 2] + mesh.h_gridded[:, 2] / 2\n            true_bounds = np.vstack([x1, x2, y1, y2, z1, z2]).T\n        cell_bounds = np.array([cell.bounds for cell in mesh])\n        np.testing.assert_allclose(true_bounds, cell_bounds)\n\n    def test_cell_int_indices(self, mesh):\n        \"\"\"\n        Test if integer indices return the expected cell.\n\n        Test if an integer index is correctly converted to a tuple of indices,\n        i.e. unravelling using FORTRAN order.\n        \"\"\"\n        if mesh.dim == 1:\n            size = len(mesh)\n            indices_tuples = [(i,) for i in range(size)]\n            for i in range(len(mesh)):\n                cell, expected_cell = mesh[i], mesh[indices_tuples[i]]\n                assert cell == expected_cell\n        elif mesh.dim == 2:\n            shape = mesh.shape_cells\n            indices_tuples = [(i, j) for j in range(shape[1]) for i in range(shape[0])]\n            for i in range(len(mesh)):\n                cell, expected_cell = mesh[i], mesh[indices_tuples[i]]\n                assert cell == expected_cell\n        elif mesh.dim == 3:\n            shape = mesh.shape_cells\n            indices_tuples = [\n                (i, j, k)\n                for k in range(shape[2])\n                for j in range(shape[1])\n                for i in range(shape[0])\n            ]\n            cells = [mesh[i] for i in range(len(mesh))]\n            expected_cells = [mesh[indices] for indices in indices_tuples]\n            assert cells == expected_cells\n\n    def test_cell_negative_int_indices(self, mesh):\n        \"\"\"Test if negative integer indices return the expected cell.\"\"\"\n        if mesh.dim == 1:\n            assert mesh[-1] == mesh[5 - 1]\n            assert mesh[-2] == mesh[5 - 2]\n        elif mesh.dim == 2:\n            assert mesh[-1] == mesh[5 * 4 - 1]\n            assert mesh[-2] == mesh[5 * 4 - 2]\n            assert mesh[-1, 0] == mesh[5 - 1, 0]\n            assert mesh[0, -1] == mesh[0, 4 - 1]\n            assert mesh[-2, -2] == mesh[5 - 2, 4 - 2]\n        elif mesh.dim == 3:\n            assert mesh[-1] == mesh[5 * 4 * 10 - 1]\n            assert mesh[-2] == mesh[5 * 4 * 10 - 2]\n            assert mesh[-1, 0, 0] == mesh[5 - 1, 0, 0]\n            assert mesh[0, -1, 0] == mesh[0, 4 - 1, 0]\n            assert mesh[0, 0, -1] == mesh[0, 0, 10 - 1]\n            assert mesh[-2, -2, -2] == mesh[5 - 2, 4 - 2, 10 - 2]\n\n    @pytest.mark.parametrize(\"start\", [None, 0, 1, -2])\n    @pytest.mark.parametrize(\"stop\", [None, 4, -1, \"end\"])\n    @pytest.mark.parametrize(\"step\", [None, 1, 2, -1])\n    def test_cells_single_slice(self, mesh, start, stop, step):\n        \"\"\"Test if a single slice return the expected cells.\"\"\"\n        if stop == \"end\":\n            stop = len(mesh)\n        cells = mesh[start:stop:step]\n        indices = _slice_to_index(slice(start, stop, step), len(mesh))\n        expected_cells = [mesh[i] for i in indices]\n        assert cells == expected_cells\n\n    @pytest.mark.parametrize(\"step\", (None, 1, 2, -1))\n    def test_cells_slices(self, mesh, step):\n        \"\"\"Test if passing slices return the expected cells.\"\"\"\n        start, stop = 1, 3\n        if mesh.dim == 1:\n            cells = mesh[start:stop:step]\n            expected_cells = [\n                mesh[i] for i in _slice_to_index(slice(start, stop, step), len(mesh))\n            ]\n            assert cells == expected_cells\n        elif mesh.dim == 2:\n            index_x = slice(start, stop, step)\n            index_y = slice(start, stop, step)\n            cells = mesh[index_x, index_y]\n            expected_cells = self.generate_expected_cells(mesh, start, stop, step)\n            assert cells == expected_cells\n        elif mesh.dim == 3:\n            index_x = slice(start, stop, step)\n            index_y = slice(start, stop, step)\n            index_z = slice(start, stop, step)\n            cells = mesh[index_x, index_y, index_z]\n            expected_cells = self.generate_expected_cells(mesh, start, stop, step)\n            assert cells == expected_cells\n\n    @pytest.mark.parametrize(\"step\", (None, 1, 2, -1))\n    def test_cells_slices_negative_bounds(self, mesh, step):\n        \"\"\"Test if passing slices with negative bounds return the expected cells.\"\"\"\n        if mesh.dim == 1:\n            n = mesh.n_cells\n            assert mesh[1:-1:step] == mesh[1 : n - 1 : step]\n            assert mesh[-3:-1:step] == mesh[n - 3 : n - 1 : step]\n        elif mesh.dim == 2:\n            nx, ny = mesh.shape_cells\n            assert (\n                mesh[1:-1:step, 1:-1:step] == mesh[1 : nx - 1 : step, 1 : ny - 1 : step]\n            )\n            assert (\n                mesh[-3:-1:step, -3:-1:step]\n                == mesh[nx - 3 : nx - 1 : step, ny - 3 : ny - 1 : step]\n            )\n        elif mesh.dim == 3:\n            nx, ny, nz = mesh.shape_cells\n            assert (\n                mesh[1:-1:step, 1:-1:step, 1:-1:step]\n                == mesh[1 : nx - 1 : step, 1 : ny - 1 : step, 1 : nz - 1 : step]\n            )\n            assert (\n                mesh[-3:-1:step, -3:-1:step, -3:-1:step]\n                == mesh[\n                    nx - 3 : nx - 1 : step,\n                    ny - 3 : ny - 1 : step,\n                    nz - 3 : nz - 1 : step,\n                ]\n            )\n\n    def generate_expected_cells(self, mesh, start, stop, step):\n        \"\"\"Generate expected cells after slicing the mesh.\"\"\"\n        if step is None:\n            step = 1\n        if mesh.dim == 2:\n            if step > 0:\n                expected_cells = [\n                    mesh[i, j]\n                    for j in range(start, stop, step)\n                    for i in range(start, stop, step)\n                ]\n            else:\n                expected_cells = [\n                    mesh[i, j]\n                    for j in reversed(range(start, stop, -step))\n                    for i in reversed(range(start, stop, -step))\n                ]\n        elif mesh.dim == 3:\n            if step > 0:\n                expected_cells = [\n                    mesh[i, j, k]\n                    for k in range(start, stop, step)\n                    for j in range(start, stop, step)\n                    for i in range(start, stop, step)\n                ]\n            else:\n                expected_cells = [\n                    mesh[i, j, k]\n                    for k in reversed(range(start, stop, -step))\n                    for j in reversed(range(start, stop, -step))\n                    for i in reversed(range(start, stop, -step))\n                ]\n        return expected_cells\n\n\nclass TestNeighbors:\n    \"\"\"Test the neighbors property.\"\"\"\n\n    @pytest.fixture\n    def sample_1D(self):\n        \"\"\"Cell attributes for building a 1D cell.\"\"\"\n        h = [3.1]\n        origin = [-2.3]\n        mesh_shape = [5]\n        return h, origin, mesh_shape\n\n    @pytest.fixture\n    def sample_2D(self):\n        \"\"\"Cell attributes for building a 2D cell.\"\"\"\n        h = [3.1, 5.6]\n        origin = [-2.3, 4.1]\n        mesh_shape = [5, 4]\n        return h, origin, mesh_shape\n\n    @pytest.fixture\n    def sample_3D(self):\n        \"\"\"Cell attributes for building a 3D cell.\"\"\"\n        h = [3.1, 5.6, 10.2]\n        origin = [-2.3, 4.1, -3.4]\n        mesh_shape = [5, 4, 10]\n        return h, origin, mesh_shape\n\n    @pytest.mark.parametrize(\"index\", (0, 3, 4))\n    def test_neighbors_1D(self, sample_1D, index):\n        \"\"\"Test the neighbors property on a 1D mesh.\"\"\"\n        h, origin, mesh_shape = sample_1D\n        cell = TensorCell(\n            h=h, origin=origin, index_unraveled=[index], mesh_shape=mesh_shape\n        )\n        expected_neighbors = []\n        if index == 0:\n            expected_neighbors = [[1]]\n        elif index == 3:\n            expected_neighbors = [[2], [4]]\n        elif index == 4:\n            expected_neighbors = [[3]]\n        assert expected_neighbors == cell.neighbors\n\n    @pytest.mark.parametrize(\"index_x\", (0, 3, 4))\n    @pytest.mark.parametrize(\"index_y\", (0, 1, 3))\n    def test_neighbors_2D(self, sample_2D, index_x, index_y):\n        \"\"\"Test the neighbors property on a 2D mesh.\"\"\"\n        h, origin, mesh_shape = sample_2D\n        cell = TensorCell(\n            h=h,\n            origin=origin,\n            index_unraveled=[index_x, index_y],\n            mesh_shape=mesh_shape,\n        )\n        cell_index = cell.index_unraveled\n        expected_neighbors = []\n        if index_x == 0:\n            expected_neighbors += [[1, cell_index[1]]]\n        elif index_x == 3:\n            expected_neighbors += [[i, cell_index[1]] for i in (2, 4)]\n        elif index_x == 4:\n            expected_neighbors += [[3, cell_index[1]]]\n        if index_y == 0:\n            expected_neighbors += [[cell_index[0], 1]]\n        elif index_y == 1:\n            expected_neighbors += [[cell_index[0], j] for j in (0, 2)]\n        elif index_y == 3:\n            expected_neighbors += [[cell_index[0], 2]]\n        expected_neighbors = [\n            np.ravel_multi_index(index, dims=mesh_shape, order=\"F\")\n            for index in expected_neighbors\n        ]\n        assert expected_neighbors == cell.neighbors\n\n    @pytest.mark.parametrize(\"index_x\", (0, 3, 4))\n    @pytest.mark.parametrize(\"index_y\", (0, 1, 3))\n    @pytest.mark.parametrize(\"index_z\", (0, 4, 9))\n    def test_neighbors_3D(self, sample_3D, index_x, index_y, index_z):\n        \"\"\"Test the neighbors property on a 3D mesh.\"\"\"\n        h, origin, mesh_shape = sample_3D\n        cell = TensorCell(\n            h=h,\n            origin=origin,\n            index_unraveled=[index_x, index_y, index_z],\n            mesh_shape=mesh_shape,\n        )\n        cell_index = cell.index_unraveled\n        expected_neighbors = []\n        if index_x == 0:\n            expected_neighbors += [[1, cell_index[1], cell_index[2]]]\n        elif index_x == 3:\n            expected_neighbors += [[i, cell_index[1], cell_index[2]] for i in (2, 4)]\n        elif index_x == 4:\n            expected_neighbors += [[3, cell_index[1], cell_index[2]]]\n        if index_y == 0:\n            expected_neighbors += [[cell_index[0], 1, cell_index[2]]]\n        elif index_y == 1:\n            expected_neighbors += [[cell_index[0], j, cell_index[2]] for j in (0, 2)]\n        elif index_y == 3:\n            expected_neighbors += [[cell_index[0], 2, cell_index[2]]]\n        if index_z == 0:\n            expected_neighbors += [[cell_index[0], cell_index[1], 1]]\n        elif index_z == 4:\n            expected_neighbors += [[cell_index[0], cell_index[1], k] for k in (3, 5)]\n        elif index_z == 9:\n            expected_neighbors += [[cell_index[0], cell_index[1], 8]]\n        expected_neighbors = [\n            np.ravel_multi_index(index, dims=mesh_shape, order=\"F\")\n            for index in expected_neighbors\n        ]\n        assert expected_neighbors == cell.neighbors\n\n\nclass TestNodes:\n    \"\"\"Test the nodes property.\"\"\"\n\n    @pytest.fixture\n    def cell_1D(self):\n        \"\"\"Sample 1D TensorCell.\"\"\"\n        return TensorCell(h=[3.4], origin=[-2.3], index_unraveled=[1], mesh_shape=[3])\n\n    @pytest.fixture\n    def cell_2D(self):\n        \"\"\"Sample 1D TensorCell.\"\"\"\n        cell = TensorCell(\n            h=[3.4, 4.3], origin=[-2.3, 0.3], index_unraveled=[1, 2], mesh_shape=[3, 4]\n        )\n        return cell\n\n    @pytest.fixture\n    def cell_3D(self):\n        \"\"\"Sample 1D TensorCell.\"\"\"\n        cell = TensorCell(\n            h=[3.4, 4.3, 5.6],\n            origin=[-2.3, 0.3, 3.1],\n            index_unraveled=[1, 2, 3],\n            mesh_shape=[3, 4, 5],\n        )\n        return cell\n\n    def test_nodes_indices_1D(self, cell_1D):\n        \"\"\"Test if nodes property return the expected indices.\"\"\"\n        assert cell_1D.nodes == [1, 2]\n\n    def test_nodes_indices_2D(self, cell_2D):\n        \"\"\"Test if nodes property return the expected indices.\"\"\"\n        assert cell_2D.nodes == [9, 10, 13, 14]\n\n    def test_nodes_indices_3D(self, cell_3D):\n        \"\"\"Test if nodes property return the expected indices.\"\"\"\n        assert cell_3D.nodes == [69, 70, 73, 74, 89, 90, 93, 94]\n\n\nclass TestEdges:\n    \"\"\"Test the edges property.\"\"\"\n\n    @pytest.fixture\n    def mesh_1D(self):\n        \"\"\"Sample 1D TensorMesh.\"\"\"\n        h = [5]\n        origin = [-2.0]\n        return TensorMesh(h, origin)\n\n    @pytest.fixture\n    def mesh_2D(self):\n        \"\"\"Sample 2D TensorMesh.\"\"\"\n        h = [5, 4]\n        origin = [-2.0, 5.0]\n        return TensorMesh(h, origin)\n\n    @pytest.fixture\n    def mesh_3D(self):\n        \"\"\"Sample 3D TensorMesh.\"\"\"\n        h = [5, 4, 10]\n        origin = [-2.0, 5.0, -12.0]\n        return TensorMesh(h, origin)\n\n    def test_edges_1D(self, mesh_1D):\n        \"\"\"Test the edges property on a 1D mesh.\"\"\"\n        index = (2,)\n        cell = mesh_1D[index]\n        xmin, xmax = cell.bounds\n        true_edges = [(xmin + xmax) / 2]\n        edges = [mesh_1D.edges[i] for i in cell.edges]\n        assert true_edges == edges\n\n    def test_edges_2D(self, mesh_2D):\n        \"\"\"Test the edges property on a 2D mesh.\"\"\"\n        index = (2, 3)\n        cell = mesh_2D[index]\n        xmin, xmax, ymin, ymax = cell.bounds\n        true_edges = [\n            np.array([(xmin + xmax) / 2, ymin]),\n            np.array([(xmin + xmax) / 2, ymax]),\n            np.array([xmin, (ymin + ymax) / 2]),\n            np.array([xmax, (ymin + ymax) / 2]),\n        ]\n        edges = [mesh_2D.edges[i] for i in cell.edges]\n        np.testing.assert_array_equal(true_edges, edges)\n\n    def test_edges_3D(self, mesh_3D):\n        \"\"\"Test the edges property on a 3D mesh.\"\"\"\n        index = (2, 3, 4)\n        cell = mesh_3D[index]\n        xmin, xmax, ymin, ymax, zmin, zmax = cell.bounds\n        true_edges = [\n            np.array([(xmin + xmax) / 2, ymin, zmin]),\n            np.array([(xmin + xmax) / 2, ymax, zmin]),\n            np.array([(xmin + xmax) / 2, ymin, zmax]),\n            np.array([(xmin + xmax) / 2, ymax, zmax]),\n            np.array([xmin, (ymin + ymax) / 2, zmin]),\n            np.array([xmax, (ymin + ymax) / 2, zmin]),\n            np.array([xmin, (ymin + ymax) / 2, zmax]),\n            np.array([xmax, (ymin + ymax) / 2, zmax]),\n            np.array([xmin, ymin, (zmin + zmax) / 2]),\n            np.array([xmax, ymin, (zmin + zmax) / 2]),\n            np.array([xmin, ymax, (zmin + zmax) / 2]),\n            np.array([xmax, ymax, (zmin + zmax) / 2]),\n        ]\n        edges = [mesh_3D.edges[i] for i in cell.edges]\n        np.testing.assert_array_equal(true_edges, edges)\n\n\nclass TestFaces:\n    \"\"\"Test the faces property.\"\"\"\n\n    @pytest.fixture\n    def mesh_1D(self):\n        \"\"\"Sample 1D TensorMesh.\"\"\"\n        h = [5]\n        origin = [-2.0]\n        return TensorMesh(h, origin)\n\n    @pytest.fixture\n    def mesh_2D(self):\n        \"\"\"Sample 2D TensorMesh.\"\"\"\n        h = [5, 4]\n        origin = [-2.0, 5.0]\n        return TensorMesh(h, origin)\n\n    @pytest.fixture\n    def mesh_3D(self):\n        \"\"\"Sample 3D TensorMesh.\"\"\"\n        h = [5, 4, 10]\n        origin = [-2.0, 5.0, -12.0]\n        return TensorMesh(h, origin)\n\n    def test_faces_1D(self, mesh_1D):\n        \"\"\"Test the faces property on a 1D mesh.\"\"\"\n        index = (2,)\n        cell = mesh_1D[index]\n        xmin, xmax = cell.bounds\n        true_faces = [xmin, xmax]\n        faces = [mesh_1D.faces[i] for i in cell.faces]\n        assert true_faces == faces\n\n    def test_faces_2D(self, mesh_2D):\n        \"\"\"Test the faces property on a 2D mesh.\"\"\"\n        index = (2, 3)\n        cell = mesh_2D[index]\n        xmin, xmax, ymin, ymax = cell.bounds\n        true_faces = [\n            np.array([xmin, (ymin + ymax) / 2]),\n            np.array([xmax, (ymin + ymax) / 2]),\n            np.array([(xmin + xmax) / 2, ymin]),\n            np.array([(xmin + xmax) / 2, ymax]),\n        ]\n        faces = [mesh_2D.faces[i] for i in cell.faces]\n        np.testing.assert_array_equal(true_faces, faces)\n\n    def test_faces_3D(self, mesh_3D):\n        \"\"\"Test the faces property on a 3D mesh.\"\"\"\n        index = (2, 3, 4)\n        cell = mesh_3D[index]\n        xmin, xmax, ymin, ymax, zmin, zmax = cell.bounds\n        true_faces = [\n            np.array([xmin, (ymin + ymax) / 2, (zmin + zmax) / 2]),\n            np.array([xmax, (ymin + ymax) / 2, (zmin + zmax) / 2]),\n            np.array([(xmin + xmax) / 2, ymin, (zmin + zmax) / 2]),\n            np.array([(xmin + xmax) / 2, ymax, (zmin + zmax) / 2]),\n            np.array([(xmin + xmax) / 2, (ymin + ymax) / 2, zmin]),\n            np.array([(xmin + xmax) / 2, (ymin + ymax) / 2, zmax]),\n        ]\n        faces = [mesh_3D.faces[i] for i in cell.faces]\n        np.testing.assert_array_equal(true_faces, faces)\n"
  },
  {
    "path": "tests/base/test_tensor_innerproduct.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\nfrom discretize import TensorMesh\nfrom discretize.utils import sdinv\n\n\nclass TestInnerProducts(discretize.tests.OrderTest):\n    \"\"\"Integrate an function over a unit cube domain\n    using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTensorMesh\", \"uniformCurv\", \"rotateCurv\"]\n    meshDimension = 3\n    meshSizes = [16, 32]\n\n    def getError(self):\n        call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\n\n        ex = lambda x, y, z: x**2 + y * z\n        ey = lambda x, y, z: (z**2) * x + y * z\n        ez = lambda x, y, z: y**2 + x * z\n\n        sigma1 = lambda x, y, z: x * y + 1\n        sigma2 = lambda x, y, z: x * z + 2\n        sigma3 = lambda x, y, z: 3 + z * y\n        sigma4 = lambda x, y, z: 0.1 * x * y * z\n        sigma5 = lambda x, y, z: 0.2 * x * y\n        sigma6 = lambda x, y, z: 0.1 * z\n\n        Gc = self.M.gridCC\n        if self.sigmaTest == 1:\n            sigma = np.c_[call(sigma1, Gc)]\n            analytic = 647.0 / 360  # Found using sympy.\n        elif self.sigmaTest == 3:\n            sigma = np.r_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc)]\n            analytic = 37.0 / 12  # Found using sympy.\n        elif self.sigmaTest == 6:\n            sigma = np.c_[\n                call(sigma1, Gc),\n                call(sigma2, Gc),\n                call(sigma3, Gc),\n                call(sigma4, Gc),\n                call(sigma5, Gc),\n                call(sigma6, Gc),\n            ]\n            analytic = 69881.0 / 21600  # Found using sympy.\n\n        if self.location == \"edges\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n            Ec = np.vstack(\n                (cart(self.M.gridEx), cart(self.M.gridEy), cart(self.M.gridEz))\n            )\n            E = self.M.project_edge_vector(Ec)\n\n            if self.invert_model:\n                A = self.M.get_edge_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_edge_inner_product(sigma)\n            numeric = E.T.dot(A.dot(E))\n        elif self.location == \"faces\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n            Fc = np.vstack(\n                (cart(self.M.gridFx), cart(self.M.gridFy), cart(self.M.gridFz))\n            )\n            F = self.M.project_face_vector(Fc)\n\n            if self.invert_model:\n                A = self.M.get_face_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_face_inner_product(sigma)\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_edges(self):\n        self.name = \"Edge Inner Product - Anisotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Anisotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order6_edges(self):\n        self.name = \"Edge Inner Product - Full Tensor\"\n        self.location = \"edges\"\n        self.sigmaTest = 6\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order6_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Full Tensor - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 6\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_faces(self):\n        self.name = \"Face Inner Product - Anisotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_faces_invert_model(self):\n        self.name = \"Face Inner Product - Anisotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order6_faces(self):\n        self.name = \"Face Inner Product - Full Tensor\"\n        self.location = \"faces\"\n        self.sigmaTest = 6\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order6_faces_invert_model(self):\n        self.name = \"Face Inner Product - Full Tensor - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 6\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsFaceProperties3D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a surface within a unit cube domain\n    using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 3\n    meshSizes = [16, 32]\n\n    def getError(self):\n        call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\n\n        ex = lambda x, y, z: x**2 + y * z\n        ey = lambda x, y, z: (z**2) * x + y * z\n        ez = lambda x, y, z: y**2 + x * z\n\n        tau_x = lambda x, y, z: y * z + 1  # x-face properties\n        tau_y = lambda x, y, z: x * z + 2  # y-face properties\n        tau_z = lambda x, y, z: 3 + x * y  # z-face properties\n        tau_funcs = [tau_x, tau_y, tau_z]\n\n        tau = 3 * [None]\n        for ii, comp in enumerate([\"x\", \"y\", \"z\"]):\n            faces = getattr(self.M, f\"faces_{comp}\")\n            k = np.isclose(faces[:, ii], 0.5)  # x, y or z location for each plane\n            tau_ii = 1e-8 * np.ones(len(faces))  # effectively zeros but stable\n            tau_ii[k] = tau_funcs[ii](*faces[k].T)\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        # integrate components parallel to the plane of integration\n        if self.location == \"edges\":\n            analytic = 5.02760416666667  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n\n            Ec = np.vstack(\n                (cart(self.M.gridEx), cart(self.M.gridEy), cart(self.M.gridEz))\n            )\n            E = self.M.project_edge_vector(Ec)\n\n            if not self.invert_model and not self.invert_matrix:\n                A = self.M.get_edge_inner_product_surface(tau)\n            elif self.invert_model:\n                A = self.M.get_edge_inner_product_surface(1 / tau, invert_model=True)\n            elif self.invert_matrix:\n                A = sdinv(\n                    self.M.get_edge_inner_product_surface(tau, invert_matrix=True)\n                )\n            else:\n                A = sdinv(\n                    self.M.get_edge_inner_product_surface(\n                        1 / tau, invert_model=True, invert_matrix=True\n                    )\n                )\n\n            numeric = E.T.dot(A.dot(E))\n\n        # integrate component normal to the plane of integration\n        elif self.location == \"faces\":\n            analytic = 2.66979166666667  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n\n            Fc = np.vstack(\n                (cart(self.M.gridFx), cart(self.M.gridFy), cart(self.M.gridFz))\n            )\n            F = self.M.project_face_vector(Fc)\n\n            if not self.invert_model and not self.invert_matrix:\n                A = self.M.get_face_inner_product_surface(tau)\n            elif self.invert_model:\n                A = self.M.get_face_inner_product_surface(1 / tau, invert_model=True)\n            elif self.invert_matrix:\n                A = sdinv(\n                    self.M.get_face_inner_product_surface(tau, invert_matrix=True)\n                )\n            else:\n                A = sdinv(\n                    self.M.get_face_inner_product_surface(\n                        1 / tau, invert_model=True, invert_matrix=True\n                    )\n                )\n\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.invert_matrix = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.invert_matrix = False\n        self.orderTest()\n\n    def test_order1_edges_invert_matrix(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_matrix\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.invert_matrix = True\n        self.orderTest()\n\n    def test_order1_edges_invert_matrix_and_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_matrix and invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.invert_matrix = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.invert_model = False\n        self.invert_matrix = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.invert_model = True\n        self.invert_matrix = False\n        self.orderTest()\n\n    def test_order1_faces_invert_matrix(self):\n        self.name = \"Face Inner Product - Isotropic - invert_matrix\"\n        self.location = \"faces\"\n        self.invert_model = False\n        self.invert_matrix = True\n        self.orderTest()\n\n    def test_order1_faces_invert_matrix_and_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_matrix and invert_model\"\n        self.location = \"faces\"\n        self.invert_model = True\n        self.invert_matrix = True\n        self.orderTest()\n\n\nclass TestInnerProductsEdgeProperties3D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a line within a unit cube domain\n    using edgeInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 3\n    meshSizes = [16, 32]\n\n    def getError(self):\n        call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\n\n        ex = lambda x, y, z: x**2 + y * z\n        ey = lambda x, y, z: (z**2) * x + y * z\n        ez = lambda x, y, z: y**2 + x * z\n\n        tau_x = lambda x, y, z: x + 1  # x-face properties  # NOQA F841\n        tau_y = lambda x, y, z: y + 2  # y-face properties  # NOQA F841\n        tau_z = lambda x, y, z: 3 * z + 1  # z-face properties  # NOQA F841\n\n        tau = 3 * [None]\n        for ii, comp in enumerate([\"x\", \"y\", \"z\"]):\n            k = np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 1], 0.5\n            ) & np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 2], 0.5\n            )  # x, y or z location for each line\n            tau_ii = 1e-8 * eval(\n                \"np.ones(self.M.nE{})\".format(comp)\n            )  # effectively zeros but stable\n            tau_ii[k] = eval(\"call(tau_{}, self.M.edges_{}[k, :])\".format(comp, comp))\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        analytic = 1.98906250000000  # Found using sympy.\n\n        cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n\n        Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy), cart(self.M.gridEz)))\n        E = self.M.project_edge_vector(Ec)\n\n        if not self.invert_model and not self.invert_matrix:\n            A = self.M.get_edge_inner_product_line(tau)\n        elif self.invert_model:\n            A = self.M.get_edge_inner_product_line(1 / tau, invert_model=True)\n        elif self.invert_matrix:\n            A = sdinv(self.M.get_edge_inner_product_line(tau, invert_matrix=True))\n        else:\n            A = sdinv(\n                self.M.get_edge_inner_product_line(\n                    1 / tau, invert_model=True, invert_matrix=True\n                )\n            )\n\n        numeric = E.T.dot(A.dot(E))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.invert_matrix = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.invert_matrix = False\n        self.orderTest()\n\n    def test_order1_edges_invert_matrix(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_matrix\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.invert_matrix = True\n        self.orderTest()\n\n    def test_order1_edges_invert_matrix_and_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_matrix and invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.invert_matrix = True\n        self.orderTest()\n\n\nclass TestInnerProducts2D(discretize.tests.OrderTest):\n    \"\"\"Integrate an function over a unit cube domain\n    using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTensorMesh\", \"uniformCurv\", \"rotateCurv\"]\n    meshDimension = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        z = 5  # Because 5 is just such a great number.\n\n        call = lambda fun, xy: fun(xy[:, 0], xy[:, 1])\n\n        ex = lambda x, y: x**2 + y * z\n        ey = lambda x, y: (z**2) * x + y * z\n\n        sigma1 = lambda x, y: x * y + 1\n        sigma2 = lambda x, y: x * z + 2\n        sigma3 = lambda x, y: 3 + z * y\n\n        Gc = self.M.gridCC\n        if self.sigmaTest == 1:\n            sigma = np.c_[call(sigma1, Gc)]\n            analytic = 144877.0 / 360  # Found using sympy. z=5\n        elif self.sigmaTest == 2:\n            sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc)]\n            analytic = 189959.0 / 120  # Found using sympy. z=5\n        elif self.sigmaTest == 3:\n            sigma = np.r_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc)]\n            analytic = 781427.0 / 360  # Found using sympy. z=5\n\n        if self.location == \"edges\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n            Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy)))\n            E = self.M.project_edge_vector(Ec)\n            if self.invert_model:\n                A = self.M.get_edge_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_edge_inner_product(sigma)\n            numeric = E.T.dot(A.dot(E))\n        elif self.location == \"faces\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n            Fc = np.vstack((cart(self.M.gridFx), cart(self.M.gridFy)))\n            F = self.M.project_face_vector(Fc)\n\n            if self.invert_model:\n                A = self.M.get_face_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_face_inner_product(sigma)\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"2D Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"2D Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_edges(self):\n        self.name = \"2D Edge Inner Product - Anisotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 2\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_edges_invert_model(self):\n        self.name = \"2D Edge Inner Product - Anisotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 2\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order6_edges(self):\n        self.name = \"2D Edge Inner Product - Full Tensor\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order6_edges_invert_model(self):\n        self.name = \"2D Edge Inner Product - Full Tensor - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"2D Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"2D Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order2_faces(self):\n        self.name = \"2D Face Inner Product - Anisotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 2\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order2_faces_invert_model(self):\n        self.name = \"2D Face Inner Product - Anisotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 2\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_faces(self):\n        self.name = \"2D Face Inner Product - Full Tensor\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_faces_invert_model(self):\n        self.name = \"2D Face Inner Product - Full Tensor - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsFaceProperties2D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a surface within a unit cube domain\n    using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 2\n    meshSizes = [8, 16, 32]\n\n    def getError(self):\n        call = lambda fun, xy: fun(xy[:, 0], xy[:, 1])\n\n        ex = lambda x, y: x**2 + y\n        ey = lambda x, y: (y**2) * x\n\n        tau_x = lambda x, y: 2 * y + 1  # x-face properties  # NOQA F841\n        tau_y = lambda x, y: x + 2  # y-face properties  # NOQA F841\n\n        tau = 2 * [None]\n        for ii, comp in enumerate([\"x\", \"y\"]):\n            k = np.isclose(\n                eval(\"self.M.faces_{}\".format(comp))[:, ii], 0.5\n            )  # x, or y location for each plane\n            tau_ii = 1e-8 * eval(\n                \"np.ones(self.M.nF{})\".format(comp)\n            )  # effectively zeros but stable\n            tau_ii[k] = eval(\"call(tau_{}, self.M.faces_{}[k, :])\".format(comp, comp))\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        # integrate components parallel to the plane of integration\n        if self.location == \"edges\":\n            analytic = 2.24166666666667  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n\n            Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy)))\n            E = self.M.project_edge_vector(Ec)\n\n            if self.invert_model:\n                A = self.M.get_edge_inner_product_surface(1 / tau, invert_model=True)\n            else:\n                A = self.M.get_edge_inner_product_surface(tau)\n\n            numeric = E.T.dot(A.dot(E))\n\n        # integrate component normal to the plane of integration\n        elif self.location == \"faces\":\n            analytic = 1.59895833333333  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n\n            Fc = np.vstack((cart(self.M.gridFx), cart(self.M.gridFy)))\n            F = self.M.project_face_vector(Fc)\n\n            if self.invert_model:\n                A = self.M.get_face_inner_product_surface(1 / tau, invert_model=True)\n            else:\n                A = self.M.get_face_inner_product_surface(tau)\n\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsEdgeProperties2D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a line within a unit cube domain\n    using edgeInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 2\n    meshSizes = [8, 16, 32]\n\n    def getError(self):\n        call = lambda fun, xy: fun(xy[:, 0], xy[:, 1])\n\n        ex = lambda x, y: x**2 + y\n        ey = lambda x, y: (x**2) * y\n\n        tau_x = lambda x, y: x + 1  # x-face properties  # NOQA F841\n        tau_y = lambda x, y: y + 2  # y-face properties  # NOQA F841\n\n        tau = 2 * [None]\n        for ii, comp in enumerate([\"x\", \"y\"]):\n            k = np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 1], 0.5\n            ) & np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 2], 0.5\n            )  # x, y or z location for each line\n            tau_ii = 1e-8 * eval(\n                \"np.ones(self.M.nE{})\".format(comp)\n            )  # effectively zeros but stable\n            tau_ii[k] = eval(\"call(tau_{}, self.M.edges_{}[k, :])\".format(comp, comp))\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        analytic = 1.38229166666667  # Found using sympy.\n\n        cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n\n        Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy)))\n        E = self.M.project_edge_vector(Ec)\n\n        if self.invert_model:\n            A = self.M.get_edge_inner_product_line(1 / tau, invert_model=True)\n        else:\n            A = self.M.get_edge_inner_product_line(tau)\n\n        numeric = E.T.dot(A.dot(E))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProducts1D(discretize.tests.OrderTest):\n    \"\"\"Integrate an function over a unit cube domain\n    using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 1\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        y = 12  # Because 12 is just such a great number.\n        z = 5  # Because 5 is just such a great number as well!\n\n        call = lambda fun, x: fun(x)\n\n        ex = lambda x: x**2 + y * z\n\n        sigma1 = lambda x: x * y + 1\n\n        Gc = self.M.gridCC\n        sigma = call(sigma1, Gc)\n        analytic = 128011.0 / 5  # Found using sympy. y=12, z=5\n\n        if self.location == \"faces\":\n            F = call(ex, self.M.gridFx)\n            if self.invert_model:\n                A = self.M.get_face_inner_product(1 / sigma, invert_model=True)\n            else:\n                A = self.M.get_face_inner_product(sigma)\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n        return err\n\n    def test_order1_faces(self):\n        self.name = \"1D Face Inner Product\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"1D Face Inner Product - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestTensorSizeErrorRaises(unittest.TestCase):\n    \"\"\"Ensure exception error when model is incorrect size\"\"\"\n\n    def setUp(self):\n        self.mesh3D = TensorMesh([4, 4, 4])\n        self.model = np.ones(self.mesh3D.nC)\n\n    def test_edge_inner_product_surface(self):\n        self.assertRaises(\n            ValueError, self.mesh3D.get_edge_inner_product_surface, self.model\n        )\n\n    def test_face_inner_product_surface(self):\n        self.assertRaises(\n            ValueError, self.mesh3D.get_face_inner_product_surface, self.model\n        )\n\n    def test_edge_inner_product_line(self):\n        self.assertRaises(\n            ValueError, self.mesh3D.get_edge_inner_product_line, self.model\n        )\n\n\n###################################################\n#### Uncomment to Reevaluate the InnerProducts ####\n###################################################\n\n# if __name__ == '__main__':\n# import sympy\n\n# x,y,z = sympy.symbols(['x','y','z'])\n# ex = x**2+y*z\n# ey = (z**2)*x+y*z\n# ez = y**2+x*z\n# e = sympy.Matrix([ex,ey,ez])\n\n# sigma1 = x*y+1\n# sigma2 = x*z+2\n# sigma3 = 3+z*y\n# sigma4 = 0.1*x*y*z\n# sigma5 = 0.2*x*y\n# sigma6 = 0.1*z\n\n# S1 = sympy.Matrix([[sigma1,0,0],[0,sigma1,0],[0,0,sigma1]])\n# S2 = sympy.Matrix([[sigma1,0,0],[0,sigma2,0],[0,0,sigma3]])\n# S3 = sympy.Matrix([[sigma1,sigma4,sigma5],[sigma4,sigma2,sigma6],[sigma5,sigma6,sigma3]])\n\n# print('3D')\n# print(sympy.integrate(sympy.integrate(sympy.integrate(e.T*S1*e, (x,0,1)), (y,0,1)), (z,0,1)))\n# print(sympy.integrate(sympy.integrate(sympy.integrate(e.T*S2*e, (x,0,1)), (y,0,1)), (z,0,1)))\n# print(sympy.integrate(sympy.integrate(sympy.integrate(e.T*S3*e, (x,0,1)), (y,0,1)), (z,0,1)))\n\n\n# z = 5\n# ex = x**2+y*z\n# ey = (z**2)*x+y*z\n# e = sympy.Matrix([ex,ey])\n\n# sigma1 = x*y+1\n# sigma2 = x*z+2\n# sigma3 = 3+z*y\n\n# S1 = sympy.Matrix([[sigma1,0],[0,sigma1]])\n# S2 = sympy.Matrix([[sigma1,0],[0,sigma2]])\n# S3 = sympy.Matrix([[sigma1,sigma3],[sigma3,sigma2]])\n\n# print('2D')\n# print(sympy.integrate(sympy.integrate(e.T*S1*e, (x,0,1)), (y,0,1)))\n# print(sympy.integrate(sympy.integrate(e.T*S2*e, (x,0,1)), (y,0,1)))\n# print(sympy.integrate(sympy.integrate(e.T*S3*e, (x,0,1)), (y,0,1)))\n\n# y = 12\n# z = 5\n# ex = x**2+y*z\n# e = ex\n\n# sigma1 = x*y+1\n\n# print('1D')\n# print(sympy.integrate(e*sigma1*e, (x,0,1)))\n"
  },
  {
    "path": "tests/base/test_tensor_innerproduct_derivs.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\nfrom discretize import TensorMesh\n\nrng = np.random.default_rng(542)\n\n\nclass TestInnerProductsDerivsTensor(unittest.TestCase):\n    def doTestFace(\n        self, h, rep, fast, meshType, invert_model=False, invert_matrix=False\n    ):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n            mesh.number(balance=False)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nF)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep)\n\n        def fun(sig):\n            M = mesh.get_face_inner_product(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_face_inner_product_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n                do_fast=fast,\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Face\",\n            h,\n            rep,\n            fast,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=452\n        )\n\n    def doTestEdge(\n        self, h, rep, fast, meshType, invert_model=False, invert_matrix=False\n    ):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n            mesh.number(balance=False)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nE)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep)\n\n        def fun(sig):\n            M = mesh.get_edge_inner_product(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_edge_inner_product_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n                do_fast=fast,\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Edge\",\n            h,\n            rep,\n            fast,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=4567\n        )\n\n    def test_FaceIP_1D_float(self):\n        self.assertTrue(self.doTestFace([10], 0, False, \"Tensor\"))\n\n    def test_FaceIP_2D_float(self):\n        self.assertTrue(self.doTestFace([10, 4], 0, False, \"Tensor\"))\n\n    def test_FaceIP_3D_float(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 0, False, \"Tensor\"))\n\n    def test_FaceIP_1D_isotropic(self):\n        self.assertTrue(self.doTestFace([10], 1, False, \"Tensor\"))\n\n    def test_FaceIP_2D_isotropic(self):\n        self.assertTrue(self.doTestFace([10, 4], 1, False, \"Tensor\"))\n\n    def test_FaceIP_3D_isotropic(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 1, False, \"Tensor\"))\n\n    def test_FaceIP_2D_anisotropic(self):\n        self.assertTrue(self.doTestFace([10, 4], 2, False, \"Tensor\"))\n\n    def test_FaceIP_3D_anisotropic(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 3, False, \"Tensor\"))\n\n    def test_FaceIP_2D_tensor(self):\n        self.assertTrue(self.doTestFace([10, 4], 3, False, \"Tensor\"))\n\n    def test_FaceIP_3D_tensor(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 6, False, \"Tensor\"))\n\n    def test_FaceIP_1D_float_fast(self):\n        self.assertTrue(self.doTestFace([10], 0, True, \"Tensor\"))\n\n    def test_FaceIP_2D_float_fast(self):\n        self.assertTrue(self.doTestFace([10, 4], 0, True, \"Tensor\"))\n\n    def test_FaceIP_3D_float_fast(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 0, True, \"Tensor\"))\n\n    def test_FaceIP_1D_isotropic_fast(self):\n        self.assertTrue(self.doTestFace([10], 1, True, \"Tensor\"))\n\n    def test_FaceIP_2D_isotropic_fast(self):\n        self.assertTrue(self.doTestFace([10, 4], 1, True, \"Tensor\"))\n\n    def test_FaceIP_3D_isotropic_fast(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 1, True, \"Tensor\"))\n\n    def test_FaceIP_2D_anisotropic_fast(self):\n        self.assertTrue(self.doTestFace([10, 4], 2, True, \"Tensor\"))\n\n    def test_FaceIP_3D_anisotropic_fast(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 3, True, \"Tensor\"))\n\n    def test_EdgeIP_1D_float(self):\n        self.assertTrue(self.doTestEdge([10], 0, False, \"Tensor\"))\n\n    def test_EdgeIP_2D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4], 0, False, \"Tensor\"))\n\n    def test_EdgeIP_3D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 0, False, \"Tensor\"))\n\n    def test_EdgeIP_1D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10], 1, False, \"Tensor\"))\n\n    def test_EdgeIP_2D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4], 1, False, \"Tensor\"))\n\n    def test_EdgeIP_3D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 1, False, \"Tensor\"))\n\n    def test_EdgeIP_2D_anisotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4], 2, False, \"Tensor\"))\n\n    def test_EdgeIP_3D_anisotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 3, False, \"Tensor\"))\n\n    def test_EdgeIP_2D_tensor(self):\n        self.assertTrue(self.doTestEdge([10, 4], 3, False, \"Tensor\"))\n\n    def test_EdgeIP_3D_tensor(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 6, False, \"Tensor\"))\n\n    def test_EdgeIP_1D_float_fast(self):\n        self.assertTrue(self.doTestEdge([10], 0, True, \"Tensor\"))\n\n    def test_EdgeIP_2D_float_fast(self):\n        self.assertTrue(self.doTestEdge([10, 4], 0, True, \"Tensor\"))\n\n    def test_EdgeIP_3D_float_fast(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 0, True, \"Tensor\"))\n\n    def test_EdgeIP_1D_isotropic_fast(self):\n        self.assertTrue(self.doTestEdge([10], 1, True, \"Tensor\"))\n\n    def test_EdgeIP_2D_isotropic_fast(self):\n        self.assertTrue(self.doTestEdge([10, 4], 1, True, \"Tensor\"))\n\n    def test_EdgeIP_3D_isotropic_fast(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 1, True, \"Tensor\"))\n\n    def test_EdgeIP_2D_anisotropic_fast(self):\n        self.assertTrue(self.doTestEdge([10, 4], 2, True, \"Tensor\"))\n\n    def test_EdgeIP_3D_anisotropic_fast(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 3, True, \"Tensor\"))\n\n    def test_FaceIP_1D_float_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10], 0, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_2D_float_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4], 0, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_3D_float_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4, 5], 0, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_1D_isotropic_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10], 1, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_2D_isotropic_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4], 1, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_3D_isotropic_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4, 5], 1, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_2D_anisotropic_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4], 2, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_3D_anisotropic_fast_harmonic(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4, 5], 3, True, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_2D_float_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4], 0, False, \"Curv\"))\n\n    def test_FaceIP_3D_float_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 0, False, \"Curv\"))\n\n    def test_FaceIP_2D_isotropic_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4], 1, False, \"Curv\"))\n\n    def test_FaceIP_3D_isotropic_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 1, False, \"Curv\"))\n\n    def test_FaceIP_2D_anisotropic_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4], 2, False, \"Curv\"))\n\n    def test_FaceIP_3D_anisotropic_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 3, False, \"Curv\"))\n\n    def test_FaceIP_2D_tensor_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4], 3, False, \"Curv\"))\n\n    def test_FaceIP_3D_tensor_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 6, False, \"Curv\"))\n\n    def test_FaceIP_2D_float_fast_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4], 0, True, \"Curv\"))\n\n    def test_FaceIP_3D_float_fast_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 0, True, \"Curv\"))\n\n    def test_FaceIP_2D_isotropic_fast_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4], 1, True, \"Curv\"))\n\n    def test_FaceIP_3D_isotropic_fast_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 1, True, \"Curv\"))\n\n    def test_FaceIP_2D_anisotropic_fast_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4], 2, True, \"Curv\"))\n\n    def test_FaceIP_3D_anisotropic_fast_Curv(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 3, True, \"Curv\"))\n\n    def test_EdgeIP_2D_float_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4], 0, False, \"Curv\"))\n\n    def test_EdgeIP_3D_float_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 0, False, \"Curv\"))\n\n    def test_EdgeIP_2D_isotropic_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4], 1, False, \"Curv\"))\n\n    def test_EdgeIP_3D_isotropic_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 1, False, \"Curv\"))\n\n    def test_EdgeIP_2D_anisotropic_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4], 2, False, \"Curv\"))\n\n    def test_EdgeIP_3D_anisotropic_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 3, False, \"Curv\"))\n\n    def test_EdgeIP_2D_tensor_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4], 3, False, \"Curv\"))\n\n    def test_EdgeIP_3D_tensor_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 6, False, \"Curv\"))\n\n    def test_EdgeIP_2D_float_fast_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4], 0, True, \"Curv\"))\n\n    def test_EdgeIP_3D_float_fast_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 0, True, \"Curv\"))\n\n    def test_EdgeIP_2D_isotropic_fast_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4], 1, True, \"Curv\"))\n\n    def test_EdgeIP_3D_isotropic_fast_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 1, True, \"Curv\"))\n\n    def test_EdgeIP_2D_anisotropic_fast_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4], 2, True, \"Curv\"))\n\n    def test_EdgeIP_3D_anisotropic_fast_Curv(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 3, True, \"Curv\"))\n\n\nclass TestFacePropertiesInnerProductsDerivsTensor(unittest.TestCase):\n    def doTestFace(self, h, rep, meshType, invert_model=False, invert_matrix=False):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n            mesh.number(balance=False)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nF)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep)\n\n        def fun(sig):\n            M = mesh.get_face_inner_product_surface(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_face_inner_product_surface_deriv(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Face\",\n            h,\n            rep,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=421\n        )\n\n    def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n            mesh.number(balance=False)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nE)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep)\n\n        def fun(sig):\n            M = mesh.get_edge_inner_product_surface(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_edge_inner_product_surface_deriv(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Edge\",\n            h,\n            rep,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=31\n        )\n\n    def test_FaceIP_2D_float(self):\n        self.assertTrue(self.doTestFace([10, 4], 0, \"Tensor\"))\n\n    def test_FaceIP_3D_float(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 0, \"Tensor\"))\n\n    def test_FaceIP_2D_isotropic(self):\n        self.assertTrue(self.doTestFace([10, 4], 1, \"Tensor\"))\n\n    def test_FaceIP_3D_isotropic(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 1, \"Tensor\"))\n\n    def test_EdgeIP_2D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4], 0, \"Tensor\"))\n\n    def test_EdgeIP_3D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 0, \"Tensor\"))\n\n    def test_EdgeIP_2D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4], 1, \"Tensor\"))\n\n    def test_EdgeIP_3D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 1, \"Tensor\"))\n\n    def test_FaceIP_2D_float_invert_all(self):\n        self.assertTrue(\n            self.doTestFace([10, 4], 0, \"Tensor\", invert_model=True, invert_matrix=True)\n        )\n\n    def test_FaceIP_3D_float_invert_all(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4, 5], 0, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_FaceIP_2D_isotropic_invert_all(self):\n        self.assertTrue(\n            self.doTestFace([10, 4], 1, \"Tensor\", invert_model=True, invert_matrix=True)\n        )\n\n    def test_FaceIP_3D_isotropic_invert_all(self):\n        self.assertTrue(\n            self.doTestFace(\n                [10, 4, 5], 1, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_EdgeIP_2D_float_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge([10, 4], 0, \"Tensor\", invert_model=True, invert_matrix=True)\n        )\n\n    def test_EdgeIP_3D_float_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge(\n                [10, 4, 5], 0, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_EdgeIP_2D_isotropic_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge([10, 4], 1, \"Tensor\", invert_model=True, invert_matrix=True)\n        )\n\n    def test_EdgeIP_3D_isotropic_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge(\n                [10, 4, 5], 1, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n\nclass TestEdgePropertiesInnerProductsDerivsTensor(unittest.TestCase):\n    def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n            mesh.number(balance=False)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nE)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nE * rep)\n\n        def fun(sig):\n            M = mesh.get_edge_inner_product_line(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n            Md = mesh.get_edge_inner_product_line_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Edge\",\n            h,\n            rep,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=64\n        )\n\n    def test_EdgeIP_2D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4], 0, \"Tensor\"))\n\n    def test_EdgeIP_3D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 0, \"Tensor\"))\n\n    def test_EdgeIP_2D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4], 1, \"Tensor\"))\n\n    def test_EdgeIP_3D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 1, \"Tensor\"))\n\n    def test_EdgeIP_2D_float_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge([10, 4], 0, \"Tensor\", invert_model=True, invert_matrix=True)\n        )\n\n    def test_EdgeIP_3D_float_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge(\n                [10, 4, 5], 0, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n    def test_EdgeIP_2D_isotropic_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge([10, 4], 1, \"Tensor\", invert_model=True, invert_matrix=True)\n        )\n\n    def test_EdgeIP_3D_isotropic_invert_all(self):\n        self.assertTrue(\n            self.doTestEdge(\n                [10, 4, 5], 1, \"Tensor\", invert_model=True, invert_matrix=True\n            )\n        )\n\n\nclass TestTensorSizeErrorRaises(unittest.TestCase):\n    \"\"\"Ensure exception error when model is incorrect size\"\"\"\n\n    def setUp(self):\n        self.mesh3D = TensorMesh([4, 4, 4])\n        self.model = rng.random(self.mesh3D.nC)\n\n    def test_edge_inner_product_surface_deriv(self):\n        self.assertRaises(\n            ValueError, self.mesh3D.get_edge_inner_product_surface_deriv, self.model\n        )\n\n    def test_face_inner_product_surface_deriv(self):\n        self.assertRaises(\n            ValueError, self.mesh3D.get_face_inner_product_surface_deriv, self.model\n        )\n\n    def test_edge_inner_product_line_deriv(self):\n        self.assertRaises(\n            ValueError, self.mesh3D.get_edge_inner_product_line_deriv, self.model\n        )\n\n\nclass TestNone(unittest.TestCase):\n    \"\"\"Test None outputs\"\"\"\n\n    def setUp(self):\n        self.mesh3D = TensorMesh([4, 4, 4])\n\n    def test_edge_inner_product_surface_deriv(self):\n        self.assertIsNone(self.mesh3D.get_edge_inner_product_surface_deriv(None))\n\n    def test_face_inner_product_surface_deriv(self):\n        self.assertIsNone(self.mesh3D.get_face_inner_product_surface_deriv(None))\n\n    def test_edge_inner_product_line_deriv(self):\n        self.assertIsNone(self.mesh3D.get_edge_inner_product_line_deriv(None))\n"
  },
  {
    "path": "tests/base/test_tensor_io.py",
    "content": "import numpy as np\nimport discretize\nimport pytest\n\ntry:\n    import vtk  # NOQA F401\n\n    has_vtk = True\nexcept ImportError:\n    has_vtk = False\n\n\n@pytest.mark.parametrize(\"dim\", [2, 3])\ndef test_write_read_ubc_mesh_model(dim, tmp_path):\n    h = np.ones(16)\n    mesh = discretize.TensorMesh([h, 2 * h, 3 * h][:dim])\n    # Make a vector\n    vec = np.arange(mesh.nC)\n    # Write and read\n    mshfname = \"temp.msh\"\n    modelfname = \"arange.txt\"\n    modelfname1 = \"arange2.txt\"\n    modeldict = {modelfname: vec, modelfname1: vec + 1}\n    comment_lines = \"!comment line\\n\" + \"!again\\n\" + \"!and again\\n\"\n\n    mesh.write_UBC(\n        \"temp.msh\", modeldict, directory=tmp_path, comment_lines=comment_lines\n    )\n    meshUBC = discretize.TensorMesh.read_UBC(mshfname, directory=tmp_path)\n\n    assert mesh is not meshUBC\n    assert mesh.equals(meshUBC)\n\n    vecUBC = meshUBC.read_model_UBC(modelfname, directory=tmp_path)\n    vec2UBC = mesh.read_model_UBC(modelfname1, directory=tmp_path)\n\n    np.testing.assert_equal(vec, vecUBC)\n    np.testing.assert_equal(vec + 1, vec2UBC)\n\n\nif has_vtk:\n\n    def test_VTKfiles(tmp_path):\n        h = np.ones(16)\n        mesh = discretize.TensorMesh([h, 2 * h, 3 * h])\n\n        vec = np.arange(mesh.nC)\n        vtrfname = \"temp.vtr\"\n        modelfname = \"arange.txt\"\n        modeldict = {modelfname: vec}\n        mesh.write_vtk(vtrfname, modeldict, directory=tmp_path)\n        meshVTR, models = discretize.TensorMesh.read_vtk(vtrfname, directory=tmp_path)\n\n        assert mesh is not meshVTR\n        assert mesh.equals(meshVTR)\n\n        np.testing.assert_equal(vec, models[modelfname])\n"
  },
  {
    "path": "tests/base/test_tensor_omf.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\n\ntry:\n    import omf\nexcept ImportError:\n    has_omf = False\nelse:\n    has_omf = True\n\n\nif has_omf:\n    from discretize.mixins.omf_mod import _unravel_data_array, _ravel_data_array\n\n    class TestTensorMeshOMF(unittest.TestCase):\n        def setUp(self):\n            h = np.ones(16)\n            mesh = discretize.TensorMesh([h, 2 * h, 3 * h])\n            self.mesh = mesh\n\n        def test_to_omf(self):\n            mesh = self.mesh\n            vec = np.arange(mesh.nC)\n            models = {\"arange\": vec}\n\n            omf_element = mesh.to_omf(models)\n            geom = omf_element.geometry\n\n            # Check geometry\n            self.assertEqual(mesh.nC, geom.num_cells)\n            self.assertEqual(mesh.nN, geom.num_nodes)\n            self.assertTrue(np.allclose(mesh.h[0], geom.tensor_u))\n            self.assertTrue(np.allclose(mesh.h[1], geom.tensor_v))\n            self.assertTrue(np.allclose(mesh.h[2], geom.tensor_w))\n            self.assertTrue(np.allclose(mesh.orientation[0], geom.axis_u))\n            self.assertTrue(np.allclose(mesh.orientation[1], geom.axis_v))\n            self.assertTrue(np.allclose(mesh.orientation[2], geom.axis_w))\n            self.assertTrue(np.allclose(mesh.x0, geom.origin))\n\n            # Check data arrays\n            self.assertEqual(len(models.keys()), len(omf_element.data))\n            for i in range(len(omf_element.data)):\n                name = list(models.keys())[i]\n                scalar_data = omf_element.data[i]\n                self.assertEqual(name, scalar_data.name)\n                arr = _unravel_data_array(\n                    np.array(scalar_data.array), *mesh.shape_cells\n                )\n                self.assertTrue(np.allclose(models[name], arr))\n\n        def test_from_omf(self):\n            rng = np.random.default_rng(52134)\n            omf_element = omf.VolumeElement(\n                name=\"vol_ir\",\n                geometry=omf.VolumeGridGeometry(\n                    axis_u=[1, 1, 0],\n                    axis_v=[0, 0, 1],\n                    axis_w=[1, -1, 0],\n                    tensor_u=np.ones(10).astype(float),\n                    tensor_v=np.ones(15).astype(float),\n                    tensor_w=np.ones(20).astype(float),\n                    origin=[10.0, 10.0, -10],\n                ),\n                data=[\n                    omf.ScalarData(\n                        name=\"Random Data\",\n                        location=\"cells\",\n                        array=rng.random((10, 15, 20)).flatten(),\n                    )\n                ],\n            )\n\n            # Make a discretize mesh\n            mesh, models = discretize.TensorMesh.from_omf(omf_element)\n\n            geom = omf_element.geometry\n            # Check geometry\n            self.assertEqual(mesh.nC, geom.num_cells)\n            self.assertEqual(mesh.nN, geom.num_nodes)\n            self.assertTrue(np.allclose(mesh.h[0], geom.tensor_u))\n            self.assertTrue(np.allclose(mesh.h[1], geom.tensor_v))\n            self.assertTrue(np.allclose(mesh.h[2], geom.tensor_w))\n            self.assertTrue(np.allclose(mesh.orientation[0], geom.axis_u))\n            self.assertTrue(np.allclose(mesh.orientation[1], geom.axis_v))\n            self.assertTrue(np.allclose(mesh.orientation[2], geom.axis_w))\n            self.assertTrue(np.allclose(mesh.x0, geom.origin))\n\n            # Check data arrays\n            self.assertEqual(len(models.keys()), len(omf_element.data))\n            for i in range(len(omf_element.data)):\n                name = list(models.keys())[i]\n                scalar_data = omf_element.data[i]\n                self.assertEqual(name, scalar_data.name)\n                arr = _ravel_data_array(\n                    models[name],\n                    len(geom.tensor_u),\n                    len(geom.tensor_v),\n                    len(geom.tensor_w),\n                )\n                self.assertTrue(np.allclose(np.array(scalar_data.array), arr))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_tensor_vtk.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\n\ntry:\n    import vtk.util.numpy_support as nps\nexcept ImportError:\n    has_vtk = False\nelse:\n    has_vtk = True\n\n\nif has_vtk:\n\n    class TestTensorMeshVTK(unittest.TestCase):\n        def setUp(self):\n            h = np.ones(16)\n            mesh = discretize.TensorMesh([h, 2 * h, 3 * h])\n            self.mesh = mesh\n\n        def test_VTK_object_conversion(self):\n            mesh = self.mesh\n            vec = np.arange(mesh.nC)\n            models = {\"arange\": vec}\n\n            vtkObj = mesh.to_vtk(models)\n\n            self.assertEqual(mesh.nC, vtkObj.GetNumberOfCells())\n            self.assertEqual(mesh.nN, vtkObj.GetNumberOfPoints())\n            self.assertEqual(\n                len(models.keys()), vtkObj.GetCellData().GetNumberOfArrays()\n            )\n            bnds = vtkObj.GetBounds()\n            self.assertEqual(mesh.x0[0], bnds[0])\n            self.assertEqual(mesh.x0[1], bnds[2])\n            self.assertEqual(mesh.x0[2], bnds[4])\n\n            for i in range(vtkObj.GetCellData().GetNumberOfArrays()):\n                name = list(models.keys())[i]\n                self.assertEqual(name, vtkObj.GetCellData().GetArrayName(i))\n                arr = nps.vtk_to_numpy(vtkObj.GetCellData().GetArray(i))\n                arr = arr.flatten(order=\"F\")\n                self.assertTrue(np.allclose(models[name], arr))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_tests.py",
    "content": "import sys\nimport pytest\nimport discretize\nimport subprocess\nimport numpy as np\nimport scipy.sparse as sp\nfrom discretize.tests import (\n    assert_isadjoint,\n    check_derivative,\n    assert_expected_order,\n    _warn_random_test,\n    setup_mesh,\n)\n\n\nclass TestAssertIsAdjoint:\n    def test_defaults(self, capsys):\n        # Use volume_average to test default case.\n\n        h = [[11, 20.5, 33], [222, 111, 333], np.ones(4) * 55.5]\n        mesh1 = discretize.TensorMesh(h, \"CCC\")\n\n        hx = np.ones(20) * 20\n        mesh2 = discretize.TensorMesh([hx, hx, hx], \"CCC\")\n\n        P = discretize.utils.volume_average(mesh1, mesh2)\n\n        # return\n        out1 = assert_isadjoint(\n            lambda u: P * u,\n            lambda v: P.T * v,\n            mesh1.n_cells,\n            mesh2.n_cells,\n            assert_error=False,\n            random_seed=41,\n        )\n        out2, _ = capsys.readouterr()\n        assert out1\n        assert \"Adjoint test PASSED\" in out2\n\n        # raise error\n        with pytest.raises(AssertionError, match=\"Adjoint test failed\"):\n            assert_isadjoint(\n                lambda u: P * u * 2,  # Add erroneous factor\n                lambda v: P.T * v,\n                mesh1.n_cells,\n                mesh2.n_cells,\n                random_seed=42,\n            )\n\n    def test_different_shape(self):\n        # Def sum operator over axis 1; ravelled in 'F' so needs 'F'-order.\n\n        nt = 3\n\n        def fwd(inp):\n            return np.sum(inp, 1)\n\n        # Def adj of fwd\n        def adj(inp):\n            out = np.expand_dims(inp, 1)\n            return np.tile(out, nt)\n\n        assert_isadjoint(\n            fwd,\n            adj,\n            shape_u=(4, nt),\n            shape_v=(4,),\n            random_seed=42,\n        )\n\n    def test_complex_clinear(self):\n        # The complex conjugate is self-adjoint, real-linear.\n        assert_isadjoint(\n            np.conj,\n            np.conj,\n            (4, 3),\n            (4, 3),\n            complex_u=True,\n            complex_v=True,\n            clinear=False,\n            random_seed=112,\n        )\n\n\nclass TestCheckDerivative:\n    def test_simplePass(self):\n        def simplePass(x):\n            return np.sin(x), sp.diags(np.cos(x))\n\n        rng = np.random.default_rng(5322)\n        check_derivative(\n            simplePass, rng.standard_normal(5), plotIt=False, random_seed=42\n        )\n\n    def test_simpleFunction(self):\n        def simpleFunction(x):\n            return np.sin(x), lambda xi: np.cos(x) * xi\n\n        rng = np.random.default_rng(5322)\n        check_derivative(\n            simpleFunction, rng.standard_normal(5), plotIt=False, random_seed=23\n        )\n\n    def test_simpleFail(self):\n        def simpleFail(x):\n            return np.sin(x), -sp.diags(np.cos(x))\n\n        rng = np.random.default_rng(5322)\n        with pytest.raises(AssertionError):\n            check_derivative(\n                simpleFail, rng.standard_normal(5), plotIt=False, random_seed=64\n            )\n\n\n@pytest.mark.parametrize(\"test_type\", [\"mean\", \"min\", \"last\", \"all\", \"mean_at_least\"])\ndef test_expected_order_pass(test_type):\n    func = lambda y: np.cos(y)\n    func_deriv = lambda y: -np.sin(y)\n\n    def deriv_error(n):\n        # The l-inf norm of the average error vector does\n        # follow second order convergence.\n        nodes = np.linspace(0, 1, n + 1)\n        cc = 0.5 * (nodes[1:] + nodes[:-1])\n        dh = nodes[1] - nodes[0]\n        node_eval = func(nodes)\n        num_deriv = (node_eval[1:] - node_eval[:-1]) / dh\n        true_deriv = func_deriv(cc)\n        err = np.linalg.norm(num_deriv - true_deriv, ord=np.inf)\n        return err, dh\n\n    assert_expected_order(deriv_error, [10, 20, 30, 40, 50], test_type=test_type)\n\n\n@pytest.mark.parametrize(\"test_type\", [\"mean\", \"min\", \"last\", \"all\", \"mean_at_least\"])\ndef test_expected_order_failed(test_type):\n    func = lambda y: np.cos(y)\n    func_deriv = lambda y: -np.sin(y)\n\n    def deriv_error(n):\n        # The l2 norm of the average error vector does not\n        # follow second order convergence.\n        nodes = np.linspace(0, 1, n + 1)\n        cc = 0.5 * (nodes[1:] + nodes[:-1])\n        dh = nodes[1] - nodes[0]\n        node_eval = func(nodes)\n        num_deriv = (node_eval[1:] - node_eval[:-1]) / dh\n        true_deriv = func_deriv(cc)\n        err = np.linalg.norm(num_deriv - true_deriv)\n        return err, dh\n\n    with pytest.raises(AssertionError):\n        assert_expected_order(deriv_error, [10, 20, 30, 40, 50], test_type=test_type)\n\n\ndef test_expected_order_bad_test_type():\n    # should fail fast if given a bad test_type\n    with pytest.raises(ValueError):\n        assert_expected_order(None, [10, 20, 30, 40, 50], test_type=\"not_a_test\")\n\n\n@pytest.mark.skipif(not sys.platform.startswith(\"linux\"), reason=\"Not Linux.\")\ndef test_import_time():\n    # Relevant for the CLI: How long does it take to import?\n    cmd = [\"time\", \"-f\", \"%U\", \"python\", \"-c\", \"import discretize\"]\n    # Run it twice, just in case.\n    subprocess.run(cmd)\n    subprocess.run(cmd)\n    # Capture it\n    out = subprocess.run(cmd, capture_output=True)\n\n    # Currently we check t < 1.25s.\n    assert float(out.stderr.decode(\"utf-8\")[:-1]) < 1.25\n\n\ndef test_random_test_warning():\n\n    match = r\"You are running a pytest without setting a random seed.*\"\n    with pytest.warns(UserWarning, match=match):\n        _warn_random_test()\n\n    def simple_deriv(x):\n        return np.sin(x), lambda y: np.cos(x) * y\n\n    with pytest.warns(UserWarning, match=match):\n        check_derivative(simple_deriv, np.zeros(10), plotIt=False)\n\n    with pytest.warns(UserWarning, match=match):\n        setup_mesh(\"randomTensorMesh\", 10, 1)\n\n    with pytest.warns(UserWarning, match=match):\n        assert_isadjoint(lambda x: x, lambda x: x, 5, 5)\n"
  },
  {
    "path": "tests/base/test_utils.py",
    "content": "import unittest\nimport numpy as np\nimport scipy.sparse as sp\nfrom discretize.utils import (\n    sdiag,\n    sub2ind,\n    ndgrid,\n    mkvc,\n    is_scalar,\n    inverse_2x2_block_diagonal,\n    inverse_3x3_block_diagonal,\n    inverse_property_tensor,\n    make_property_tensor,\n    index_cube,\n    ind2sub,\n    as_array_n_by_dim,\n    TensorType,\n    Zero,\n    Identity,\n    extract_core_mesh,\n    active_from_xyz,\n    mesh_builder_xyz,\n    refine_tree_xyz,\n    unpack_widths,\n    cross2d,\n)\nimport discretize\n\nTOL = 1e-8\n\n\nclass TestSequenceFunctions(unittest.TestCase):\n    def setUp(self):\n        self.a = np.array([1, 2, 3])\n        self.b = np.array([1, 2])\n        self.c = np.array([1, 2, 3, 4])\n\n    def test_mkvc1(self):\n        x = mkvc(self.a)\n        self.assertTrue(x.shape, (3,))\n\n    def test_mkvc2(self):\n        x = mkvc(self.a, 2)\n        self.assertTrue(x.shape, (3, 1))\n\n    def test_mkvc3(self):\n        x = mkvc(self.a, 3)\n        self.assertTrue(x.shape, (3, 1, 1))\n\n    def test_ndgrid_2D(self):\n        XY = ndgrid([self.a, self.b])\n\n        X1_test = np.array([1, 2, 3, 1, 2, 3])\n        X2_test = np.array([1, 1, 1, 2, 2, 2])\n\n        self.assertTrue(np.all(XY[:, 0] == X1_test))\n        self.assertTrue(np.all(XY[:, 1] == X2_test))\n\n    def test_ndgrid_3D(self):\n        XYZ = ndgrid([self.a, self.b, self.c])\n\n        X1_test = np.array(\n            [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]\n        )\n        X2_test = np.array(\n            [1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2]\n        )\n        X3_test = np.array(\n            [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4]\n        )\n\n        self.assertTrue(np.all(XYZ[:, 0] == X1_test))\n        self.assertTrue(np.all(XYZ[:, 1] == X2_test))\n        self.assertTrue(np.all(XYZ[:, 2] == X3_test))\n\n    def test_sub2ind(self):\n        x = np.ones((5, 2))\n        self.assertTrue(np.all(sub2ind(x.shape, [0, 0]) == [0]))\n        self.assertTrue(np.all(sub2ind(x.shape, [4, 0]) == [4]))\n        self.assertTrue(np.all(sub2ind(x.shape, [0, 1]) == [5]))\n        self.assertTrue(np.all(sub2ind(x.shape, [4, 1]) == [9]))\n        self.assertTrue(np.all(sub2ind(x.shape, [[4, 1]]) == [9]))\n        self.assertTrue(\n            np.all(sub2ind(x.shape, [[0, 0], [4, 0], [0, 1], [4, 1]]) == [0, 4, 5, 9])\n        )\n\n    def test_ind2sub(self):\n        x = np.ones((5, 2))\n        self.assertTrue(np.all(ind2sub(x.shape, [0, 4, 5, 9])[0] == [0, 4, 0, 4]))\n        self.assertTrue(np.all(ind2sub(x.shape, [0, 4, 5, 9])[1] == [0, 0, 1, 1]))\n\n    def test_index_cube_2D(self):\n        nN = np.array([3, 3])\n        self.assertTrue(np.all(index_cube(\"A\", nN) == np.array([0, 1, 3, 4])))\n        self.assertTrue(np.all(index_cube(\"B\", nN) == np.array([3, 4, 6, 7])))\n        self.assertTrue(np.all(index_cube(\"C\", nN) == np.array([4, 5, 7, 8])))\n        self.assertTrue(np.all(index_cube(\"D\", nN) == np.array([1, 2, 4, 5])))\n\n    def test_index_cube_3D(self):\n        nN = np.array([3, 3, 3])\n        self.assertTrue(\n            np.all(index_cube(\"A\", nN) == np.array([0, 1, 3, 4, 9, 10, 12, 13]))\n        )\n        self.assertTrue(\n            np.all(index_cube(\"B\", nN) == np.array([3, 4, 6, 7, 12, 13, 15, 16]))\n        )\n        self.assertTrue(\n            np.all(index_cube(\"C\", nN) == np.array([4, 5, 7, 8, 13, 14, 16, 17]))\n        )\n        self.assertTrue(\n            np.all(index_cube(\"D\", nN) == np.array([1, 2, 4, 5, 10, 11, 13, 14]))\n        )\n        self.assertTrue(\n            np.all(index_cube(\"E\", nN) == np.array([9, 10, 12, 13, 18, 19, 21, 22]))\n        )\n        self.assertTrue(\n            np.all(index_cube(\"F\", nN) == np.array([12, 13, 15, 16, 21, 22, 24, 25]))\n        )\n        self.assertTrue(\n            np.all(index_cube(\"G\", nN) == np.array([13, 14, 16, 17, 22, 23, 25, 26]))\n        )\n        self.assertTrue(\n            np.all(index_cube(\"H\", nN) == np.array([10, 11, 13, 14, 19, 20, 22, 23]))\n        )\n\n    def test_invXXXBlockDiagonal(self):\n        rng = np.random.default_rng(78352)\n        a = [rng.random((5, 1)) for i in range(4)]\n\n        B = inverse_2x2_block_diagonal(*a)\n\n        A = sp.vstack(\n            (\n                sp.hstack((sdiag(a[0]), sdiag(a[1]))),\n                sp.hstack((sdiag(a[2]), sdiag(a[3]))),\n            )\n        )\n\n        Z2 = B * A - sp.identity(10)\n        self.assertTrue(np.linalg.norm(Z2.todense().ravel(), 2) < TOL)\n\n        a = [rng.random((5, 1)) for i in range(9)]\n        B = inverse_3x3_block_diagonal(*a)\n\n        A = sp.vstack(\n            (\n                sp.hstack((sdiag(a[0]), sdiag(a[1]), sdiag(a[2]))),\n                sp.hstack((sdiag(a[3]), sdiag(a[4]), sdiag(a[5]))),\n                sp.hstack((sdiag(a[6]), sdiag(a[7]), sdiag(a[8]))),\n            )\n        )\n\n        Z3 = B * A - sp.identity(15)\n\n        self.assertTrue(np.linalg.norm(Z3.todense().ravel(), 2) < TOL)\n\n    def test_inverse_property_tensor2D(self):\n        rng = np.random.default_rng(763)\n        M = discretize.TensorMesh([6, 6])\n        a1 = rng.random(M.nC)\n        a2 = rng.random(M.nC)\n        a3 = rng.random(M.nC)\n        prop1 = a1\n        prop2 = np.c_[a1, a2]\n        prop3 = np.c_[a1, a2, a3]\n\n        for prop in [4, prop1, prop2, prop3]:\n            b = inverse_property_tensor(M, prop)\n            A = make_property_tensor(M, prop)\n            B1 = make_property_tensor(M, b)\n            B2 = inverse_property_tensor(M, prop, return_matrix=True)\n\n            Z = B1 * A - sp.identity(M.nC * 2)\n            self.assertTrue(np.linalg.norm(Z.todense().ravel(), 2) < TOL)\n            Z = B2 * A - sp.identity(M.nC * 2)\n            self.assertTrue(np.linalg.norm(Z.todense().ravel(), 2) < TOL)\n\n    def test_TensorType2D(self):\n        rng = np.random.default_rng(8546)\n        M = discretize.TensorMesh([6, 6])\n        a1 = rng.random(M.nC)\n        a2 = rng.random(M.nC)\n        a3 = rng.random(M.nC)\n        prop1 = a1\n        prop2 = np.c_[a1, a2]\n        prop3 = np.c_[a1, a2, a3]\n\n        for ii, prop in enumerate([4, prop1, prop2, prop3]):\n            self.assertTrue(TensorType(M, prop) == ii)\n\n        self.assertRaises(Exception, TensorType, M, np.c_[a1, a2, a3, a3])\n        self.assertTrue(TensorType(M, None) == -1)\n\n    def test_TensorType3D(self):\n        rng = np.random.default_rng(78352)\n        M = discretize.TensorMesh([6, 6, 7])\n        a1 = rng.random(M.nC)\n        a2 = rng.random(M.nC)\n        a3 = rng.random(M.nC)\n        a4 = rng.random(M.nC)\n        a5 = rng.random(M.nC)\n        a6 = rng.random(M.nC)\n        prop1 = a1\n        prop2 = np.c_[a1, a2, a3]\n        prop3 = np.c_[a1, a2, a3, a4, a5, a6]\n\n        for ii, prop in enumerate([4, prop1, prop2, prop3]):\n            self.assertTrue(TensorType(M, prop) == ii)\n\n        self.assertRaises(Exception, TensorType, M, np.c_[a1, a2, a3, a3])\n        self.assertTrue(TensorType(M, None) == -1)\n\n    def test_inverse_property_tensor3D(self):\n        rng = np.random.default_rng(78352)\n        M = discretize.TensorMesh([6, 6, 6])\n        a1 = rng.random(M.nC)\n        a2 = rng.random(M.nC)\n        a3 = rng.random(M.nC)\n        a4 = rng.random(M.nC)\n        a5 = rng.random(M.nC)\n        a6 = rng.random(M.nC)\n        prop1 = a1\n        prop2 = np.c_[a1, a2, a3]\n        prop3 = np.c_[a1, a2, a3, a4, a5, a6]\n\n        for prop in [4, prop1, prop2, prop3]:\n            b = inverse_property_tensor(M, prop)\n            A = make_property_tensor(M, prop)\n            B1 = make_property_tensor(M, b)\n            B2 = inverse_property_tensor(M, prop, return_matrix=True)\n\n            Z = B1 * A - sp.identity(M.nC * 3)\n            self.assertTrue(np.linalg.norm(Z.todense().ravel(), 2) < TOL)\n            Z = B2 * A - sp.identity(M.nC * 3)\n            self.assertTrue(np.linalg.norm(Z.todense().ravel(), 2) < TOL)\n\n    def test_is_scalar(self):\n        self.assertTrue(is_scalar(1.0))\n        self.assertTrue(is_scalar(1))\n        self.assertTrue(is_scalar(1j))\n        self.assertTrue(is_scalar(np.array(1.0)))\n        self.assertTrue(is_scalar(np.array(1)))\n        self.assertTrue(is_scalar(np.array(1j)))\n        self.assertTrue(is_scalar(np.array([1.0])))\n        self.assertTrue(is_scalar(np.array([1])))\n        self.assertTrue(is_scalar(np.array([1j])))\n        self.assertTrue(is_scalar(np.array([[1.0]])))\n        self.assertTrue(is_scalar(np.array([[1]])))\n        self.assertTrue(is_scalar(np.array([[1j]])))\n\n    def test_as_array_n_by_dim(self):\n        true = np.array([[1, 2, 3]])\n\n        listArray = as_array_n_by_dim([1, 2, 3], 3)\n        self.assertTrue(np.all(true == listArray))\n        self.assertTrue(true.shape == listArray.shape)\n\n        listArray = as_array_n_by_dim(np.r_[1, 2, 3], 3)\n        self.assertTrue(np.all(true == listArray))\n        self.assertTrue(true.shape == listArray.shape)\n\n        listArray = as_array_n_by_dim(np.array([[1, 2, 3.0]]), 3)\n        self.assertTrue(np.all(true == listArray))\n        self.assertTrue(true.shape == listArray.shape)\n\n        true = np.array([[1, 2], [4, 5]])\n\n        listArray = as_array_n_by_dim([[1, 2], [4, 5]], 2)\n        self.assertTrue(np.all(true == listArray))\n        self.assertTrue(true.shape == listArray.shape)\n\n\nclass TestZero(unittest.TestCase):\n    def test_zero(self):\n        z = Zero()\n        assert not z\n        assert z == 0\n        assert not (z < 0)\n        assert z <= 0\n        assert not (z > 0)\n        assert z >= 0\n        assert +z == z\n        assert -z == z\n        assert z + 1 == 1\n        assert z + 3 + z == 3\n        assert z - 3 == -3\n        assert z - 3 - z == -3\n        assert 3 * z == 0\n        assert z * 3 == 0\n        assert z / 3 == 0\n\n        a = 1\n        a += z\n        assert a == 1\n        a = 1\n        a += z\n        assert a == 1\n        self.assertRaises(ZeroDivisionError, lambda: 3 / z)\n\n        assert mkvc(z) == 0\n        assert sdiag(z) * a == 0\n        assert z.T == 0\n        assert z.transpose() == 0\n\n    def test_mat_zero(self):\n        z = Zero()\n        S = sdiag(np.r_[2, 3])\n        assert S * z == 0\n\n    def test_numpy_multiply(self):\n        z = Zero()\n        x = np.r_[1, 2, 3]\n        a = x * z\n        assert isinstance(a, Zero)\n\n        z = Zero()\n        x = np.r_[1, 2, 3]\n        a = z * x\n        assert isinstance(a, Zero)\n\n    def test_one(self):\n        o = Identity()\n        assert o\n        assert o == 1\n        assert not (o < 1)\n        assert o <= 1\n        assert not (o > 1)\n        assert o >= 1\n        o = -o\n        assert o == -1\n        assert not (o < -1)\n        assert o <= -1\n        assert not (o > -1)\n        assert o >= -1\n        assert -1.0 * (-o) * o == -o\n        o = Identity()\n        assert +o == o\n        assert -o == -o\n        assert o * 3 == 3\n        assert -o * 3 == -3\n        assert -o * o == -1\n        assert -o * o * -o == 1\n        assert -o + 3 == 2\n        assert 3 + -o == 2\n\n        assert -o - 3 == -4\n        assert o - 3 == -2\n        assert 3 - -o == 4\n        assert 3 - o == 2\n\n        assert o // 2 == 0\n        assert o / 2.0 == 0.5\n        assert -o // 2 == -1\n        assert -o / 2.0 == -0.5\n        assert 2 / o == 2\n        assert 2 // -o == -2\n        assert 2.3 // o == 2\n        assert 2.3 // -o == -3\n\n        assert o.T == 1\n        assert o.transpose() == 1\n\n    def test_mat_one(self):\n        o = Identity()\n        S = sdiag(np.r_[2, 3])\n\n        def check(exp, ans):\n            assert np.all((exp).todense() == ans)\n\n        check(S * o, [[2, 0], [0, 3]])\n        check(o * S, [[2, 0], [0, 3]])\n        check(S * -o, [[-2, 0], [0, -3]])\n        check(-o * S, [[-2, 0], [0, -3]])\n        check(S / o, [[2, 0], [0, 3]])\n        check(S / -o, [[-2, 0], [0, -3]])\n        self.assertRaises(NotImplementedError, lambda: o / S)\n\n        check(S + o, [[3, 0], [0, 4]])\n        check(o + S, [[3, 0], [0, 4]])\n        check(S - o, [[1, 0], [0, 2]])\n\n        check(S + -o, [[1, 0], [0, 2]])\n        check(-o + S, [[1, 0], [0, 2]])\n\n    def test_mat_shape(self):\n        o = Identity()\n        S = sdiag(np.r_[2, 3])[:1, :]\n        self.assertRaises(ValueError, lambda: S + o)\n\n        def check(exp, ans):\n            assert np.all((exp).todense() == ans)\n\n        check(S * o, [[2, 0]])\n        check(S * -o, [[-2, 0]])\n\n    def test_numpy_one(self):\n        o = Identity()\n        n = np.r_[2.0, 3]\n\n        assert np.all(n + 1 == n + o)\n        assert np.all(1 + n == o + n)\n        assert np.all(n - 1 == n - o)\n        assert np.all(1 - n == o - n)\n        assert np.all(n / 1 == n / o)\n        assert np.all(n / -1 == n / -o)\n        assert np.all(1 / n == o / n)\n        assert np.all(-1 / n == -o / n)\n        assert np.all(n * 1 == n * o)\n        assert np.all(n * -1 == n * -o)\n        assert np.all(1 * n == o * n)\n        assert np.all(-1 * n == -o * n)\n\n    def test_both(self):\n        z = Zero()\n        o = Identity()\n        assert z or o\n        assert not (z and o)\n        assert o and not z\n        assert o * z == 0\n        assert o * z + o == 1\n        assert o - z == 1\n\n\nclass TestMeshUtils(unittest.TestCase):\n    def test_extract_core_mesh(self):\n        # 1D Test on TensorMesh\n        meshtest1d = discretize.TensorMesh([[(50.0, 10)]])\n        xzlim1d = np.r_[[[0.0, 250.0]]]\n        actind1d, meshCore1d = extract_core_mesh(xzlim1d, meshtest1d)\n\n        self.assertEqual(len(actind1d), meshtest1d.nC)\n        self.assertEqual(meshCore1d.nC, np.count_nonzero(actind1d))\n        self.assertGreater(meshCore1d.cell_centers_x.min(), xzlim1d[0, :].min())\n        self.assertLess(meshCore1d.cell_centers_x.max(), xzlim1d[0, :].max())\n\n        # 2D Test on TensorMesh\n        meshtest2d = discretize.TensorMesh([[(50.0, 10)], [(25.0, 10)]])\n        xzlim2d = np.r_[[[0.0, 200.0], [0.0, 200.0]]]\n        actind2d, meshCore2d = extract_core_mesh(xzlim2d, meshtest2d)\n\n        self.assertEqual(len(actind2d), meshtest2d.nC)\n        self.assertEqual(meshCore2d.nC, np.count_nonzero(actind2d))\n        self.assertGreater(meshCore2d.cell_centers_x.min(), xzlim2d[0, :].min())\n        self.assertLess(meshCore2d.cell_centers_x.max(), xzlim2d[0, :].max())\n        self.assertGreater(meshCore2d.cell_centers_y.min(), xzlim2d[1, :].min())\n        self.assertLess(meshCore2d.cell_centers_y.max(), xzlim2d[1, :].max())\n\n        # 3D Test on TensorMesh\n        meshtest3d = discretize.TensorMesh([[(50.0, 10)], [(25.0, 10)], [(5.0, 40)]])\n        xzlim3d = np.r_[[[0.0, 250.0], [0.0, 200.0], [0.0, 150]]]\n        actind3d, meshCore3d = extract_core_mesh(xzlim3d, meshtest3d)\n\n        self.assertEqual(len(actind3d), meshtest3d.nC)\n        self.assertEqual(meshCore3d.nC, np.count_nonzero(actind3d))\n        self.assertGreater(meshCore3d.cell_centers_x.min(), xzlim3d[0, :].min())\n        self.assertLess(meshCore3d.cell_centers_x.max(), xzlim3d[0, :].max())\n        self.assertGreater(meshCore3d.cell_centers_y.min(), xzlim3d[1, :].min())\n        self.assertLess(meshCore3d.cell_centers_y.max(), xzlim3d[1, :].max())\n        self.assertGreater(meshCore3d.cell_centers_z.min(), xzlim3d[2, :].min())\n        self.assertLess(meshCore3d.cell_centers_z.max(), xzlim3d[2, :].max())\n\n    def test_active_from_xyz(self):\n        # Create 3D topo\n        [xx, yy] = np.meshgrid(np.linspace(-200, 200, 50), np.linspace(-200, 200, 50))\n        b = 50\n        A = 50\n        zz = A * np.exp(-0.5 * ((xx / b) ** 2.0 + (yy / b) ** 2.0))\n\n        h = [5.0, 5.0, 5.0]\n\n        # Test 1D Mesh\n        topo1D = zz[25, :].ravel()\n        mesh1D = discretize.TensorMesh([np.ones(10) * 20], x0=\"C\")\n\n        indtopoCC = active_from_xyz(\n            mesh1D, topo1D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(mesh1D, topo1D, grid_reference=\"N\", method=\"nearest\")\n\n        self.assertEqual(indtopoCC.sum(), 3)\n        self.assertEqual(indtopoN.sum(), 2)\n\n        # Test 2D Tensor mesh\n        topo2D = np.c_[xx[25, :].ravel(), zz[25, :].ravel()]\n\n        mesh_tensor = discretize.TensorMesh([[(h[0], 24)], [(h[1], 20)]], x0=\"CC\")\n\n        indtopoCC = active_from_xyz(\n            mesh_tensor, topo2D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(\n            mesh_tensor, topo2D, grid_reference=\"N\", method=\"nearest\"\n        )\n\n        self.assertEqual(indtopoCC.sum(), 434)\n        self.assertEqual(indtopoN.sum(), 412)\n\n        # test 2D Curvilinear mesh\n        nodes_x = mesh_tensor.gridN[:, 0].reshape(mesh_tensor.vnN, order=\"F\")\n        nodes_y = mesh_tensor.gridN[:, 1].reshape(mesh_tensor.vnN, order=\"F\")\n        mesh_curvi = discretize.CurvilinearMesh([nodes_x, nodes_y])\n\n        indtopoCC = active_from_xyz(\n            mesh_curvi, topo2D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(\n            mesh_curvi, topo2D, grid_reference=\"N\", method=\"nearest\"\n        )\n\n        self.assertEqual(indtopoCC.sum(), 434)\n        self.assertEqual(indtopoN.sum(), 412)\n\n        # Test 2D Tree mesh\n        mesh_tree = mesh_builder_xyz(topo2D, h[:2], mesh_type=\"TREE\")\n        mesh_tree = refine_tree_xyz(\n            mesh_tree,\n            topo2D,\n            method=\"surface\",\n            octree_levels=[1],\n            octree_levels_padding=None,\n            finalize=True,\n        )\n        indtopoCC = active_from_xyz(\n            mesh_tree, topo2D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(\n            mesh_tree, topo2D, grid_reference=\"N\", method=\"nearest\"\n        )\n\n        self.assertEqual(indtopoCC.sum(), 167)\n        self.assertEqual(indtopoN.sum(), 119)\n\n        # Test 3D Tensor meshes\n        topo3D = np.c_[xx.ravel(), yy.ravel(), zz.ravel()]\n\n        mesh_tensor = discretize.TensorMesh(\n            [[(h[0], 24)], [(h[1], 20)], [(h[2], 30)]], x0=\"CCC\"\n        )\n\n        indtopoCC = active_from_xyz(\n            mesh_tensor, topo3D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(\n            mesh_tensor, topo3D, grid_reference=\"N\", method=\"nearest\"\n        )\n\n        self.assertEqual(indtopoCC.sum(), 10496)\n        self.assertEqual(indtopoN.sum(), 10084)\n\n        # test 3D Curvilinear mesh\n        nodes_x = mesh_tensor.gridN[:, 0].reshape(mesh_tensor.vnN, order=\"F\")\n        nodes_y = mesh_tensor.gridN[:, 1].reshape(mesh_tensor.vnN, order=\"F\")\n        nodes_z = mesh_tensor.gridN[:, 2].reshape(mesh_tensor.vnN, order=\"F\")\n        mesh_curvi = discretize.CurvilinearMesh([nodes_x, nodes_y, nodes_z])\n\n        indtopoCC = active_from_xyz(\n            mesh_curvi, topo3D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(\n            mesh_curvi, topo3D, grid_reference=\"N\", method=\"nearest\"\n        )\n\n        self.assertEqual(indtopoCC.sum(), 10496)\n        self.assertEqual(indtopoN.sum(), 10084)\n\n        # Test 3D Tree mesh\n        mesh_tree = mesh_builder_xyz(topo3D, h, mesh_type=\"TREE\")\n        mesh_tree = refine_tree_xyz(\n            mesh_tree,\n            topo3D,\n            method=\"surface\",\n            octree_levels=[1],\n            octree_levels_padding=None,\n            finalize=True,\n        )\n        indtopoCC = active_from_xyz(\n            mesh_tree, topo3D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(\n            mesh_tree, topo3D, grid_reference=\"N\", method=\"nearest\"\n        )\n\n        self.assertIn(indtopoCC.sum(), [6285, 6292, 6299])\n        self.assertIn(indtopoN.sum(), [4625, 4632, 4639])\n\n        # Test 3D CYL Mesh\n        ncr = 10  # number of mesh cells in r\n        ncz = 15  # number of mesh cells in z\n        dr = 15  # cell width r\n        dz = 10  # cell width z\n        npad_r = 4  # number of padding cells in r\n        npad_z = 4  # number of padding cells in z\n        exp_r = 1.25  # expansion rate of padding cells in r\n        exp_z = 1.25  # expansion rate of padding cells in z\n\n        hr = [(dr, ncr), (dr, npad_r, exp_r)]\n        hz = [(dz, npad_z, -exp_z), (dz, ncz), (dz, npad_z, exp_z)]\n\n        # A value of 1 is used to define the discretization in phi for this case.\n        mesh_cyl = discretize.CylindricalMesh([hr, 1, hz], x0=\"00C\")\n\n        indtopoCC = active_from_xyz(\n            mesh_cyl, topo3D, grid_reference=\"CC\", method=\"nearest\"\n        )\n        indtopoN = active_from_xyz(\n            mesh_cyl, topo3D, grid_reference=\"N\", method=\"nearest\"\n        )\n\n        self.assertEqual(indtopoCC.sum(), 183)\n        self.assertEqual(indtopoN.sum(), 171)\n\n        htheta = unpack_widths([(1.0, 4)])\n        htheta = htheta * 2 * np.pi / htheta.sum()\n\n        mesh_cyl2 = discretize.CylindricalMesh([hr, htheta, hz], x0=\"00C\")\n        with self.assertRaises(NotImplementedError):\n            indtopoCC = active_from_xyz(\n                mesh_cyl2, topo3D, grid_reference=\"CC\", method=\"nearest\"\n            )\n\n\ndef test_cross2d():\n    x = np.linspace(3, 4, 20).reshape(10, 2)\n    y = np.linspace(1, 2, 20).reshape(10, 2)\n\n    x_boost = np.c_[x, np.zeros(10)]\n    y_boost = np.c_[y, np.zeros(10)]\n\n    np.testing.assert_allclose(np.cross(x_boost, y_boost)[:, -1], cross2d(x, y))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_view.py",
    "content": "import unittest\nimport numpy as np\nimport pytest\n\ntry:\n    import matplotlib.pyplot as plt\nexcept ImportError:\n    pytest.skip(\n        \"Skipping CylindricalMesh tests due to no matplotlib\", allow_module_level=True\n    )\nimport discretize\n\nimport pytest\n\nTOL = 1e-1\n\n\nclass Cyl3DView(unittest.TestCase):\n    def setUp(self):\n        self.mesh = discretize.CylindricalMesh([10, 4, 12])\n\n    def test_incorrectAxesWarnings(self):\n        # axes aren't polar\n        fig, ax = plt.subplots(1, 1)\n        # test z-slice\n        with pytest.warns(UserWarning):\n            self.mesh.plot_grid(slice=\"z\", ax=ax)\n\n        # axes aren't right shape\n        with pytest.warns(UserWarning):\n            self.mesh.plot_grid(slice=\"both\", ax=ax)\n            self.mesh.plot_grid(ax=ax)\n\n        # this should be fine\n        self.mesh.plot_grid(slice=\"theta\", ax=ax)\n\n        fig, ax = plt.subplots(2, 1)\n        # axes are right shape, but not polar\n        with pytest.warns(UserWarning):\n            self.mesh.plot_grid(slice=\"both\", ax=ax)\n            self.mesh.plot_grid(ax=ax)\n\n        # these should be fine\n        self.mesh.plot_grid()\n\n        ax0 = plt.subplot(121, projection=\"polar\")\n        ax1 = plt.subplot(122)\n\n        self.mesh.plot_grid(slice=\"z\", ax=ax0)  # plot z only\n        self.mesh.plot_grid(slice=\"theta\", ax=ax1)  # plot theta only\n        self.mesh.plot_grid(slice=\"both\", ax=[ax0, ax1])  # plot both\n        self.mesh.plot_grid(slice=\"both\", ax=[ax1, ax0])  # plot both\n        self.mesh.plot_grid(ax=[ax1, ax0])  # plot both\n        plt.close(\"all\")\n\n    def test_plot_image(self):\n        with self.assertRaises(NotImplementedError):\n            self.mesh.plot_image(np.empty(self.mesh.nC))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/base/test_volume_avg.py",
    "content": "import numpy as np\nimport pytest\nimport discretize\nfrom discretize.utils import volume_average\nfrom numpy.testing import assert_allclose\n\n\ndef generate_mesh(dim, mesh_type, tree_point=None, sub_mesh=False, seed=0):\n    if seed is None:\n        h = np.ones(16)\n    else:\n        rng = np.random.default_rng(seed)\n        h = rng.random(16)\n        h /= h.sum()\n    if sub_mesh:\n        h = h[:8]\n        if tree_point is not None:\n            tree_point = tree_point * 0.5\n    hs = [h] * dim\n    mesh = mesh_type(hs)\n    if isinstance(mesh, discretize.TreeMesh):\n        mesh.insert_cells(tree_point, -1)\n    return mesh\n\n\n@pytest.mark.parametrize(\"same_base\", [True, False])\n@pytest.mark.parametrize(\"sub_mesh\", [True, False])\n@pytest.mark.parametrize(\n    \"dim, mesh1_type, mesh2_type\",\n    [\n        (1, discretize.TensorMesh, discretize.TensorMesh),\n        (2, discretize.TensorMesh, discretize.TensorMesh),\n        (3, discretize.TensorMesh, discretize.TensorMesh),\n        (2, discretize.TreeMesh, discretize.TensorMesh),\n        (3, discretize.TreeMesh, discretize.TensorMesh),\n        (2, discretize.TensorMesh, discretize.TreeMesh),\n        (3, discretize.TensorMesh, discretize.TreeMesh),\n        (2, discretize.TreeMesh, discretize.TreeMesh),\n        (3, discretize.TreeMesh, discretize.TreeMesh),\n    ],\n)\ndef test_volume_average(dim, mesh1_type, mesh2_type, same_base, sub_mesh, seed=102):\n    if dim == 1:\n        if mesh1_type is discretize.TreeMesh or mesh2_type is discretize.TreeMesh:\n            pytest.skip(\"TreeMesh only in 2D or higher\")\n\n    p1 = p2 = None\n    if mesh1_type is discretize.TreeMesh:\n        p1 = np.asarray([0.25] * dim)\n    if mesh2_type is discretize.TreeMesh:\n        p2 = np.asarray([0.75] * dim)\n\n    rng = np.random.default_rng(seed)\n\n    if not sub_mesh:\n        seed1, seed2 = rng.integers(554, size=(2,))\n        if same_base:\n            seed2 = seed1\n    else:\n        seed1 = seed2 = None\n\n    mesh1 = generate_mesh(dim, mesh1_type, tree_point=p1, seed=seed1)\n    mesh2 = generate_mesh(dim, mesh2_type, tree_point=p2, sub_mesh=sub_mesh, seed=seed2)\n\n    model_in = rng.random(mesh1.nC)\n    model_out1 = np.empty(mesh2.nC)\n\n    # test the three ways of calling...\n\n    # providing an output array\n    out1 = volume_average(mesh1, mesh2, model_in, model_out1)\n    # assert_array_equal(out1, model_out1)\n    assert out1 is model_out1\n\n    # only providing input array\n    out2 = volume_average(mesh1, mesh2, model_in)\n    assert_allclose(out1, out2)\n\n    # not providing either (which constructs a sparse matrix representing the operation)\n    Av = volume_average(mesh1, mesh2)\n    out3 = Av @ model_in\n    assert_allclose(out1, out3)\n\n    # test for mass conserving properties:\n    if sub_mesh:\n        # get cells in extent of smaller mesh\n        cells = mesh1.cell_centers < 8\n        if dim > 1:\n            cells = np.all(cells, axis=1)\n\n        mass1 = np.sum(mesh1.cell_volumes[cells] * model_in[cells])\n    else:\n        mass1 = np.sum(mesh1.cell_volumes * model_in)\n    mass2 = np.sum(mesh2.cell_volumes * out3)\n    assert_allclose(mass1, mass2)\n\n\ndef test_errors():\n    h1 = np.ones(16) + np.arange(16)\n    h1 /= h1.sum()\n    h2 = np.ones(16) + 2 * np.arange(16)\n    h2 /= h2.sum()\n    mesh1D = discretize.TensorMesh([h1])\n    mesh2D = discretize.TensorMesh([h1, h1])\n    mesh3D = discretize.TensorMesh([h1, h1, h1])\n\n    hr = np.r_[1, 1, 0.5]\n    hz = np.r_[2, 1]\n    meshCyl = discretize.CylindricalMesh([hr, 1, hz], np.r_[0.0, 0.0, 0.0])\n    mesh2 = discretize.TreeMesh([h2, h2])\n    mesh2.insert_cells([0.75, 0.75], [4])\n\n    with pytest.raises(TypeError):\n        # Gives a wrong typed object to the function\n        volume_average(mesh1D, h1)\n    with pytest.raises(NotImplementedError):\n        # Gives a wrong typed mesh\n        volume_average(meshCyl, mesh2)\n    with pytest.raises(ValueError):\n        # Gives mismatching mesh dimensions\n        volume_average(mesh2D, mesh3D)\n\n    model1 = np.random.randn(mesh2D.nC)\n    bad_model1 = np.array([-1, 2, 3])\n    bad_model2 = np.array([1])\n    # gives input values with incorrect lengths\n    with pytest.raises(ValueError):\n        volume_average(mesh2D, mesh2, bad_model1)\n    with pytest.raises(ValueError):\n        volume_average(mesh2D, mesh2, model1, bad_model2)\n"
  },
  {
    "path": "tests/boundaries/test_boundary_integrals.py",
    "content": "import numpy as np\nimport scipy.sparse as sp\nimport discretize\nfrom discretize.utils import cart2cyl, cyl2cart\n\n\ndef u(*args):\n    if len(args) == 1:\n        x = args[0]\n        return x**3\n    if len(args) == 2:\n        x, y = args\n        return x**3 + y**2\n    x, y, z = args\n    return x**3 + y**2 + z**4\n\n\ndef u_cyl(*args):\n    xyz = cyl2cart(np.stack(args, axis=-1))\n    return u(*xyz.T)\n\n\ndef v(*args):\n    if len(args) == 1:\n        x = args[0]\n        return 2 * x**2\n    if len(args) == 2:\n        x, y = args\n        return np.c_[2 * x**2, 3 * y**3]\n    x, y, z = args\n    return np.c_[2 * x**2, 3 * y**3, -4 * z**2]\n\n\ndef v_cyl(*args):\n    xyz = cyl2cart(np.stack(args, axis=-1))\n    xyz_vec = v(*xyz.T)\n    return cart2cyl(xyz, xyz_vec)\n\n\ndef w(*args):\n    if len(args) == 2:\n        x, y = args\n        return np.c_[(y - 2) ** 2, (x + 2) ** 2]\n    x, y, z = args\n    return np.c_[(y - 2) ** 2 + z**2, (x + 2) ** 2 - (z - 4) ** 2, y**2 - x**2]\n\n\ndef w_cyl(*args):\n    xyz = cyl2cart(np.stack(args, axis=-1))\n    xyz_vec = w(*xyz.T)\n    return cart2cyl(xyz, xyz_vec)\n\n\n# mesh will be on [0, 1] square\n\n# 1D\n# int_V grad_u dot v dV = 6/5\n# int_V u dot div v dV = 4/5\n\n# 2D\n# square vals:\n# int_V grad_u dot v dV = 12/5\n# int_V u div_v dV = 241/60\n# int_v curl_w dot v dV = -173/30\n\n# circle vals:\n# int_V grad_u dot  dV = 3*np.pi/2\n# int_V u div_v dV = 13*np.pi/8\n# int_v curl_w dot v dV = -43*np.pi/8\n\n# 3D square vals:\n\n# int_V grad_u dot v dV = -4/15\n# int_V u div_v dV = 27/20\n# int_v curl_w dot v dV = 17/6\n\n# 3D Cylindrical Values\n# int_V grad_u dot v dV = -7 * np.pi/6\n# int_V u div_v dV = -31 * np.pi/120\n# int_v curl_w dot v dV = -85 * np.pi/6\n\n\nclass Test1DBoundaryIntegral(discretize.tests.OrderTest):\n    name = \"1D Boundary Integrals\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        mesh = self.M\n        if self.myTest == \"cell_grad\":\n            u_cc = u(mesh.cell_centers)\n            v_f = v(mesh.nodes)\n            u_bf = u(mesh.boundary_faces)\n\n            D = mesh.face_divergence\n            M_c = sp.diags(mesh.cell_volumes)\n            M_bf = mesh.boundary_face_scalar_integral\n\n            discrete_val = -(v_f.T @ D.T) @ M_c @ u_cc + v_f.T @ (M_bf @ u_bf)\n            true_val = 6 / 5\n        if self.myTest == \"edge_div\":\n            u_n = u(mesh.nodes)\n            v_e = v(mesh.edges)\n            v_bn = v(mesh.boundary_nodes).reshape(-1, order=\"F\")\n\n            M_e = mesh.get_edge_inner_product()\n            G = mesh.nodal_gradient\n            M_bn = mesh.boundary_node_vector_integral\n\n            discrete_val = -(u_n.T @ G.T) @ M_e @ v_e + u_n.T @ (M_bn @ v_bn)\n            true_val = 4 / 5\n        return np.abs(discrete_val - true_val)\n\n    def test_orderWeakCellGradIntegral(self):\n        self.name = \"1D - weak cell gradient integral w/boundary\"\n        self.myTest = \"cell_grad\"\n        self.orderTest()\n\n    def test_orderWeakEdgeDivIntegral(self):\n        self.name = \"1D - weak edge divergence integral w/boundary\"\n        self.myTest = \"edge_div\"\n        self.orderTest()\n\n\nclass Test2DBoundaryIntegral(discretize.tests.OrderTest):\n    name = \"2D Boundary Integrals\"\n    meshTypes = [\n        \"uniformTensorMesh\",\n        \"uniformTree\",\n        \"uniformCurv\",\n        \"rotateCurv\",\n        \"sphereCurv\",\n    ]\n    meshDimension = 2\n    expectedOrders = [2, 2, 2, 2, 1]\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        mesh = self.M\n        if self.myTest == \"cell_grad\":\n            # Functions:\n            u_cc = u(*mesh.cell_centers.T)\n            v_f = mesh.project_face_vector(v(*mesh.faces.T))\n            u_bf = u(*mesh.boundary_faces.T)\n\n            D = mesh.face_divergence\n            M_c = sp.diags(mesh.cell_volumes)\n            M_bf = mesh.boundary_face_scalar_integral\n\n            discrete_val = -(v_f.T @ D.T) @ M_c @ u_cc + v_f.T @ (M_bf @ u_bf)\n            if \"sphere\" not in self._meshType:\n                true_val = 12 / 5\n            else:\n                true_val = 3 * np.pi / 2\n        elif self.myTest == \"edge_div\":\n            u_n = u(*mesh.nodes.T)\n            v_e = mesh.project_edge_vector(v(*mesh.edges.T))\n            v_bn = v(*mesh.boundary_nodes.T).reshape(-1, order=\"F\")\n\n            M_e = mesh.get_edge_inner_product()\n            G = mesh.nodal_gradient\n            M_bn = mesh.boundary_node_vector_integral\n\n            discrete_val = -(u_n.T @ G.T) @ M_e @ v_e + u_n.T @ (M_bn @ v_bn)\n            if \"sphere\" not in self._meshType:\n                true_val = 241 / 60\n            else:\n                true_val = 13 * np.pi / 8\n        elif self.myTest == \"face_curl\":\n            w_e = mesh.project_edge_vector(w(*mesh.edges.T))\n            u_c = u(*mesh.cell_centers.T)\n            u_be = u(*mesh.boundary_edges.T)\n\n            M_c = sp.diags(mesh.cell_volumes)\n            Curl = mesh.edge_curl\n            M_be = mesh.boundary_edge_vector_integral\n\n            discrete_val = (w_e.T @ Curl.T) @ M_c @ u_c - w_e.T @ (M_be @ u_be)\n            if \"Curv\" in self._meshType:\n                self._expectedOrder = -1.0\n            if \"sphere\" not in self._meshType:\n                true_val = -173 / 30\n            else:\n                true_val = -43 * np.pi / 8\n\n        return np.abs(discrete_val - true_val)\n\n    def test_orderWeakCellGradIntegral(self):\n        self.name = \"2D - weak cell gradient integral w/boundary\"\n        self.myTest = \"cell_grad\"\n        self.orderTest()\n\n    def test_orderWeakEdgeDivIntegral(self):\n        self.name = \"2D - weak edge divergence integral w/boundary\"\n        self.myTest = \"edge_div\"\n        self.orderTest()\n\n    def test_orderWeakFaceCurlIntegral(self):\n        self.name = \"2D - weak face curl integral w/boundary\"\n        self.myTest = \"face_curl\"\n        self.orderTest()\n\n\nclass Test3DBoundaryIntegral(discretize.tests.OrderTest):\n    name = \"3D Boundary Integrals\"\n    meshTypes = [\n        \"uniformTensorMesh\",\n        \"randomTensorMesh\",\n        \"uniformTree\",\n        \"uniformCurv\",\n        \"rotateCurv\",\n        \"sphereCurv\",\n    ]\n    meshDimension = 3\n    expectedOrders = [2, 1, 2, 2, 2, 2]\n    meshSizes = [4, 8, 16, 32]\n\n    def getError(self):\n        mesh = self.M\n        if self.myTest == \"cell_grad\":\n            # Functions:\n            u_cc = u(*mesh.cell_centers.T)\n            v_f = mesh.project_face_vector(v(*mesh.faces.T))\n            u_bf = u(*mesh.boundary_faces.T)\n\n            D = mesh.face_divergence\n            M_c = sp.diags(mesh.cell_volumes)\n            M_bf = mesh.boundary_face_scalar_integral\n\n            discrete_val = -(v_f.T @ D.T) @ M_c @ u_cc + v_f.T @ (M_bf @ u_bf)\n            if \"sphere\" not in self._meshType:\n                true_val = -4 / 15\n            else:\n                true_val = 48 * np.pi / 35\n        elif self.myTest == \"edge_div\":\n            u_n = u(*mesh.nodes.T)\n            v_e = mesh.project_edge_vector(v(*mesh.edges.T))\n            v_bn = v(*mesh.boundary_nodes.T).reshape(-1, order=\"F\")\n\n            M_e = mesh.get_edge_inner_product()\n            G = mesh.nodal_gradient\n            M_bn = mesh.boundary_node_vector_integral\n\n            discrete_val = -(u_n.T @ G.T) @ M_e @ v_e + u_n.T @ (M_bn @ v_bn)\n            if \"sphere\" not in self._meshType:\n                true_val = 27 / 20\n            else:\n                true_val = 8 * np.pi / 5\n        elif self.myTest == \"face_curl\":\n            w_f = mesh.project_face_vector(w(*mesh.faces.T))\n            v_e = mesh.project_edge_vector(v(*mesh.edges.T))\n            w_be = w(*mesh.boundary_edges.T).reshape(-1, order=\"F\")\n\n            M_f = mesh.get_face_inner_product()\n            Curl = mesh.edge_curl\n            M_be = mesh.boundary_edge_vector_integral\n\n            discrete_val = (v_e.T @ Curl.T) @ M_f @ w_f - v_e.T @ (M_be @ w_be)\n            if \"sphere\" not in self._meshType:\n                true_val = -79 / 6\n            else:\n                true_val = -64 * np.pi / 5\n\n        return np.abs(discrete_val - true_val)\n\n    def test_orderWeakCellGradIntegral(self):\n        self.name = \"3D - weak cell gradient integral w/boundary\"\n        self.myTest = \"cell_grad\"\n        self.orderTest(random_seed=51235)\n\n    def test_orderWeakEdgeDivIntegral(self):\n        self.name = \"3D - weak edge divergence integral w/boundary\"\n        self.myTest = \"edge_div\"\n        self.orderTest(random_seed=51123)\n\n    def test_orderWeakFaceCurlIntegral(self):\n        self.name = \"3D - weak face curl integral w/boundary\"\n        self.myTest = \"face_curl\"\n        self.orderTest(random_seed=5522)\n"
  },
  {
    "path": "tests/boundaries/test_boundary_maxwell.py",
    "content": "import numpy as np\nimport discretize\nfrom discretize import utils\nfrom scipy.sparse.linalg import spsolve\n\n\nclass TestFz2D_InhomogeneousDirichlet(discretize.tests.OrderTest):\n    name = \"2D - Dirichlet\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 2\n    expectedOrders = [2, 2, 1]\n    meshSizes = [4, 8, 16, 32, 64]\n\n    def getError(self):\n        # Test function\n        # PDE: Curl(Curl Ez) + Ez = q\n        # faces_z are cell_centers on 2D mesh\n        ez_fun = lambda x: np.cos(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n        q_fun = lambda x: (1 + 2 * np.pi**2) * ez_fun(x)\n\n        mesh = self.M\n        ez_ana = ez_fun(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n\n        ez_bc = ez_fun(mesh.boundary_edges)\n\n        MeI = mesh.get_edge_inner_product(invert_matrix=True)\n        M_be = mesh.boundary_edge_vector_integral\n\n        V = utils.sdiag(mesh.cell_volumes)\n        C = mesh.edge_curl\n\n        A = V @ C @ MeI @ C.T @ V + V\n        rhs = V @ q_ana + V @ C @ MeI @ M_be @ ez_bc\n\n        ez_test = spsolve(A, rhs)\n        if self._meshType == \"rotateCurv\":\n            err = np.linalg.norm(mesh.cell_volumes * (ez_test - ez_ana))\n        else:\n            err = np.linalg.norm(ez_test - ez_ana) / np.sqrt(mesh.n_cells)\n\n        return err\n\n    def test_orderX(self):\n        self.name = \"2D - InhomogeneousDirichlet_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass TestE3D_Inhomogeneous(discretize.tests.OrderTest):\n    name = \"3D - Maxwell\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 3\n    expectedOrders = [2, 2, 1]\n    meshSizes = [4, 8, 16]\n\n    def getError(self):\n        # Test function\n        # PDE: Curl(Curl E) + E = q\n        def e_fun(x):\n            x = np.cos(x)\n            cx, cy, cz = x[:, 0], x[:, 1], x[:, 2]\n            return np.c_[cy * cz, cx * cz, cx * cy]\n\n        def h_fun(x):\n            cx, cy, cz = np.cos(x[:, 0]), np.cos(x[:, 1]), np.cos(x[:, 2])\n            sx, sy, sz = np.sin(x[:, 0]), np.sin(x[:, 1]), np.sin(x[:, 2])\n            return np.c_[(-sy + sz) * cx, (sx - sz) * cy, (-sx + sy) * cz]\n\n        def q_fun(x):\n            return 3 * e_fun(x)\n\n        mesh = self.M\n        C = mesh.edge_curl\n\n        if \"Face\" in self.myTest:\n            e_ana = mesh.project_face_vector(e_fun(mesh.faces))\n            q_ana = mesh.project_face_vector(q_fun(mesh.faces))\n\n            e_bc = e_fun(mesh.boundary_edges).reshape(-1, order=\"F\")\n\n            MeI = mesh.get_edge_inner_product(invert_matrix=True)\n            M_be = mesh.boundary_edge_vector_integral\n\n            Mf = mesh.get_face_inner_product()\n\n            A = Mf @ C @ MeI @ C.T @ Mf + Mf\n            rhs = Mf @ q_ana + Mf @ C @ MeI @ M_be @ e_bc\n        elif \"Edge\" in self.myTest:\n            e_ana = mesh.project_edge_vector(e_fun(mesh.edges))\n            q_ana = mesh.project_edge_vector(q_fun(mesh.edges))\n\n            h_bc = h_fun(mesh.boundary_edges).reshape(-1, order=\"F\")\n\n            Mf = mesh.get_face_inner_product()\n            Me = mesh.get_edge_inner_product()\n            M_be = mesh.boundary_edge_vector_integral\n\n            A = C.T @ Mf @ C + Me\n            rhs = Me @ q_ana + M_be * h_bc\n\n        e_test = spsolve(A, rhs)\n\n        diff = e_test - e_ana\n        if \"Face\" in self.myTest:\n            if \"Curv\" in self._meshType or \"Tree\" in self._meshType:\n                err = np.linalg.norm(Mf * diff)\n            else:\n                err = np.linalg.norm(e_test - e_ana) / np.sqrt(mesh.n_faces)\n        elif \"Edge\" in self.myTest:\n            if \"Curv\" in self._meshType or \"Tree\" in self._meshType:\n                err = np.linalg.norm(Me * diff)\n            else:\n                err = np.linalg.norm(e_test - e_ana) / np.sqrt(mesh.n_edges)\n        return err\n\n    def test_orderFace(self):\n        self.name = \"3D - InhomogeneousDirichlet_Inverse\"\n        self.myTest = \"Face - Dirichlet\"\n        self.orderTest()\n\n    def test_orderNuemann(self):\n        self.name = \"3D - InhomogeneousDirichlet_Inverse\"\n        self.myTest = \"Edge - Nuemann\"\n        self.orderTest()\n"
  },
  {
    "path": "tests/boundaries/test_boundary_poisson.py",
    "content": "import numpy as np\nimport scipy.sparse as sp\nfrom scipy.sparse import linalg\nimport unittest\nimport discretize\nfrom discretize import utils\nfrom scipy.sparse.linalg import spsolve\n\n\nclass TestCC1D_InhomogeneousDirichlet(discretize.tests.OrderTest):\n    name = \"1D - Dirichlet\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.cos(np.pi * x)\n        q_fun = lambda x: -(np.pi**2) * np.cos(np.pi * x)\n\n        mesh = self.M\n\n        phi_ana = phi(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n\n        boundary_faces = mesh.boundary_faces\n\n        phi_bc = phi(boundary_faces)\n\n        MfI = mesh.get_face_inner_product(invert_matrix=True)\n        M_bf = mesh.boundary_face_scalar_integral\n\n        V = utils.sdiag(mesh.cell_volumes)\n        G = -mesh.face_divergence.T * V\n        D = mesh.face_divergence\n\n        # Sinc the xc_bc is a known, move it to the RHS!\n        A = V @ D @ MfI @ G\n        rhs = V @ q_ana - V @ D @ MfI @ M_bf @ phi_bc\n\n        phi_test = spsolve(A, rhs)\n        err = np.linalg.norm((phi_test - phi_ana)) / np.sqrt(mesh.n_cells)\n\n        return err\n\n    def test_orderX(self):\n        self.name = \"1D - InhomogeneousDirichlet_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass TestCC2D_InhomogeneousDirichlet(discretize.tests.OrderTest):\n    name = \"2D - Dirichlet\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 2\n    expectedOrders = [2, 2, 1]\n    meshSizes = [4, 8, 16, 32, 64]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.cos(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n        q_fun = lambda x: -2 * (np.pi**2) * phi(x)\n\n        mesh = self.M\n        phi_ana = phi(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n\n        phi_bc = phi(mesh.boundary_faces)\n\n        MfI = mesh.get_face_inner_product(invert_matrix=True)\n        M_bf = mesh.boundary_face_scalar_integral\n\n        V = utils.sdiag(mesh.cell_volumes)\n        G = -mesh.face_divergence.T * V\n        D = mesh.face_divergence\n\n        # Sinc the xc_bc is a known, move it to the RHS!\n        A = V @ D @ MfI @ G\n        rhs = V @ q_ana - V @ D @ MfI @ M_bf @ phi_bc\n\n        phi_test = spsolve(A, rhs)\n        if self._meshType == \"rotateCurv\":\n            err = np.linalg.norm(mesh.cell_volumes * (phi_test - phi_ana))\n        else:\n            err = np.linalg.norm(phi_test - phi_ana) / np.sqrt(mesh.n_cells)\n\n        return err\n\n    def test_orderX(self):\n        self.name = \"2D - InhomogeneousDirichlet_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass TestCC1D_InhomogeneousNeumann(discretize.tests.OrderTest):\n    name = \"1D - Neumann\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.sin(np.pi * x)\n        j_fun = lambda x: np.pi * np.cos(np.pi * x)\n        q_fun = lambda x: -(np.pi**2) * np.sin(np.pi * x)\n\n        mesh = self.M\n        xc_ana = phi(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n        j_ana = j_fun(mesh.faces_x)\n\n        phi_bc = phi(mesh.boundary_faces)\n        j_bc = j_fun(mesh.boundary_faces)\n\n        MfI = mesh.get_face_inner_product(invert_matrix=True)\n\n        V = utils.sdiag(mesh.cell_volumes)\n        G = mesh.face_divergence.T * V\n        D = mesh.face_divergence\n\n        # construct matrix with robin operator\n        alpha = 0.0\n        beta = 1.0\n        gamma = alpha * phi_bc + beta * j_bc * mesh.boundary_face_outward_normals\n        B_bc, b_bc = mesh.cell_gradient_weak_form_robin(\n            alpha=alpha, beta=beta, gamma=gamma\n        )\n\n        j = MfI @ ((-G + B_bc) @ xc_ana + b_bc)\n\n        # Since the xc_bc is a known, move it to the RHS!\n        A = V @ D @ MfI @ (-G + B_bc)\n        rhs = V @ q_ana - V @ D @ MfI @ b_bc\n\n        xc_disc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n\n        if self.myTest == \"j\":\n            err = np.linalg.norm((j - j_ana), np.inf)\n        elif self.myTest == \"xcJ\":\n            # TODO: fix the null space\n            xc, info = linalg.minres(A, rhs, rtol=1e-6)\n            j = MfI @ ((-G + B_bc) @ xc + b_bc)\n            err = np.linalg.norm((j - j_ana)) / np.sqrt(mesh.n_edges)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        return err\n\n    def test_orderJ(self):\n        self.name = \"1D - InhomogeneousNeumann_Forward j\"\n        self.myTest = \"j\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"1D - InhomogeneousNeumann_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nclass TestCC2D_InhomogeneousNeumann(discretize.tests.OrderTest):\n    name = \"2D - Neumann\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 2\n    expectedOrders = [2, 2, 1]\n    meshSizes = [4, 8, 16, 32]\n    # meshSizes = [4]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.sin(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funX = lambda x: np.pi * np.cos(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funY = lambda x: np.pi * np.sin(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n        q_fun = lambda x: -2 * (np.pi**2) * phi(x)\n\n        mesh = self.M\n        phi_ana = phi(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n\n        phi_bc = phi(mesh.boundary_faces)\n        jx_bc = j_funX(mesh.boundary_faces)\n        jy_bc = j_funY(mesh.boundary_faces)\n        j_bc = np.c_[jx_bc, jy_bc]\n\n        j_bc_dot_n = np.sum(j_bc * mesh.boundary_face_outward_normals, axis=-1)\n\n        MfI = mesh.get_face_inner_product(invert_matrix=True)\n\n        V = utils.sdiag(mesh.cell_volumes)\n        G = mesh.face_divergence.T * V\n        D = mesh.face_divergence\n\n        # construct matrix with robin operator\n        alpha = 0.0\n        beta = 1.0\n        gamma = alpha * phi_bc + beta * j_bc_dot_n\n\n        B_bc, b_bc = mesh.cell_gradient_weak_form_robin(\n            alpha=alpha, beta=beta, gamma=gamma\n        )\n\n        A = V @ D @ MfI @ (-G + B_bc)\n        rhs = V @ q_ana - V @ D @ MfI @ b_bc\n\n        phi_test, info = linalg.minres(A, rhs, rtol=1e-6)\n        phi_test -= phi_test.mean()\n        phi_ana -= phi_ana.mean()\n\n        # err = np.linalg.norm(phi_test - phi_ana)/np.sqrt(mesh.n_cells)\n\n        if self._meshType == \"rotateCurv\":\n            err = np.linalg.norm(mesh.cell_volumes * (phi_test - phi_ana))\n        else:\n            err = np.linalg.norm((phi_test - phi_ana), np.inf)\n\n        return err\n\n    def test_orderX(self):\n        self.name = \"2D - InhomogeneousNeumann_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass TestCC1D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"1D - Mixed\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.cos(0.5 * np.pi * x)\n        j_fun = lambda x: -0.5 * np.pi * np.sin(0.5 * np.pi * x)\n        q_fun = lambda x: -0.25 * (np.pi**2) * np.cos(0.5 * np.pi * x)\n\n        mesh = self.M\n        xc_ana = phi(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n        j_ana = j_fun(mesh.faces_x)\n\n        phi_bc = phi(mesh.boundary_faces)\n        j_bc = j_fun(mesh.boundary_faces)\n\n        MfI = mesh.get_face_inner_product(invert_matrix=True)\n\n        V = utils.sdiag(mesh.cell_volumes)\n        G = mesh.face_divergence.T * V\n        D = mesh.face_divergence\n\n        # construct matrix with robin operator\n        alpha = np.r_[1.0, 0.0]\n        beta = np.r_[0.0, 1.0]\n        gamma = alpha * phi_bc + beta * j_bc * mesh.boundary_face_outward_normals\n        B_bc, b_bc = mesh.cell_gradient_weak_form_robin(\n            alpha=alpha, beta=beta, gamma=gamma\n        )\n\n        A = V @ D @ MfI @ (-G + B_bc)\n        rhs = V @ q_ana - V @ D @ MfI @ b_bc\n\n        if self.myTest == \"xc\":\n            xc = spsolve(A, rhs)\n            err = np.linalg.norm(xc - xc_ana) / np.sqrt(mesh.n_cells)\n        elif self.myTest == \"xcJ\":\n            xc = spsolve(A, rhs)\n            j = MfI @ ((-G + B_bc) @ xc + b_bc)\n            err = np.linalg.norm(j - j_ana, np.inf)\n        return err\n\n    def test_orderX(self):\n        self.name = \"1D - InhomogeneousMixed_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"1D - InhomogeneousMixed_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nclass TestCC2D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"2D - Mixed\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 2\n    expectedOrders = [2, 2, 1]\n    meshSizes = [2, 4, 8, 16]\n    # meshSizes = [4]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.sin(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funX = lambda x: np.pi * np.cos(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funY = lambda x: np.pi * np.sin(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n        q_fun = lambda x: -2 * (np.pi**2) * phi(x)\n\n        mesh = self.M\n        phi_ana = phi(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n\n        phi_bc = phi(mesh.boundary_faces)\n        jx_bc = j_funX(mesh.boundary_faces)\n        jy_bc = j_funY(mesh.boundary_faces)\n        j_bc = np.c_[jx_bc, jy_bc]\n\n        j_bc_dot_n = np.sum(j_bc * mesh.boundary_face_outward_normals, axis=-1)\n\n        MfI = mesh.get_face_inner_product(invert_matrix=True)\n\n        V = utils.sdiag(mesh.cell_volumes)\n        G = mesh.face_divergence.T * V\n        D = mesh.face_divergence\n\n        # construct matrix with robin operator\n        # get indices of x0 boundary and y0 boundary\n        n_bounary_faces = len(j_bc_dot_n)\n        dirichlet_locs = np.any(mesh.boundary_faces == 0.0, axis=1)\n\n        alpha = np.zeros(n_bounary_faces)\n        alpha[dirichlet_locs] = 1.0\n\n        beta = np.zeros(n_bounary_faces)\n        beta[~dirichlet_locs] = 1.0\n\n        gamma = alpha * phi_bc + beta * j_bc_dot_n\n\n        B_bc, b_bc = mesh.cell_gradient_weak_form_robin(\n            alpha=alpha, beta=beta, gamma=gamma\n        )\n\n        A = V @ D @ MfI @ (-G + B_bc)\n        rhs = V @ q_ana - V @ D @ MfI @ b_bc\n\n        phi_test = spsolve(A, rhs)\n\n        if self._meshType == \"rotateCurv\":\n            err = np.linalg.norm(mesh.cell_volumes * (phi_test - phi_ana))\n        else:\n            err = np.linalg.norm((phi_test - phi_ana)) / np.sqrt(mesh.n_cells)\n\n        return err\n\n    def test_orderX(self):\n        self.name = \"2D - InhomogeneousMixed_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass TestCC3D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"3D - Mixed\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 3\n    expectedOrders = [2, 2, 2]\n    meshSizes = [2, 4, 8, 16]\n\n    def getError(self):\n        # Test function\n        phi = (\n            lambda x: np.sin(np.pi * x[:, 0])\n            * np.sin(np.pi * x[:, 1])\n            * np.sin(np.pi * x[:, 2])\n        )\n\n        j_funX = (\n            lambda x: np.pi\n            * np.cos(np.pi * x[:, 0])\n            * np.sin(np.pi * x[:, 1])\n            * np.sin(np.pi * x[:, 2])\n        )\n        j_funY = (\n            lambda x: np.pi\n            * np.sin(np.pi * x[:, 0])\n            * np.cos(np.pi * x[:, 1])\n            * np.sin(np.pi * x[:, 2])\n        )\n        j_funZ = (\n            lambda x: np.pi\n            * np.sin(np.pi * x[:, 0])\n            * np.sin(np.pi * x[:, 1])\n            * np.cos(np.pi * x[:, 2])\n        )\n\n        q_fun = lambda x: -3 * (np.pi**2) * phi(x)\n\n        mesh = self.M\n        phi_ana = phi(mesh.cell_centers)\n        q_ana = q_fun(mesh.cell_centers)\n\n        phi_bc = phi(mesh.boundary_faces)\n        jx_bc = j_funX(mesh.boundary_faces)\n        jy_bc = j_funY(mesh.boundary_faces)\n        jz_bc = j_funZ(mesh.boundary_faces)\n        j_bc = np.c_[jx_bc, jy_bc, jz_bc]\n\n        j_bc_dot_n = np.sum(j_bc * mesh.boundary_face_outward_normals, axis=-1)\n\n        MfI = mesh.get_face_inner_product(invert_matrix=True)\n\n        V = utils.sdiag(mesh.cell_volumes)\n        G = mesh.face_divergence.T * V\n        D = mesh.face_divergence\n\n        # construct matrix with robin operator\n        # get indices of x0 boundary, y0, and z0 boundary\n        n_bounary_faces = len(j_bc_dot_n)\n        dirichlet_locs = np.any(mesh.boundary_faces == 0.0, axis=1)\n\n        alpha = np.zeros(n_bounary_faces)\n        alpha[dirichlet_locs] = 1.0\n\n        beta = np.zeros(n_bounary_faces)\n        beta[~dirichlet_locs] = 1.0\n\n        gamma = alpha * phi_bc + beta * j_bc_dot_n\n\n        B_bc, b_bc = mesh.cell_gradient_weak_form_robin(\n            alpha=alpha, beta=beta, gamma=gamma\n        )\n\n        A = V @ D @ MfI @ (-G + B_bc)\n        rhs = V @ q_ana - V @ D @ MfI @ b_bc\n\n        phi_test = spsolve(A, rhs)\n\n        if self._meshType == \"rotateCurv\":\n            err = np.linalg.norm(mesh.cell_volumes * (phi_test - phi_ana))\n        else:\n            err = np.linalg.norm(phi_test - phi_ana) / np.sqrt(mesh.n_cells)\n        return err\n\n    def test_orderX(self):\n        self.name = \"3D - InhomogeneousMixed_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass TestN1D_boundaries(discretize.tests.OrderTest):\n    name = \"1D - Boundaries\"\n    meshTypes = [\"uniformTensorMesh\"]\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [2, 4, 8, 16, 32, 64, 128]\n    # meshSizes = [4]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.sin(np.pi * x)\n        j_fun = lambda x: np.pi * np.cos(np.pi * x)\n        q_fun = lambda x: -1 * (np.pi**2) * phi(x)\n\n        mesh = self.M\n        mesh.origin = [\n            -0.25,\n        ]\n\n        phi_ana = phi(mesh.nodes)\n        q_ana = q_fun(mesh.nodes)\n\n        phi_bc = phi(mesh.boundary_nodes)\n        j_bc = j_fun(mesh.boundary_nodes)\n\n        # construct matrix with robin operator\n        beta = 1.0\n        if self.boundary_type == \"Robin\":\n            alpha = 1.0\n        elif self.boundary_type == \"Mixed\":\n            alpha = np.r_[1.0, 0.0]\n        else:\n            alpha = 0.0\n\n        gamma = alpha * phi_bc + beta * j_bc * mesh.boundary_face_outward_normals\n\n        Me = mesh.get_edge_inner_product()\n        Mn = sp.diags(mesh.average_node_to_cell.T @ mesh.cell_volumes)\n        G = mesh.nodal_gradient\n\n        B_bc, b_bc = mesh.edge_divergence_weak_form_robin(alpha, beta, gamma)\n\n        A = -G.T @ Me @ G + B_bc\n        rhs = Mn @ q_ana - b_bc\n\n        if self.boundary_type == \"Nuemann\":\n            # put a single dirichlet node on the boundary\n            P_b = sp.eye(mesh.n_nodes, format=\"csc\")[:, 0]\n            P_f = sp.eye(mesh.n_nodes, format=\"csc\")[:, 1:]\n            # P_f.T @ A @ (P_f @ x + P_b @ ana) = P_f.T @ rhs\n            rhs = P_f.T @ rhs - (P_f.T @ A @ P_b) @ phi_ana[[0]]\n            A = P_f.T @ A @ P_f\n\n        phi_test = spsolve(A, rhs)\n\n        if self.boundary_type == \"Nuemann\":\n            phi_test = P_f @ phi_test + P_b @ phi_ana[[0]]\n\n        err = np.linalg.norm(phi_test - phi_ana, np.inf)\n\n        return err\n\n    def test_orderNuemannX(self):\n        self.name = \"1D - NuemannBoundary_Inverse\"\n        self.boundary_type = \"Nuemann\"\n        self.orderTest()\n\n    def test_orderRobinX(self):\n        self.name = \"1D - RobinBoundary_Inverse\"\n        self.boundary_type = \"Robin\"\n        self.orderTest()\n\n    def test_orderMixed(self):\n        self.name = \"1D - MixedBoundary_Inverse\"\n        self.boundary_type = \"Mixed\"\n        self.orderTest()\n\n\nclass TestN2D_boundaries(discretize.tests.OrderTest):\n    name = \"2D - Boundaries\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 2\n    expectedOrders = 2\n    tolerance = [0.8, 0.8, 0.6]\n    meshSizes = [8, 16, 32, 64]\n    # meshSizes = [4]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.sin(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funX = lambda x: np.pi * np.cos(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funY = lambda x: np.pi * np.cos(np.pi * x[:, 1]) * np.sin(np.pi * x[:, 0])\n        q_fun = lambda x: -2 * (np.pi**2) * phi(x)\n\n        mesh = self.M\n        if self._meshType == \"rotateCurv\":\n            nodes_x, nodes_y = mesh.node_list\n            nodes_x -= 0.25\n            nodes_y -= 0.25\n            mesh = discretize.CurvilinearMesh([nodes_x, nodes_y])\n        else:\n            mesh.origin = np.r_[-0.25, -0.25]\n\n        phi_ana = phi(mesh.nodes)\n        q_ana = q_fun(mesh.nodes)\n\n        if self.boundary_type == \"Nuemann\":\n            # Nuemann with J defined at boundary nodes\n            jx_bc = j_funX(mesh.boundary_nodes)\n            jy_bc = j_funY(mesh.boundary_nodes)\n            j_bc = np.c_[jx_bc, jy_bc]\n\n            M_bn = mesh.boundary_node_vector_integral\n\n            B_bc = sp.csr_matrix((mesh.n_nodes, mesh.n_nodes))\n            b_bc = M_bn @ (j_bc.reshape(-1, order=\"F\"))\n        else:\n            phi_bc = phi(mesh.boundary_faces)\n            jx_bc = j_funX(mesh.boundary_faces)\n            jy_bc = j_funY(mesh.boundary_faces)\n            j_bc = np.c_[jx_bc, jy_bc]\n\n            j_bc_dot_n = np.sum(j_bc * mesh.boundary_face_outward_normals, axis=-1)\n\n            # construct matrix with robin operator\n            if self.boundary_type == \"Robin\":\n                alpha = 1.0\n            else:\n                # get indices of x0 boundary and y0 boundary\n                n_boundary_faces = len(j_bc_dot_n)\n                robin_locs = np.any(mesh.boundary_faces == -0.25, axis=1)\n\n                alpha = np.zeros(n_boundary_faces)\n                alpha[robin_locs] = 1.0\n\n            beta = 1.0\n            gamma = alpha * phi_bc + beta * j_bc_dot_n\n\n            B_bc, b_bc = mesh.edge_divergence_weak_form_robin(alpha, beta, gamma)\n\n        Me = mesh.get_edge_inner_product()\n        Mn = sp.diags(mesh.average_node_to_cell.T @ mesh.cell_volumes)\n        G = mesh.nodal_gradient\n\n        A = -G.T @ Me @ G + B_bc\n        rhs = Mn @ q_ana - b_bc\n\n        if self.boundary_type == \"Nuemann\":\n            # put a single dirichlet node on the boundary\n            P_b = sp.eye(mesh.n_nodes, format=\"csc\")[:, 0]\n            P_f = sp.eye(mesh.n_nodes, format=\"csc\")[:, 1:]\n            # P_f.T @ A @ (P_f @ x + P_b @ ana) = P_f.T @ rhs\n            rhs = P_f.T @ rhs - (P_f.T @ A @ P_b) @ phi_ana[[0]]\n            A = P_f.T @ A @ P_f\n\n        phi_test = spsolve(A, rhs)\n\n        if self.boundary_type == \"Nuemann\":\n            phi_test = P_f @ phi_test + P_b @ phi_ana[[0]]\n\n        err = np.linalg.norm(phi_test - phi_ana, np.inf)\n        return err\n\n    def test_orderNuemannX(self):\n        self.name = \"2D - NuemannBoundary_Inverse\"\n        self.boundary_type = \"Nuemann\"\n        self.orderTest()\n\n    def test_orderRobinX(self):\n        self.name = \"2D - RobinBoundary_Inverse\"\n        self.boundary_type = \"Robin\"\n        self.orderTest()\n\n    def test_orderMixed(self):\n        self.name = \"2D - MixedBoundary_Inverse\"\n        self.boundary_type = \"Mixed\"\n        self.orderTest()\n\n\nclass TestN3D_boundaries(discretize.tests.OrderTest):\n    name = \"3D - Boundaries\"\n    meshTypes = [\"uniformTensorMesh\", \"uniformTree\", \"rotateCurv\"]\n    meshDimension = 3\n    expectedOrders = 2\n    tolerance = 0.6\n    meshSizes = [2, 4, 8, 16]\n    # meshSizes = [4]\n\n    def getError(self):\n        # Test function\n        phi = (\n            lambda x: np.sin(np.pi * x[:, 0])\n            * np.sin(np.pi * x[:, 1])\n            * np.sin(np.pi * x[:, 2])\n        )\n\n        j_funX = (\n            lambda x: np.pi\n            * np.cos(np.pi * x[:, 0])\n            * np.sin(np.pi * x[:, 1])\n            * np.sin(np.pi * x[:, 2])\n        )\n        j_funY = (\n            lambda x: np.pi\n            * np.sin(np.pi * x[:, 0])\n            * np.cos(np.pi * x[:, 1])\n            * np.sin(np.pi * x[:, 2])\n        )\n        j_funZ = (\n            lambda x: np.pi\n            * np.sin(np.pi * x[:, 0])\n            * np.sin(np.pi * x[:, 1])\n            * np.cos(np.pi * x[:, 2])\n        )\n\n        q_fun = lambda x: -3 * (np.pi**2) * phi(x)\n\n        mesh = self.M\n        if self._meshType == \"rotateCurv\":\n            nodes_x, nodes_y, nodes_z = mesh.node_list\n            nodes_x -= 0.25\n            nodes_y -= 0.25\n            nodes_z -= 0.25\n            mesh = discretize.CurvilinearMesh([nodes_x, nodes_y, nodes_z])\n        else:\n            mesh.origin = np.r_[-0.25, -0.25, -0.25]\n\n        phi_ana = phi(mesh.nodes)\n        q_ana = q_fun(mesh.nodes)\n\n        if self.boundary_type == \"Nuemann\":\n            # Nuemann with J defined at boundary nodes\n            jx_bc = j_funX(mesh.boundary_nodes)\n            jy_bc = j_funY(mesh.boundary_nodes)\n            jz_bc = j_funZ(mesh.boundary_nodes)\n            j_bc = np.c_[jx_bc, jy_bc, jz_bc]\n\n            M_bn = mesh.boundary_node_vector_integral\n\n            B_bc = sp.csr_matrix((mesh.n_nodes, mesh.n_nodes))\n            b_bc = M_bn @ (j_bc.reshape(-1, order=\"F\"))\n        else:\n            phi_bc = phi(mesh.boundary_faces)\n            jx_bc = j_funX(mesh.boundary_faces)\n            jy_bc = j_funY(mesh.boundary_faces)\n            jz_bc = j_funZ(mesh.boundary_faces)\n            j_bc = np.c_[jx_bc, jy_bc, jz_bc]\n\n            j_bc_dot_n = np.sum(j_bc * mesh.boundary_face_outward_normals, axis=-1)\n\n            # construct matrix with robin operator\n            if self.boundary_type == \"Robin\":\n                alpha = 1.0\n            else:\n                # get indices of x0, y0 and z0 boundaries\n                n_boundary_faces = len(j_bc_dot_n)\n                robin_locs = np.any(mesh.boundary_faces == -0.25, axis=1)\n\n                alpha = np.zeros(n_boundary_faces)\n                alpha[robin_locs] = 1.0\n\n            beta = 1.0\n            gamma = alpha * phi_bc + beta * j_bc_dot_n\n\n            B_bc, b_bc = mesh.edge_divergence_weak_form_robin(alpha, beta, gamma)\n\n        Me = mesh.get_edge_inner_product()\n        Mn = sp.diags(mesh.average_node_to_cell.T @ mesh.cell_volumes)\n        G = mesh.nodal_gradient\n\n        A = -G.T @ Me @ G + B_bc\n        rhs = Mn @ q_ana - b_bc\n\n        if self.boundary_type == \"Nuemann\":\n            P_b = sp.eye(mesh.n_nodes, format=\"csc\")[:, 0]\n            P_f = sp.eye(mesh.n_nodes, format=\"csc\")[:, 1:]\n            # P_f.T @ A @ (P_f @ x + P_b @ ana) = P_f.T @ rhs\n            rhs = P_f.T @ rhs - (P_f.T @ A @ P_b) @ phi_ana[[0]]\n            A = P_f.T @ A @ P_f\n\n        phi_test = spsolve(A, rhs)\n\n        if self.boundary_type == \"Nuemann\":\n            phi_test = P_f @ phi_test + P_b @ phi_ana[[0]]\n        err = np.linalg.norm(phi_test - phi_ana) / np.sqrt(mesh.n_nodes)\n        return err\n\n    def test_orderNuemannX(self):\n        self.name = \"3D - NuemannBoundary_Inverse\"\n        self.boundary_type = \"Nuemann\"\n        self.orderTest()\n\n    def test_orderRobinX(self):\n        self.name = \"3D - RobinBoundary_Inverse\"\n        self.boundary_type = \"Robin\"\n        self.orderTest()\n\n    def test_orderMixed(self):\n        self.name = \"3D - MixedBoundary_Inverse\"\n        self.boundary_type = \"Mixed\"\n        self.orderTest()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/boundaries/test_errors.py",
    "content": "import unittest\nimport numpy as np\nimport discretize\n\n\nrng = np.random.default_rng(53679)\n\n\nclass RobinOperatorTest(unittest.TestCase):\n    def setUp(self):\n        self.mesh = discretize.TensorMesh([18, 20, 32])\n\n    def testCellGradBroadcasting(self):\n        mesh = self.mesh\n        boundary_faces = mesh.boundary_faces\n\n        n_boundary_faces = boundary_faces.shape[0]\n\n        alpha = np.full(n_boundary_faces, 0.5)\n        beta = np.full(n_boundary_faces, 1.5)\n        gamma = np.full(n_boundary_faces, 2.0)\n\n        B1, b1 = mesh.cell_gradient_weak_form_robin(alpha=alpha, beta=beta, gamma=gamma)\n\n        for a in [0.5, alpha]:\n            for b in [1.5, beta]:\n                for g in [2.0, gamma]:\n                    B_t, bt = mesh.cell_gradient_weak_form_robin(\n                        alpha=a, beta=b, gamma=g\n                    )\n                    np.testing.assert_equal(bt, b1)\n                    self.assertEqual((B1 - B_t).nnz, 0)\n\n        gamma = rng.random((n_boundary_faces, 2))\n        B1, b1 = mesh.cell_gradient_weak_form_robin(\n            alpha=0.5, beta=1.5, gamma=gamma[:, 0]\n        )\n        B2, b2 = mesh.cell_gradient_weak_form_robin(\n            alpha=0.5, beta=1.5, gamma=gamma[:, 1]\n        )\n        B3, b3 = mesh.cell_gradient_weak_form_robin(alpha=0.5, beta=1.5, gamma=gamma)\n        np.testing.assert_allclose(B1.data, B2.data)\n        np.testing.assert_allclose(B1.data, B3.data)\n        np.testing.assert_allclose(np.c_[b1, b2], b3)\n\n    def testEdgeDivBroadcasting(self):\n        mesh = self.mesh\n        boundary_faces = mesh.boundary_faces\n        boundary_nodes = mesh.boundary_nodes\n\n        n_boundary_faces = boundary_faces.shape[0]\n        n_boundary_nodes = boundary_nodes.shape[0]\n\n        alpha = np.full(n_boundary_faces, 0.5)\n        beta = np.full(n_boundary_faces, 1.5)\n        gamma = np.full(n_boundary_faces, 2.0)\n\n        B1, b1 = mesh.edge_divergence_weak_form_robin(\n            alpha=alpha, beta=beta, gamma=gamma\n        )\n\n        for a in [0.5, alpha]:\n            for b in [1.5, beta]:\n                for g in [2.0, gamma]:\n                    B_t, bt = mesh.edge_divergence_weak_form_robin(\n                        alpha=a, beta=b, gamma=g\n                    )\n                    np.testing.assert_allclose(bt, b1)\n                    np.testing.assert_allclose(B1.data, B_t.data)\n\n        alpha = np.full(n_boundary_nodes, 0.5)\n        beta = np.full(n_boundary_nodes, 1.5)\n        gamma = np.full(n_boundary_nodes, 2.0)\n\n        for a in [0.5, alpha]:\n            for b in [1.5, beta]:\n                for g in [2.0, gamma]:\n                    B_t, bt = mesh.edge_divergence_weak_form_robin(\n                        alpha=a, beta=b, gamma=g\n                    )\n                    np.testing.assert_allclose(bt, b1)\n                    np.testing.assert_allclose(B1.data, B_t.data)\n\n        gamma = rng.random((n_boundary_faces, 2))\n        B1, b1 = mesh.edge_divergence_weak_form_robin(\n            alpha=0.5, beta=1.5, gamma=gamma[:, 0]\n        )\n        B2, b2 = mesh.edge_divergence_weak_form_robin(\n            alpha=0.5, beta=1.5, gamma=gamma[:, 1]\n        )\n        B3, b3 = mesh.edge_divergence_weak_form_robin(alpha=0.5, beta=1.5, gamma=gamma)\n        np.testing.assert_allclose(B1.data, B2.data)\n        np.testing.assert_allclose(B1.data, B3.data)\n        np.testing.assert_allclose(np.c_[b1, b2], b3)\n\n        gamma = rng.random((n_boundary_nodes, 2))\n        B1, b1 = mesh.edge_divergence_weak_form_robin(\n            alpha=0.5, beta=1.5, gamma=gamma[:, 0]\n        )\n        B2, b2 = mesh.edge_divergence_weak_form_robin(\n            alpha=0.5, beta=1.5, gamma=gamma[:, 1]\n        )\n        B3, b3 = mesh.edge_divergence_weak_form_robin(alpha=0.5, beta=1.5, gamma=gamma)\n        np.testing.assert_allclose(B1.data, B2.data)\n        np.testing.assert_allclose(B1.data, B3.data)\n        np.testing.assert_allclose(np.c_[b1, b2], b3)\n\n    def testEdgeDivErrors(self):\n        mesh = self.mesh\n        boundary_faces = mesh.boundary_faces\n        boundary_nodes = mesh.boundary_nodes\n\n        n_boundary_faces = boundary_faces.shape[0]\n        n_boundary_nodes = boundary_nodes.shape[0]\n\n        alpha_f = np.full(n_boundary_faces, 0.5)\n        beta_n = np.full(n_boundary_nodes, 1.5)\n        gamma_n = np.full(n_boundary_nodes, 2.0)\n\n        # throws error if a beta is 0\n        with self.assertRaises(ValueError):\n            mesh.edge_divergence_weak_form_robin(1.0, 0.0, 1.0)\n\n        # incorrect length of an input\n        with self.assertRaises(ValueError):\n            mesh.edge_divergence_weak_form_robin(3, 2, [1.0, 0.0])\n\n        with self.assertRaises(ValueError):\n            # inconsistent input lengths\n            mesh.edge_divergence_weak_form_robin(alpha_f, beta_n, gamma_n)\n\n\nclass mesh1DTests(unittest.TestCase):\n    def setUp(self):\n        self.mesh, _ = discretize.tests.setup_mesh(\"uniformTensorMesh\", 32, 1)\n\n    def testItems(self):\n        mesh = self.mesh\n        np.testing.assert_equal(mesh.boundary_faces, mesh.boundary_nodes)\n\n        self.assertIs(mesh.boundary_edges, None)\n        self.assertIs(mesh.project_edge_to_boundary_edge, None)\n"
  },
  {
    "path": "tests/boundaries/test_tensor_boundary.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\nfrom scipy.sparse.linalg import spsolve\n\nMESHTYPES = [\"uniformTensorMesh\"]\n\n\ndef getxBCyBC_CC(mesh, alpha, beta, gamma):\n    r\"\"\"\n    This is a subfunction generating mixed-boundary condition:\n\n    .. math::\n        \\nabla \\cdot \\vec{j} = -\\nabla \\cdot \\vec{j}_s = q \\\\\n        \\rho \\vec{j} = -\\nabla \\phi \\phi \\\\\n        \\alpha \\phi + \\beta \\frac{\\partial \\phi}{\\partial r} = \\gamma \\ at \\ r\n        = \\partial \\Omega \\\\\n        xBC = f_1(\\alpha, \\beta, \\gamma)\\\\\n        yBC = f(\\alpha, \\beta, \\gamma)\\\\\n\n    Computes xBC and yBC for cell-centered discretizations\n    \"\"\"\n\n    if mesh.dim == 1:  # 1D\n        if len(alpha) != 2 or len(beta) != 2 or len(gamma) != 2:\n            raise Exception(\"Lenght of list, alpha should be 2\")\n        mesh.cell_boundary_indices\n\n        alpha_xm, beta_xm, gamma_xm = alpha[0], beta[0], gamma[0]\n        alpha_xp, beta_xp, gamma_xp = alpha[1], beta[1], gamma[1]\n\n        # h_xm, h_xp = mesh.gridCC[fCCxm], mesh.gridCC[fCCxp]\n        h_xm, h_xp = mesh.h[0][0], mesh.h[0][-1]\n\n        a_xm = gamma_xm / (0.5 * alpha_xm - beta_xm / h_xm)\n        b_xm = (0.5 * alpha_xm + beta_xm / h_xm) / (0.5 * alpha_xm - beta_xm / h_xm)\n        a_xp = gamma_xp / (0.5 * alpha_xp - beta_xp / h_xp)\n        b_xp = (0.5 * alpha_xp + beta_xp / h_xp) / (0.5 * alpha_xp - beta_xp / h_xp)\n\n        xBC_xm = 0.5 * a_xm\n        xBC_xp = 0.5 * a_xp / b_xp\n        yBC_xm = 0.5 * (1.0 - b_xm)\n        yBC_xp = 0.5 * (1.0 - 1.0 / b_xp)\n\n        xBC = np.r_[xBC_xm, xBC_xp]\n        yBC = np.r_[yBC_xm, yBC_xp]\n\n    elif mesh.dim == 2:  # 2D\n        if len(alpha) != 4 or len(beta) != 4 or len(gamma) != 4:\n            raise Exception(\"Lenght of list, alpha should be 4\")\n\n        fxm, fxp, fym, fyp = mesh.face_boundary_indices\n\n        alpha_xm, beta_xm, gamma_xm = alpha[0], beta[0], gamma[0]\n        alpha_xp, beta_xp, gamma_xp = alpha[1], beta[1], gamma[1]\n        alpha_ym, beta_ym, gamma_ym = alpha[2], beta[2], gamma[2]\n        alpha_yp, beta_yp, gamma_yp = alpha[3], beta[3], gamma[3]\n\n        # h_xm, h_xp = mesh.gridCC[fCCxm,0], mesh.gridCC[fCCxp,0]\n        # h_ym, h_yp = mesh.gridCC[fCCym,1], mesh.gridCC[fCCyp,1]\n\n        h_xm = mesh.h[0][0] * np.ones_like(alpha_xm)\n        h_xp = mesh.h[0][-1] * np.ones_like(alpha_xp)\n        h_ym = mesh.h[1][0] * np.ones_like(alpha_ym)\n        h_yp = mesh.h[1][-1] * np.ones_like(alpha_yp)\n\n        a_xm = gamma_xm / (0.5 * alpha_xm - beta_xm / h_xm)\n        b_xm = (0.5 * alpha_xm + beta_xm / h_xm) / (0.5 * alpha_xm - beta_xm / h_xm)\n        a_xp = gamma_xp / (0.5 * alpha_xp - beta_xp / h_xp)\n        b_xp = (0.5 * alpha_xp + beta_xp / h_xp) / (0.5 * alpha_xp - beta_xp / h_xp)\n\n        a_ym = gamma_ym / (0.5 * alpha_ym - beta_ym / h_ym)\n        b_ym = (0.5 * alpha_ym + beta_ym / h_ym) / (0.5 * alpha_ym - beta_ym / h_ym)\n        a_yp = gamma_yp / (0.5 * alpha_yp - beta_yp / h_yp)\n        b_yp = (0.5 * alpha_yp + beta_yp / h_yp) / (0.5 * alpha_yp - beta_yp / h_yp)\n\n        xBC_xm = 0.5 * a_xm\n        xBC_xp = 0.5 * a_xp / b_xp\n        yBC_xm = 0.5 * (1.0 - b_xm)\n        yBC_xp = 0.5 * (1.0 - 1.0 / b_xp)\n        xBC_ym = 0.5 * a_ym\n        xBC_yp = 0.5 * a_yp / b_yp\n        yBC_ym = 0.5 * (1.0 - b_ym)\n        yBC_yp = 0.5 * (1.0 - 1.0 / b_yp)\n\n        sortindsfx = np.argsort(\n            np.r_[np.arange(mesh.nFx)[fxm], np.arange(mesh.nFx)[fxp]]\n        )\n        sortindsfy = np.argsort(\n            np.r_[np.arange(mesh.nFy)[fym], np.arange(mesh.nFy)[fyp]]\n        )\n\n        xBC_x = np.r_[xBC_xm, xBC_xp][sortindsfx]\n        xBC_y = np.r_[xBC_ym, xBC_yp][sortindsfy]\n        yBC_x = np.r_[yBC_xm, yBC_xp][sortindsfx]\n        yBC_y = np.r_[yBC_ym, yBC_yp][sortindsfy]\n\n        xBC = np.r_[xBC_x, xBC_y]\n        yBC = np.r_[yBC_x, yBC_y]\n\n    elif mesh.dim == 3:  # 3D\n        if len(alpha) != 6 or len(beta) != 6 or len(gamma) != 6:\n            raise Exception(\"Lenght of list, alpha should be 6\")\n        # fCCxm,fCCxp,fCCym,fCCyp,fCCzm,fCCzp = mesh.cell_boundary_indices\n        fxm, fxp, fym, fyp, fzm, fzp = mesh.face_boundary_indices\n\n        alpha_xm, beta_xm, gamma_xm = alpha[0], beta[0], gamma[0]\n        alpha_xp, beta_xp, gamma_xp = alpha[1], beta[1], gamma[1]\n        alpha_ym, beta_ym, gamma_ym = alpha[2], beta[2], gamma[2]\n        alpha_yp, beta_yp, gamma_yp = alpha[3], beta[3], gamma[3]\n        alpha_zm, beta_zm, gamma_zm = alpha[4], beta[4], gamma[4]\n        alpha_zp, beta_zp, gamma_zp = alpha[5], beta[5], gamma[5]\n\n        # h_xm, h_xp = mesh.gridCC[fCCxm,0], mesh.gridCC[fCCxp,0]\n        # h_ym, h_yp = mesh.gridCC[fCCym,1], mesh.gridCC[fCCyp,1]\n        # h_zm, h_zp = mesh.gridCC[fCCzm,2], mesh.gridCC[fCCzp,2]\n\n        h_xm = mesh.h[0][0] * np.ones_like(alpha_xm)\n        h_xp = mesh.h[0][-1] * np.ones_like(alpha_xp)\n        h_ym = mesh.h[1][0] * np.ones_like(alpha_ym)\n        h_yp = mesh.h[1][-1] * np.ones_like(alpha_yp)\n        h_zm = mesh.h[2][0] * np.ones_like(alpha_zm)\n        h_zp = mesh.h[2][-1] * np.ones_like(alpha_zp)\n\n        a_xm = gamma_xm / (0.5 * alpha_xm - beta_xm / h_xm)\n        b_xm = (0.5 * alpha_xm + beta_xm / h_xm) / (0.5 * alpha_xm - beta_xm / h_xm)\n        a_xp = gamma_xp / (0.5 * alpha_xp - beta_xp / h_xp)\n        b_xp = (0.5 * alpha_xp + beta_xp / h_xp) / (0.5 * alpha_xp - beta_xp / h_xp)\n\n        a_ym = gamma_ym / (0.5 * alpha_ym - beta_ym / h_ym)\n        b_ym = (0.5 * alpha_ym + beta_ym / h_ym) / (0.5 * alpha_ym - beta_ym / h_ym)\n        a_yp = gamma_yp / (0.5 * alpha_yp - beta_yp / h_yp)\n        b_yp = (0.5 * alpha_yp + beta_yp / h_yp) / (0.5 * alpha_yp - beta_yp / h_yp)\n\n        a_zm = gamma_zm / (0.5 * alpha_zm - beta_zm / h_zm)\n        b_zm = (0.5 * alpha_zm + beta_zm / h_zm) / (0.5 * alpha_zm - beta_zm / h_zm)\n        a_zp = gamma_zp / (0.5 * alpha_zp - beta_zp / h_zp)\n        b_zp = (0.5 * alpha_zp + beta_zp / h_zp) / (0.5 * alpha_zp - beta_zp / h_zp)\n\n        xBC_xm = 0.5 * a_xm\n        xBC_xp = 0.5 * a_xp / b_xp\n        yBC_xm = 0.5 * (1.0 - b_xm)\n        yBC_xp = 0.5 * (1.0 - 1.0 / b_xp)\n        xBC_ym = 0.5 * a_ym\n        xBC_yp = 0.5 * a_yp / b_yp\n        yBC_ym = 0.5 * (1.0 - b_ym)\n        yBC_yp = 0.5 * (1.0 - 1.0 / b_yp)\n        xBC_zm = 0.5 * a_zm\n        xBC_zp = 0.5 * a_zp / b_zp\n        yBC_zm = 0.5 * (1.0 - b_zm)\n        yBC_zp = 0.5 * (1.0 - 1.0 / b_zp)\n\n        sortindsfx = np.argsort(\n            np.r_[np.arange(mesh.nFx)[fxm], np.arange(mesh.nFx)[fxp]]\n        )\n        sortindsfy = np.argsort(\n            np.r_[np.arange(mesh.nFy)[fym], np.arange(mesh.nFy)[fyp]]\n        )\n        sortindsfz = np.argsort(\n            np.r_[np.arange(mesh.nFz)[fzm], np.arange(mesh.nFz)[fzp]]\n        )\n\n        xBC_x = np.r_[xBC_xm, xBC_xp][sortindsfx]\n        xBC_y = np.r_[xBC_ym, xBC_yp][sortindsfy]\n        xBC_z = np.r_[xBC_zm, xBC_zp][sortindsfz]\n\n        yBC_x = np.r_[yBC_xm, yBC_xp][sortindsfx]\n        yBC_y = np.r_[yBC_ym, yBC_yp][sortindsfy]\n        yBC_z = np.r_[yBC_zm, yBC_zp][sortindsfz]\n\n        xBC = np.r_[xBC_x, xBC_y, xBC_z]\n        yBC = np.r_[yBC_x, yBC_y, yBC_z]\n\n    return xBC, yBC\n\n\nclass Test1D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"1D - Mixed\"\n    meshTypes = MESHTYPES\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32]\n\n    def getError(self):\n        # Test function\n        def phi_fun(x):\n            return np.cos(np.pi * x)\n\n        def j_fun(x):\n            return np.pi * np.sin(np.pi * x)\n\n        def phi_deriv(x):\n            return -j_fun(x)\n\n        def q_fun(x):\n            return (np.pi**2) * np.cos(np.pi * x)\n\n        xc_ana = phi_fun(self.M.gridCC)\n\n        # Get boundary locations\n        vecN = self.M.nodes_x\n\n        # Setup Mixed B.C (alpha, beta, gamma)\n        alpha_xm, alpha_xp = 1.0, 1.0\n        beta_xm, beta_xp = 1.0, 1.0\n        alpha = np.r_[alpha_xm, alpha_xp]\n        beta = np.r_[beta_xm, beta_xp]\n        phi_bc = phi_fun(vecN[[0, -1]])\n        phi_deriv_bc = phi_deriv(vecN[[0, -1]])\n        gamma = alpha * phi_bc + beta * phi_deriv_bc\n        x_BC, y_BC = getxBCyBC_CC(self.M, alpha, beta, gamma)\n\n        sigma = np.ones(self.M.nC)\n        self.M.get_face_inner_product(1.0 / sigma)\n        MfrhoI = self.M.get_face_inner_product(1.0 / sigma, invert_matrix=True)\n        V = discretize.utils.sdiag(self.M.cell_volumes)\n        Div = V * self.M.face_divergence\n        P_BC, B = self.M.get_BC_projections_simple()\n        q = q_fun(self.M.gridCC)\n        M = B * self.M.aveCC2F\n        G = Div.T - P_BC * discretize.utils.sdiag(y_BC) * M\n        # Mrhoj = D.T V phi + P_BC*discretize.utils.sdiag(y_BC)*M phi - P_BC*x_BC\n        rhs = V * q + Div * MfrhoI * P_BC * x_BC\n        A = Div * MfrhoI * G\n\n        if self.myTest == \"xc\":\n            # TODO: fix the null space\n            xc = spsolve(A, rhs)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n        else:\n            NotImplementedError\n        return err\n\n    def test_order(self):\n        print(\"==== Testing Mixed boudary conduction for CC-problem ====\")\n        self.name = \"1D\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass Test2D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"2D - Mixed\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32]\n\n    def getError(self):\n        # Test function\n        def phi_fun(x):\n            return np.cos(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n\n        def j_funX(x):\n            return +np.pi * np.sin(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n\n        def j_funY(x):\n            return +np.pi * np.cos(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n\n        def phideriv_funX(x):\n            return -j_funX(x)\n\n        def phideriv_funY(x):\n            return -j_funY(x)\n\n        def q_fun(x):\n            return +2 * (np.pi**2) * phi_fun(x)\n\n        xc_ana = phi_fun(self.M.gridCC)\n\n        # Get boundary locations\n        fxm, fxp, fym, fyp = self.M.face_boundary_indices\n        gBFxm = self.M.gridFx[fxm, :]\n        gBFxp = self.M.gridFx[fxp, :]\n        gBFym = self.M.gridFy[fym, :]\n        gBFyp = self.M.gridFy[fyp, :]\n\n        # Setup Mixed B.C (alpha, beta, gamma)\n        alpha_xm = np.ones_like(gBFxm[:, 0])\n        alpha_xp = np.ones_like(gBFxp[:, 0])\n        beta_xm = np.ones_like(gBFxm[:, 0])\n        beta_xp = np.ones_like(gBFxp[:, 0])\n        alpha_ym = np.ones_like(gBFym[:, 1])\n        alpha_yp = np.ones_like(gBFyp[:, 1])\n        beta_ym = np.ones_like(gBFym[:, 1])\n        beta_yp = np.ones_like(gBFyp[:, 1])\n\n        phi_bc_xm, phi_bc_xp = phi_fun(gBFxm), phi_fun(gBFxp)\n        phi_bc_ym, phi_bc_yp = phi_fun(gBFym), phi_fun(gBFyp)\n\n        phiderivX_bc_xm = phideriv_funX(gBFxm)\n        phiderivX_bc_xp = phideriv_funX(gBFxp)\n        phiderivY_bc_ym = phideriv_funY(gBFym)\n        phiderivY_bc_yp = phideriv_funY(gBFyp)\n\n        def gamma_fun(alpha, beta, phi, phi_deriv):\n            return alpha * phi + beta * phi_deriv\n\n        gamma_xm = gamma_fun(alpha_xm, beta_xm, phi_bc_xm, phiderivX_bc_xm)\n        gamma_xp = gamma_fun(alpha_xp, beta_xp, phi_bc_xp, phiderivX_bc_xp)\n        gamma_ym = gamma_fun(alpha_ym, beta_ym, phi_bc_ym, phiderivY_bc_ym)\n        gamma_yp = gamma_fun(alpha_yp, beta_yp, phi_bc_yp, phiderivY_bc_yp)\n\n        alpha = [alpha_xm, alpha_xp, alpha_ym, alpha_yp]\n        beta = [beta_xm, beta_xp, beta_ym, beta_yp]\n        gamma = [gamma_xm, gamma_xp, gamma_ym, gamma_yp]\n\n        x_BC, y_BC = getxBCyBC_CC(self.M, alpha, beta, gamma)\n\n        sigma = np.ones(self.M.nC)\n        self.M.get_face_inner_product(1.0 / sigma)\n        MfrhoI = self.M.get_face_inner_product(1.0 / sigma, invert_matrix=True)\n        V = discretize.utils.sdiag(self.M.cell_volumes)\n        Div = V * self.M.face_divergence\n        P_BC, B = self.M.get_BC_projections_simple()\n        q = q_fun(self.M.gridCC)\n        M = B * self.M.aveCC2F\n        G = Div.T - P_BC * discretize.utils.sdiag(y_BC) * M\n        rhs = V * q + Div * MfrhoI * P_BC * x_BC\n        A = Div * MfrhoI * G\n\n        if self.myTest == \"xc\":\n            xc = spsolve(A, rhs)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n        else:\n            NotImplementedError\n        return err\n\n    def test_order(self):\n        print(\"==== Testing Mixed boudary conduction for CC-problem ====\")\n        self.name = \"2D\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nclass Test3D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"3D - Mixed\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n    expectedOrders = 2\n    meshSizes = [4, 8, 16]\n\n    def getError(self):\n        # Test function\n        def phi_fun(x):\n            return (\n                np.cos(np.pi * x[:, 0])\n                * np.cos(np.pi * x[:, 1])\n                * np.cos(np.pi * x[:, 2])\n            )\n\n        def j_funX(x):\n            return (\n                np.pi\n                * np.sin(np.pi * x[:, 0])\n                * np.cos(np.pi * x[:, 1])\n                * np.cos(np.pi * x[:, 2])\n            )\n\n        def j_funY(x):\n            return (\n                np.pi\n                * np.cos(np.pi * x[:, 0])\n                * np.sin(np.pi * x[:, 1])\n                * np.cos(np.pi * x[:, 2])\n            )\n\n        def j_funZ(x):\n            return (\n                np.pi\n                * np.cos(np.pi * x[:, 0])\n                * np.cos(np.pi * x[:, 1])\n                * np.sin(np.pi * x[:, 2])\n            )\n\n        def phideriv_funX(x):\n            return -j_funX(x)\n\n        def phideriv_funY(x):\n            return -j_funY(x)\n\n        def phideriv_funZ(x):\n            return -j_funZ(x)\n\n        def q_fun(x):\n            return 3 * (np.pi**2) * phi_fun(x)\n\n        xc_ana = phi_fun(self.M.gridCC)\n\n        # Get boundary locations\n        fxm, fxp, fym, fyp, fzm, fzp = self.M.face_boundary_indices\n        gBFxm = self.M.gridFx[fxm, :]\n        gBFxp = self.M.gridFx[fxp, :]\n        gBFym = self.M.gridFy[fym, :]\n        gBFyp = self.M.gridFy[fyp, :]\n        gBFzm = self.M.gridFz[fzm, :]\n        gBFzp = self.M.gridFz[fzp, :]\n\n        # Setup Mixed B.C (alpha, beta, gamma)\n        alpha_xm = np.ones_like(gBFxm[:, 0])\n        alpha_xp = np.ones_like(gBFxp[:, 0])\n        beta_xm = np.ones_like(gBFxm[:, 0])\n        beta_xp = np.ones_like(gBFxp[:, 0])\n        alpha_ym = np.ones_like(gBFym[:, 1])\n        alpha_yp = np.ones_like(gBFyp[:, 1])\n        beta_ym = np.ones_like(gBFym[:, 1])\n        beta_yp = np.ones_like(gBFyp[:, 1])\n        alpha_zm = np.ones_like(gBFzm[:, 2])\n        alpha_zp = np.ones_like(gBFzp[:, 2])\n        beta_zm = np.ones_like(gBFzm[:, 2])\n        beta_zp = np.ones_like(gBFzp[:, 2])\n\n        phi_bc_xm, phi_bc_xp = phi_fun(gBFxm), phi_fun(gBFxp)\n        phi_bc_ym, phi_bc_yp = phi_fun(gBFym), phi_fun(gBFyp)\n        phi_bc_zm, phi_bc_zp = phi_fun(gBFzm), phi_fun(gBFzp)\n\n        phiderivX_bc_xm = phideriv_funX(gBFxm)\n        phiderivX_bc_xp = phideriv_funX(gBFxp)\n        phiderivY_bc_ym = phideriv_funY(gBFym)\n        phiderivY_bc_yp = phideriv_funY(gBFyp)\n        phiderivY_bc_zm = phideriv_funZ(gBFzm)\n        phiderivY_bc_zp = phideriv_funZ(gBFzp)\n\n        def gamma_fun(alpha, beta, phi, phi_deriv):\n            return alpha * phi + beta * phi_deriv\n\n        gamma_xm = gamma_fun(alpha_xm, beta_xm, phi_bc_xm, phiderivX_bc_xm)\n        gamma_xp = gamma_fun(alpha_xp, beta_xp, phi_bc_xp, phiderivX_bc_xp)\n        gamma_ym = gamma_fun(alpha_ym, beta_ym, phi_bc_ym, phiderivY_bc_ym)\n        gamma_yp = gamma_fun(alpha_yp, beta_yp, phi_bc_yp, phiderivY_bc_yp)\n        gamma_zm = gamma_fun(alpha_zm, beta_zm, phi_bc_zm, phiderivY_bc_zm)\n        gamma_zp = gamma_fun(alpha_zp, beta_zp, phi_bc_zp, phiderivY_bc_zp)\n\n        alpha = [alpha_xm, alpha_xp, alpha_ym, alpha_yp, alpha_zm, alpha_zp]\n        beta = [beta_xm, beta_xp, beta_ym, beta_yp, beta_zm, beta_zp]\n        gamma = [gamma_xm, gamma_xp, gamma_ym, gamma_yp, gamma_zm, gamma_zp]\n\n        x_BC, y_BC = getxBCyBC_CC(self.M, alpha, beta, gamma)\n\n        sigma = np.ones(self.M.nC)\n        self.M.get_face_inner_product(1.0 / sigma)\n        MfrhoI = self.M.get_face_inner_product(1.0 / sigma, invert_matrix=True)\n        V = discretize.utils.sdiag(self.M.cell_volumes)\n        Div = V * self.M.face_divergence\n        P_BC, B = self.M.get_BC_projections_simple()\n        q = q_fun(self.M.gridCC)\n        M = B * self.M.aveCC2F\n        G = Div.T - P_BC * discretize.utils.sdiag(y_BC) * M\n        rhs = V * q + Div * MfrhoI * P_BC * x_BC\n        A = Div * MfrhoI * G\n\n        if self.myTest == \"xc\":\n            # TODO: fix the null space\n            xc = spsolve(A, rhs)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n        else:\n            NotImplementedError\n        return err\n\n    def test_order(self):\n        print(\"==== Testing Mixed boudary conduction for CC-problem ====\")\n        self.name = \"3D\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/boundaries/test_tensor_boundary_poisson.py",
    "content": "import numpy as np\nimport scipy.sparse as sp\nimport unittest\nimport discretize\nfrom discretize import utils\nfrom scipy.sparse.linalg import spsolve\n\n\nMESHTYPES = [\"uniformTensorMesh\"]\n\n\nclass Test1D_InhomogeneousDirichlet(discretize.tests.OrderTest):\n    name = \"1D - Dirichlet\"\n    meshTypes = MESHTYPES\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.cos(np.pi * x)\n        j_fun = lambda x: -np.pi * np.sin(np.pi * x)\n        q_fun = lambda x: -(np.pi**2) * np.cos(np.pi * x)\n\n        xc_ana = phi(self.M.gridCC)\n        q_ana = q_fun(self.M.gridCC)\n        j_ana = j_fun(self.M.gridFx)\n\n        # TODO: Check where our boundary conditions are CCx or Nx\n        # vec = self.M.nodes_x\n        vec = self.M.cell_centers_x\n\n        phi_bc = phi(vec[[0, -1]])\n        j_bc = j_fun(vec[[0, -1]])\n\n        P, Pin, Pout = self.M.get_BC_projections([[\"dirichlet\", \"dirichlet\"]])\n\n        Mc = self.M.get_face_inner_product()\n        McI = utils.sdinv(Mc)\n        V = utils.sdiag(self.M.cell_volumes)\n        G = -Pin.T * Pin * self.M.face_divergence.T * V\n        D = self.M.face_divergence\n        j = McI * (G * xc_ana + P * phi_bc)\n        q = D * Pin.T * Pin * j + D * Pout.T * j_bc\n\n        # Rearrange if we know q to solve for x\n        A = V * D * Pin.T * Pin * McI * G\n        rhs = V * q_ana - V * D * Pin.T * Pin * McI * P * phi_bc - V * D * Pout.T * j_bc\n        # A = D*McI*G\n        # rhs = q_ana - D*McI*P*phi_bc\n\n        if self.myTest == \"j\":\n            err = np.linalg.norm((j - j_ana), np.inf)\n        elif self.myTest == \"q\":\n            err = np.linalg.norm((q - q_ana), np.inf)\n        elif self.myTest == \"xc\":\n            # TODO: fix the null space\n            xc = spsolve(A, rhs)\n            print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n        elif self.myTest == \"xcJ\":\n            # TODO: fix the null space\n            xc = spsolve(A, rhs)\n            print(np.linalg.norm(utils.mkvc(A * xc) - rhs))\n            j = McI * (G * xc + P * phi_bc)\n            err = np.linalg.norm((j - j_ana), np.inf)\n\n        return err\n\n    def test_orderJ(self):\n        self.name = \"1D - InhomogeneousDirichlet_Forward j\"\n        self.myTest = \"j\"\n        self.orderTest()\n\n    def test_orderQ(self):\n        self.name = \"1D - InhomogeneousDirichlet_Forward q\"\n        self.myTest = \"q\"\n        self.orderTest()\n\n    def test_orderX(self):\n        self.name = \"1D - InhomogeneousDirichlet_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"1D - InhomogeneousDirichlet_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nclass Test2D_InhomogeneousDirichlet(discretize.tests.OrderTest):\n    name = \"2D - Dirichlet\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.cos(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n        j_funX = lambda x: -np.pi * np.sin(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n        j_funY = lambda x: -np.pi * np.cos(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        q_fun = lambda x: -2 * (np.pi**2) * phi(x)\n\n        xc_ana = phi(self.M.gridCC)\n        q_ana = q_fun(self.M.gridCC)\n        jX_ana = j_funX(self.M.gridFx)\n        jY_ana = j_funY(self.M.gridFy)\n        j_ana = np.r_[jX_ana, jY_ana]\n\n        # TODO: Check where our boundary conditions are CCx or Nx\n        # fxm,fxp,fym,fyp = self.M.face_boundary_indices\n        # gBFx = self.M.gridFx[(fxm|fxp),:]\n        # gBFy = self.M.gridFy[(fym|fyp),:]\n        fxm, fxp, fym, fyp = self.M.cell_boundary_indices\n        gBFx = self.M.gridCC[(fxm | fxp), :]\n        gBFy = self.M.gridCC[(fym | fyp), :]\n\n        bc = phi(np.r_[gBFx, gBFy])\n\n        # P = sp.csr_matrix(([-1,1],([0,self.M.nF-1],[0,1])), shape=(self.M.nF, 2))\n\n        P, Pin, Pout = self.M.get_BC_projections(\"dirichlet\")\n\n        Mc = self.M.get_face_inner_product()\n        McI = utils.sdinv(Mc)\n        G = -self.M.face_divergence.T * utils.sdiag(self.M.cell_volumes)\n        D = self.M.face_divergence\n        j = McI * (G * xc_ana + P * bc)\n        q = D * j\n\n        # self.M.plot_image(j, 'FxFy', show_it=True)\n\n        # Rearrange if we know q to solve for x\n        A = D * McI * G\n        rhs = q_ana - D * McI * P * bc\n\n        if self.myTest == \"j\":\n            err = np.linalg.norm((j - j_ana), np.inf)\n        elif self.myTest == \"q\":\n            err = np.linalg.norm((q - q_ana), np.inf)\n        elif self.myTest == \"xc\":\n            xc = spsolve(A, rhs)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n        elif self.myTest == \"xcJ\":\n            xc = spsolve(A, rhs)\n            j = McI * (G * xc + P * bc)\n            err = np.linalg.norm((j - j_ana), np.inf)\n\n        return err\n\n    def test_orderJ(self):\n        self.name = \"2D - InhomogeneousDirichlet_Forward j\"\n        self.myTest = \"j\"\n        self.orderTest()\n\n    def test_orderQ(self):\n        self.name = \"2D - InhomogeneousDirichlet_Forward q\"\n        self.myTest = \"q\"\n        self.orderTest()\n\n    def test_orderX(self):\n        self.name = \"2D - InhomogeneousDirichlet_Inverse\"\n        self.myTest = \"xc\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"2D - InhomogeneousDirichlet_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nclass Test1D_InhomogeneousNeumann(discretize.tests.OrderTest):\n    name = \"1D - Neumann\"\n    meshTypes = MESHTYPES\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.sin(np.pi * x)\n        j_fun = lambda x: np.pi * np.cos(np.pi * x)\n        q_fun = lambda x: -(np.pi**2) * np.sin(np.pi * x)\n\n        xc_ana = phi(self.M.gridCC)\n        q_ana = q_fun(self.M.gridCC)\n        j_ana = j_fun(self.M.gridFx)\n\n        # TODO: Check where our boundary conditions are CCx or Nx\n        vecN = self.M.nodes_x\n        vecC = self.M.cell_centers_x\n\n        phi_bc = phi(vecC[[0, -1]])\n        j_bc = j_fun(vecN[[0, -1]])\n\n        P, Pin, Pout = self.M.get_BC_projections([[\"neumann\", \"neumann\"]])\n\n        Mc = self.M.get_face_inner_product()\n        McI = utils.sdinv(Mc)\n        V = utils.sdiag(self.M.cell_volumes)\n        G = -Pin.T * Pin * self.M.face_divergence.T * V\n        D = self.M.face_divergence\n        j = McI * (G * xc_ana + P * phi_bc)\n        q = V * D * Pin.T * Pin * j + V * D * Pout.T * j_bc\n\n        # Rearrange if we know q to solve for x\n        A = V * D * Pin.T * Pin * McI * G\n        rhs = V * q_ana - V * D * Pin.T * Pin * McI * P * phi_bc - V * D * Pout.T * j_bc\n        # A = D*McI*G\n        # rhs = q_ana - D*McI*P*phi_bc\n\n        if self.myTest == \"j\":\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n        elif self.myTest == \"q\":\n            err = np.linalg.norm((q - V * q_ana), np.inf)\n        elif self.myTest == \"xc\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        elif self.myTest == \"xcJ\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            j = McI * (G * xc + P * phi_bc)\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        return err\n\n    def test_orderJ(self):\n        self.name = \"1D - InhomogeneousNeumann_Forward j\"\n        self.myTest = \"j\"\n        self.orderTest()\n\n    def test_orderQ(self):\n        self.name = \"1D - InhomogeneousNeumann_Forward q\"\n        self.myTest = \"q\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"1D - InhomogeneousNeumann_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nclass Test2D_InhomogeneousNeumann(discretize.tests.OrderTest):\n    name = \"2D - Neumann\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32]\n    # meshSizes = [4]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.sin(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funX = lambda x: np.pi * np.cos(np.pi * x[:, 0]) * np.sin(np.pi * x[:, 1])\n        j_funY = lambda x: np.pi * np.sin(np.pi * x[:, 0]) * np.cos(np.pi * x[:, 1])\n        q_fun = lambda x: -2 * (np.pi**2) * phi(x)\n\n        xc_ana = phi(self.M.gridCC)\n        q_ana = q_fun(self.M.gridCC)\n        jX_ana = j_funX(self.M.gridFx)\n        jY_ana = j_funY(self.M.gridFy)\n        j_ana = np.r_[jX_ana, jY_ana]\n\n        # TODO: Check where our boundary conditions are CCx or Nx\n\n        cxm, cxp, cym, cyp = self.M.cell_boundary_indices\n        fxm, fxp, fym, fyp = self.M.face_boundary_indices\n\n        gBFx = self.M.gridFx[(fxm | fxp), :]\n        gBFy = self.M.gridFy[(fym | fyp), :]\n\n        phi_bc = phi(np.r_[gBFx, gBFy])\n        j_bc = np.r_[j_funX(gBFx), j_funY(gBFy)]\n\n        # P = sp.csr_matrix(([-1,1],([0,self.M.nF-1],[0,1])), shape=(self.M.nF, 2))\n\n        P, Pin, Pout = self.M.get_BC_projections(\"neumann\")\n\n        Mc = self.M.get_face_inner_product()\n        McI = utils.sdinv(Mc)\n        V = utils.sdiag(self.M.cell_volumes)\n        G = -Pin.T * Pin * self.M.face_divergence.T * V\n        D = self.M.face_divergence\n        j = McI * (G * xc_ana + P * phi_bc)\n        q = V * D * Pin.T * Pin * j + V * D * Pout.T * j_bc\n\n        # Rearrange if we know q to solve for x\n        A = V * D * Pin.T * Pin * McI * G\n        rhs = V * q_ana - V * D * Pin.T * Pin * McI * P * phi_bc - V * D * Pout.T * j_bc\n\n        if self.myTest == \"j\":\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n        elif self.myTest == \"q\":\n            err = np.linalg.norm((q - V * q_ana), np.inf)\n        elif self.myTest == \"xc\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        elif self.myTest == \"xcJ\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            j = McI * (G * xc + P * phi_bc)\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        return err\n\n    def test_orderJ(self):\n        self.name = \"2D - InhomogeneousNeumann_Forward j\"\n        self.myTest = \"j\"\n        self.orderTest()\n\n    def test_orderQ(self):\n        self.name = \"2D - InhomogeneousNeumann_Forward q\"\n        self.myTest = \"q\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"2D - InhomogeneousNeumann_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nclass Test1D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"1D - Mixed\"\n    meshTypes = MESHTYPES\n    meshDimension = 1\n    expectedOrders = 2\n    meshSizes = [4, 8, 16, 32, 64, 128]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.cos(0.5 * np.pi * x)\n        j_fun = lambda x: -0.5 * np.pi * np.sin(0.5 * np.pi * x)\n        q_fun = lambda x: -0.25 * (np.pi**2) * np.cos(0.5 * np.pi * x)\n\n        xc_ana = phi(self.M.gridCC)\n        q_ana = q_fun(self.M.gridCC)\n        j_ana = j_fun(self.M.gridFx)\n\n        # TODO: Check where our boundary conditions are CCx or Nx\n        vecN = self.M.nodes_x\n        vecC = self.M.cell_centers_x\n\n        phi_bc = phi(vecC[[0, -1]])\n        j_bc = j_fun(vecN[[0, -1]])\n\n        P, Pin, Pout = self.M.get_BC_projections([[\"dirichlet\", \"neumann\"]])\n\n        Mc = self.M.get_face_inner_product()\n        McI = utils.sdinv(Mc)\n        V = utils.sdiag(self.M.cell_volumes)\n        G = -Pin.T * Pin * self.M.face_divergence.T * V\n        D = self.M.face_divergence\n        j = McI * (G * xc_ana + P * phi_bc)\n        q = V * D * Pin.T * Pin * j + V * D * Pout.T * j_bc\n\n        # Rearrange if we know q to solve for x\n        A = V * D * Pin.T * Pin * McI * G\n        rhs = V * q_ana - V * D * Pin.T * Pin * McI * P * phi_bc - V * D * Pout.T * j_bc\n        # A = D*McI*G\n        # rhs = q_ana - D*McI*P*phi_bc\n\n        if self.myTest == \"j\":\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n        elif self.myTest == \"q\":\n            err = np.linalg.norm((q - V * q_ana), np.inf)\n        elif self.myTest == \"xc\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        elif self.myTest == \"xcJ\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            j = McI * (G * xc + P * phi_bc)\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        return err\n\n    def test_orderJ(self):\n        self.name = \"1D - InhomogeneousMixed_Forward j\"\n        self.myTest = \"j\"\n        self.orderTest()\n\n    def test_orderQ(self):\n        self.name = \"1D - InhomogeneousMixed_Forward q\"\n        self.myTest = \"q\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"1D - InhomogeneousMixed_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nclass Test2D_InhomogeneousMixed(discretize.tests.OrderTest):\n    name = \"2D - Mixed\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    expectedOrders = 2\n    meshSizes = [2, 4, 8, 16]\n    # meshSizes = [4]\n\n    def getError(self):\n        # Test function\n        phi = lambda x: np.cos(0.5 * np.pi * x[:, 0]) * np.cos(0.5 * np.pi * x[:, 1])\n        j_funX = (\n            lambda x: -0.5\n            * np.pi\n            * np.sin(0.5 * np.pi * x[:, 0])\n            * np.cos(0.5 * np.pi * x[:, 1])\n        )\n        j_funY = (\n            lambda x: -0.5\n            * np.pi\n            * np.cos(0.5 * np.pi * x[:, 0])\n            * np.sin(0.5 * np.pi * x[:, 1])\n        )\n        q_fun = lambda x: -2 * ((0.5 * np.pi) ** 2) * phi(x)\n\n        xc_ana = phi(self.M.gridCC)\n        q_ana = q_fun(self.M.gridCC)\n        jX_ana = j_funX(self.M.gridFx)\n        jY_ana = j_funY(self.M.gridFy)\n        j_ana = np.r_[jX_ana, jY_ana]\n\n        # TODO: Check where our boundary conditions are CCx or Nx\n\n        cxm, cxp, cym, cyp = self.M.cell_boundary_indices\n        fxm, fxp, fym, fyp = self.M.face_boundary_indices\n\n        gBFx = self.M.gridFx[(fxm | fxp), :]\n        gBFy = self.M.gridFy[(fym | fyp), :]\n\n        gBCx = self.M.gridCC[(cxm | cxp), :]\n        gBCy = self.M.gridCC[(cym | cyp), :]\n\n        phi_bc = phi(np.r_[gBCx, gBCy])\n        j_bc = np.r_[j_funX(gBFx), j_funY(gBFy)]\n\n        # P = sp.csr_matrix(([-1,1],([0,self.M.nF-1],[0,1])), shape=(self.M.nF, 2))\n\n        P, Pin, Pout = self.M.get_BC_projections(\n            [[\"dirichlet\", \"neumann\"], [\"dirichlet\", \"neumann\"]]\n        )\n\n        Mc = self.M.get_face_inner_product()\n        McI = utils.sdinv(Mc)\n        V = utils.sdiag(self.M.cell_volumes)\n        G = -Pin.T * Pin * self.M.face_divergence.T * V\n        D = self.M.face_divergence\n        j = McI * (G * xc_ana + P * phi_bc)\n        q = V * D * Pin.T * Pin * j + V * D * Pout.T * j_bc\n\n        # Rearrange if we know q to solve for x\n        A = V * D * Pin.T * Pin * McI * G\n        rhs = V * q_ana - V * D * Pin.T * Pin * McI * P * phi_bc - V * D * Pout.T * j_bc\n\n        if self.myTest == \"j\":\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n        elif self.myTest == \"q\":\n            err = np.linalg.norm((q - V * q_ana), np.inf)\n        elif self.myTest == \"xc\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            err = np.linalg.norm((xc - xc_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        elif self.myTest == \"xcJ\":\n            # TODO: fix the null space\n            xc, info = sp.linalg.minres(A, rhs, rtol=1e-6)\n            j = McI * (G * xc + P * phi_bc)\n            err = np.linalg.norm((Pin * j - Pin * j_ana), np.inf)\n            if info > 0:\n                print(\"Solve does not work well\")\n                print(\"ACCURACY\", np.linalg.norm(utils.mkvc(A * xc) - rhs))\n        return err\n\n    def test_orderJ(self):\n        self.name = \"2D - InhomogeneousMixed_Forward j\"\n        self.myTest = \"j\"\n        self.orderTest()\n\n    def test_orderQ(self):\n        self.name = \"2D - InhomogeneousMixed_Forward q\"\n        self.myTest = \"q\"\n        self.orderTest()\n\n    def test_orderXJ(self):\n        self.name = \"2D - InhomogeneousMixed_Inverse J\"\n        self.myTest = \"xcJ\"\n        self.orderTest()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/cyl/__init__.py",
    "content": "if __name__ == \"__main__\":\n    import glob\n    import unittest\n\n    test_file_strings = glob.glob(\"test_*.py\")\n    module_strings = [strng[0 : len(strng) - 3] for strng in test_file_strings]\n    suites = [\n        unittest.defaultTestLoader.loadTestsFromName(strng) for strng in module_strings\n    ]\n    testSuite = unittest.TestSuite(suites)\n\n    unittest.TextTestRunner(verbosity=2).run(testSuite)\n"
  },
  {
    "path": "tests/cyl/test_cyl.py",
    "content": "import unittest\nimport numpy as np\nimport pytest\n\nimport discretize\nfrom discretize import tests, utils\n\nrng = np.random.default_rng(87564123)\n\n\nclass TestCylSymmetricMesh(unittest.TestCase):\n    def setUp(self):\n        hx = np.r_[1, 1, 0.5]\n        hz = np.r_[2, 1]\n        self.mesh = discretize.CylindricalMesh([hx, 1, hz], np.r_[0.0, 0.0, 0.0])\n\n    def test_vectorsCC(self):\n        v = np.r_[0.5, 1.5, 2.25]\n        self.assertEqual(np.linalg.norm((v - self.mesh.cell_centers_x)), 0)\n        v = np.r_[0]\n        self.assertEqual(np.linalg.norm((v - self.mesh.cell_centers_y)), 0)\n        v = np.r_[1, 2.5]\n        self.assertEqual(np.linalg.norm((v - self.mesh.cell_centers_z)), 0)\n\n    def test_vectorsN(self):\n        v = np.r_[1, 2, 2.5]\n        self.assertEqual(np.linalg.norm((v - self.mesh.nodes_x)), 0)\n        v = np.r_[0]\n        self.assertEqual(np.linalg.norm((v - self.mesh.nodes_y)), 0)\n        v = np.r_[0, 2, 3.0]\n        self.assertEqual(np.linalg.norm((v - self.mesh.nodes_z)), 0)\n\n    def test_edge(self):\n        edge = np.r_[1, 2, 2.5, 1, 2, 2.5, 1, 2, 2.5] * 2 * np.pi\n        self.assertEqual(np.linalg.norm((edge - self.mesh.edge_lengths)), 0)\n\n    def test_area(self):\n        r = np.r_[0, 1, 2, 2.5]\n        a = r[1:] * 2 * np.pi\n        areaX = np.r_[2 * a, a]\n        a = (r[1:] ** 2 - r[:-1] ** 2) * np.pi\n        areaZ = np.r_[a, a, a]\n        area = np.r_[areaX, areaZ]\n        self.assertEqual(np.linalg.norm((area - self.mesh.face_areas)), 0)\n\n    def test_vol(self):\n        r = np.r_[0, 1, 2, 2.5]\n        a = (r[1:] ** 2 - r[:-1] ** 2) * np.pi\n        vol = np.r_[2 * a, a]\n        self.assertEqual(np.linalg.norm((vol - self.mesh.cell_volumes)), 0)\n\n    def test_vol_simple(self):\n        mesh = discretize.CylindricalMesh([1.0, 1.0, 1.0])\n        self.assertEqual(mesh.cell_volumes, np.pi)\n\n        mesh = discretize.CylindricalMesh([2.0, 1.0, 1.0])\n        self.assertTrue(np.all(mesh.cell_volumes == np.pi * np.r_[0.5**2, 1 - 0.5**2]))\n\n    def test_gridSizes(self):\n        self.assertEqual(self.mesh.gridCC.shape, (self.mesh.nC, 3))\n        self.assertEqual(self.mesh._nodes_full.shape, (9, 3))\n\n        self.assertEqual(self.mesh.gridFx.shape, (self.mesh.nFx, 3))\n        self.assertTrue(self.mesh.gridFy is None)\n        self.assertEqual(self.mesh.gridFz.shape, (self.mesh.nFz, 3))\n\n        self.assertTrue(self.mesh.gridEx is None)\n        self.assertEqual(self.mesh.gridEy.shape, (self.mesh.nEy, 3))\n        self.assertTrue(self.mesh.gridEz is None)\n\n    def test_gridCC(self):\n        x = np.r_[0.5, 1.5, 2.25, 0.5, 1.5, 2.25]\n        y = np.zeros(6)\n        z = np.r_[1, 1, 1, 2.5, 2.5, 2.5]\n        G = np.c_[x, y, z]\n        self.assertEqual(np.linalg.norm((G - self.mesh.gridCC).ravel()), 0)\n\n    def test_gridN(self):\n        x = np.r_[1, 2, 2.5, 1, 2, 2.5, 1, 2, 2.5]\n        y = np.zeros(9)\n        z = np.r_[0, 0, 0, 2, 2, 2, 3, 3, 3]\n        G = np.c_[x, y, z]\n        self.assertEqual(np.linalg.norm((G - self.mesh.gridN).ravel()), 0)\n\n    def test_gridFx(self):\n        x = np.r_[1, 2, 2.5, 1, 2, 2.5]\n        y = np.zeros(6)\n        z = np.r_[1, 1, 1, 2.5, 2.5, 2.5]\n        G = np.c_[x, y, z]\n        self.assertEqual(np.linalg.norm((G - self.mesh.gridFx).ravel()), 0)\n\n    def test_gridFz(self):\n        x = np.r_[0.5, 1.5, 2.25, 0.5, 1.5, 2.25, 0.5, 1.5, 2.25]\n        y = np.zeros(9)\n        z = np.r_[0, 0, 0, 2, 2, 2, 3, 3, 3.0]\n        G = np.c_[x, y, z]\n        self.assertEqual(np.linalg.norm((G - self.mesh.gridFz).ravel()), 0)\n\n    def test_gridEy(self):\n        x = np.r_[1, 2, 2.5, 1, 2, 2.5, 1, 2, 2.5]\n        y = np.zeros(9)\n        z = np.r_[0, 0, 0, 2, 2, 2, 3, 3, 3.0]\n        G = np.c_[x, y, z]\n        self.assertEqual(np.linalg.norm((G - self.mesh.gridEy).ravel()), 0)\n\n    def test_lightOperators(self):\n        self.assertTrue(self.mesh.nodal_gradient is None)\n\n    def test_getInterpMatCartMesh_Cells(self):\n        Mr = discretize.TensorMesh([100, 100, 2], x0=\"CC0\")\n        Mc = discretize.CylindricalMesh(\n            [np.ones(10) / 5, 1, 10], x0=\"0C0\", cartesian_origin=[-0.2, -0.2, 0]\n        )\n\n        mc = np.arange(Mc.nC)\n        xr = np.linspace(0, 0.4, 50)\n        xc = np.linspace(0, 0.4, 50) + 0.2\n        Pr = Mr.get_interpolation_matrix(\n            np.c_[xr, np.ones(50) * -0.2, np.ones(50) * 0.5], \"CC\"\n        )\n        Pc = Mc.get_interpolation_matrix(\n            np.c_[xc, np.zeros(50), np.ones(50) * 0.5], \"CC\"\n        )\n        Pc2r = Mc.get_interpolation_matrix_cartesian_mesh(Mr, \"CC\")\n\n        assert np.abs(Pr * (Pc2r * mc) - Pc * mc).max() < 1e-3\n\n    def test_getInterpMatCartMesh_Cells2Nodes(self):\n        Mr = discretize.TensorMesh([100, 100, 2], x0=\"CC0\")\n        Mc = discretize.CylindricalMesh(\n            [np.ones(10) / 5, 1, 10], x0=\"0C0\", cartesian_origin=[-0.2, -0.2, 0]\n        )\n\n        mc = np.arange(Mc.nC)\n        xr = np.linspace(0, 0.4, 50)\n        xc = np.linspace(0, 0.4, 50) + 0.2\n        Pr = Mr.get_interpolation_matrix(\n            np.c_[xr, np.ones(50) * -0.2, np.ones(50) * 0.5], \"N\"\n        )\n        Pc = Mc.get_interpolation_matrix(\n            np.c_[xc, np.zeros(50), np.ones(50) * 0.5], \"CC\"\n        )\n        Pc2r = Mc.get_interpolation_matrix_cartesian_mesh(\n            Mr, \"CC\", location_type_to=\"N\"\n        )\n\n        assert np.abs(Pr * (Pc2r * mc) - Pc * mc).max() < 1e-3\n\n    def test_getInterpMatCartMesh_Faces(self):\n        Mr = discretize.TensorMesh([100, 100, 2], x0=\"CC0\")\n        Mc = discretize.CylindricalMesh(\n            [np.ones(10) / 5, 1, 10], x0=\"0C0\", cartesian_origin=[-0.2, -0.2, 0]\n        )\n\n        Pf = Mc.get_interpolation_matrix_cartesian_mesh(Mr, \"F\")\n        mf = np.ones(Mc.nF)\n\n        frect = Pf * mf\n\n        fxcc = Mr.aveFx2CC * Mr.reshape(frect, \"F\", \"Fx\")\n        fycc = Mr.aveFy2CC * Mr.reshape(frect, \"F\", \"Fy\")\n        fzcc = Mr.reshape(frect, \"F\", \"Fz\")\n\n        indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0]\n        indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0]\n\n        TOL = 1e-2\n        assert np.abs(float(fxcc[indX]) - 1) < TOL\n        assert np.abs(float(fxcc[indY]) - 0) < TOL\n        assert np.abs(float(fycc[indX]) - 0) < TOL\n        assert np.abs(float(fycc[indY]) - 1) < TOL\n        assert np.abs((fzcc - 1).sum()) < TOL\n\n        mag = (fxcc**2 + fycc**2) ** 0.5\n        dist = ((Mr.gridCC[:, 0] + 0.2) ** 2 + (Mr.gridCC[:, 1] + 0.2) ** 2) ** 0.5\n\n        assert np.abs(mag[dist > 0.1].max() - 1) < TOL\n        assert np.abs(mag[dist > 0.1].min() - 1) < TOL\n\n    def test_getInterpMatCartMesh_Faces2Edges(self):\n        Mr = discretize.TensorMesh([100, 100, 2], x0=\"CC0\")\n        Mc = discretize.CylindricalMesh(\n            [np.ones(10) / 5, 1, 10], x0=\"0C0\", cartesian_origin=[-0.2, -0.2, 0]\n        )\n\n        self.assertTrue((Mc.cartesian_origin == [-0.2, -0.2, 0]).all())\n\n        Pf2e = Mc.get_interpolation_matrix_cartesian_mesh(Mr, \"F\", location_type_to=\"E\")\n        mf = np.ones(Mc.nF)\n\n        ecart = Pf2e * mf\n\n        excc = Mr.aveEx2CC * Mr.reshape(ecart, \"E\", \"Ex\")\n        eycc = Mr.aveEy2CC * Mr.reshape(ecart, \"E\", \"Ey\")\n        ezcc = Mr.reshape(ecart, \"E\", \"Ez\")\n\n        indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0]\n        indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0]\n\n        TOL = 1e-2\n        assert np.abs(float(excc[indX]) - 1) < TOL\n        assert np.abs(float(excc[indY]) - 0) < TOL\n        assert np.abs(float(eycc[indX]) - 0) < TOL\n        assert np.abs(float(eycc[indY]) - 1) < TOL\n        assert np.abs((ezcc - 1).sum()) < TOL\n\n        mag = (excc**2 + eycc**2) ** 0.5\n        dist = ((Mr.gridCC[:, 0] + 0.2) ** 2 + (Mr.gridCC[:, 1] + 0.2) ** 2) ** 0.5\n\n        assert np.abs(mag[dist > 0.1].max() - 1) < TOL\n        assert np.abs(mag[dist > 0.1].min() - 1) < TOL\n\n    def test_getInterpMatCartMesh_Edges(self):\n        Mr = discretize.TensorMesh([100, 100, 2], x0=\"CC0\")\n        Mc = discretize.CylindricalMesh(\n            [np.ones(10) / 5, 1, 10], x0=\"0C0\", cartesian_origin=[-0.2, -0.2, 0]\n        )\n\n        Pe = Mc.get_interpolation_matrix_cartesian_mesh(Mr, \"E\")\n        me = np.ones(Mc.nE)\n\n        ecart = Pe * me\n\n        excc = Mr.aveEx2CC * Mr.reshape(ecart, \"E\", \"Ex\")\n        eycc = Mr.aveEy2CC * Mr.reshape(ecart, \"E\", \"Ey\")\n        ezcc = Mr.aveEz2CC * Mr.reshape(ecart, \"E\", \"Ez\")\n\n        indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0]\n        indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0]\n\n        TOL = 1e-2\n        assert np.abs(float(excc[indX]) - 0) < TOL\n        assert np.abs(float(excc[indY]) + 1) < TOL\n        assert np.abs(float(eycc[indX]) - 1) < TOL\n        assert np.abs(float(eycc[indY]) - 0) < TOL\n        assert np.abs(ezcc.sum()) < TOL\n\n        mag = (excc**2 + eycc**2) ** 0.5\n        dist = ((Mr.gridCC[:, 0] + 0.2) ** 2 + (Mr.gridCC[:, 1] + 0.2) ** 2) ** 0.5\n\n        assert np.abs(mag[dist > 0.1].max() - 1) < TOL\n        assert np.abs(mag[dist > 0.1].min() - 1) < TOL\n\n    def test_getInterpMatCartMesh_Edges2Faces(self):\n        Mr = discretize.TensorMesh([100, 100, 2], x0=\"CC0\")\n        Mc = discretize.CylindricalMesh(\n            [np.ones(10) / 5, 1, 10], x0=\"0C0\", cartesian_origin=[-0.2, -0.2, 0]\n        )\n\n        Pe2f = Mc.get_interpolation_matrix_cartesian_mesh(Mr, \"E\", location_type_to=\"F\")\n        me = np.ones(Mc.nE)\n\n        frect = Pe2f * me\n\n        excc = Mr.aveFx2CC * Mr.reshape(frect, \"F\", \"Fx\")\n        eycc = Mr.aveFy2CC * Mr.reshape(frect, \"F\", \"Fy\")\n        ezcc = Mr.reshape(frect, \"F\", \"Fz\")\n\n        indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0]\n        indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0]\n\n        TOL = 1e-2\n        assert np.abs(float(excc[indX]) - 0) < TOL\n        assert np.abs(float(excc[indY]) + 1) < TOL\n        assert np.abs(float(eycc[indX]) - 1) < TOL\n        assert np.abs(float(eycc[indY]) - 0) < TOL\n        assert np.abs(ezcc.sum()) < TOL\n\n        mag = (excc**2 + eycc**2) ** 0.5\n        dist = ((Mr.gridCC[:, 0] + 0.2) ** 2 + (Mr.gridCC[:, 1] + 0.2) ** 2) ** 0.5\n\n        assert np.abs(mag[dist > 0.1].max() - 1) < TOL\n        assert np.abs(mag[dist > 0.1].min() - 1) < TOL\n\n    def test_serialization(self):\n        mesh = discretize.CylindricalMesh.deserialize(self.mesh.serialize())\n        self.assertTrue(np.all(self.mesh.x0 == mesh.x0))\n        self.assertTrue(np.all(self.mesh.shape_cells == mesh.shape_cells))\n        self.assertTrue(np.all(self.mesh.h[0] == mesh.h[0]))\n        self.assertTrue(np.all(self.mesh.h[1] == mesh.h[1]))\n        self.assertTrue(np.all(self.mesh.h[2] == mesh.h[2]))\n        self.assertTrue(np.all(self.mesh.gridCC == mesh.gridCC))\n\n\nMESHTYPES = [\"uniform_symmetric_CylMesh\"]\ncall2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 2])\ncall3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\ncyl_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)]\ncyl_row3 = lambda g, xfun, yfun, zfun: np.c_[\n    call3(xfun, g), call3(yfun, g), call3(zfun, g)\n]\ncylF2 = lambda M, fx, fy: np.vstack(\n    (cyl_row2(M.gridFx, fx, fy), cyl_row2(M.gridFz, fx, fy))\n)\n\n\nclass TestFaceDiv2D(tests.OrderTest):\n    name = \"FaceDiv\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n\n    def getError(self):\n        funR = lambda r, z: np.sin(2.0 * np.pi * r)\n        funZ = lambda r, z: np.sin(2.0 * np.pi * z)\n\n        sol = lambda r, t, z: (\n            2 * np.pi * r * np.cos(2 * np.pi * r) + np.sin(2 * np.pi * r)\n        ) / r + 2 * np.pi * np.cos(2 * np.pi * z)\n\n        Fc = cylF2(self.M, funR, funZ)\n        Fc = np.c_[Fc[:, 0], np.zeros(self.M.nF), Fc[:, 1]]\n        F = self.M.project_face_vector(Fc)\n\n        divF = self.M.face_divergence.dot(F)\n        divF_ana = call3(sol, self.M.gridCC)\n\n        err = np.linalg.norm((divF - divF_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestEdgeCurl2D(tests.OrderTest):\n    name = \"EdgeCurl\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n\n    def getError(self):\n        # To Recreate or change the functions:\n\n        # import sympy\n        # r, t, z = sympy.symbols('r, t, z')\n\n        # fR = 0\n        # fZ = 0\n        # fT = sympy.sin(2.*sympy.pi*z)\n\n        # print(1/r*sympy.diff(fZ, t) - sympy.diff(fT, z))\n        # print(sympy.diff(fR, z) - sympy.diff(fZ, r))\n        # print(1/r*(sympy.diff(r*fT, r) - sympy.diff(fR, t)))\n\n        funT = lambda r, t, z: np.sin(2.0 * np.pi * z)\n\n        solR = lambda r, z: -2.0 * np.pi * np.cos(2.0 * np.pi * z)\n        solZ = lambda r, z: np.sin(2.0 * np.pi * z) / r\n\n        E = call3(funT, self.M.gridEy)\n\n        curlE = self.M.edge_curl.dot(E)\n\n        Fc = cylF2(self.M, solR, solZ)\n        Fc = np.c_[Fc[:, 0], np.zeros(self.M.nF), Fc[:, 1]]\n        curlE_ana = self.M.project_face_vector(Fc)\n\n        err = np.linalg.norm((curlE - curlE_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCellGrad2D_Dirichlet(unittest.TestCase):\n    # name = \"Cell Grad 2 - Dirichlet\"\n    # meshTypes = MESHTYPES\n    # meshDimension = 2\n    # meshSizes = [8, 16, 32, 64]\n\n    # def getError(self):\n    #     #Test function\n    #     fx = lambda x, z: -2*np.pi*np.sin(2*np.pi*x)*np.cos(2*np.pi*z)\n    #     fz = lambda x, z: -2*np.pi*np.sin(2*np.pi*z)*np.cos(2*np.pi*x)\n    #     sol = lambda x, z: np.cos(2*np.pi*x)*np.cos(2*np.pi*z)\n\n    #     xc = call2(sol, self.M.gridCC)\n\n    #     Fc = cylF2(self.M, fx, fz)\n    #     Fc = np.c_[Fc[:, 0], np.zeros(self.M.nF), Fc[:, 1]]\n    #     gradX_ana = self.M.project_face_vector(Fc)\n\n    #     gradX = self.M.cell_gradient.dot(xc)\n\n    #     err = np.linalg.norm((gradX-gradX_ana), np.inf)\n\n    #     return err\n\n    # def test_order(self):\n    #     self.orderTest()\n\n    def setUp(self):\n        hx = rng.random(10)\n        hz = rng.random(10)\n        self.mesh = discretize.CylindricalMesh([hx, 1, hz])\n\n    def test_NotImplementedError(self):\n        with self.assertRaises(NotImplementedError):\n            self.mesh.cell_gradient\n\n\nclass TestAveragingSimple(unittest.TestCase):\n    def setUp(self):\n        hx = rng.random(10)\n        hz = rng.random(10)\n        self.mesh = discretize.CylindricalMesh([hx, 1, hz])\n\n    def test_simpleEdges(self):\n        edge_vec = self.mesh.gridEy[:, 0]\n        assert (\n            np.linalg.norm(self.mesh.aveE2CC * edge_vec - self.mesh.gridCC[:, 0])\n            < 1e-10\n        )\n        assert (\n            np.linalg.norm(self.mesh.aveE2CCV * edge_vec - self.mesh.gridCC[:, 0])\n            < 1e-10\n        )\n\n    def test_constantFaces(self):\n        face_vec = np.hstack([self.mesh.gridFx[:, 0], self.mesh.gridFz[:, 0]])\n        assert np.linalg.norm(\n            self.mesh.aveF2CCV * face_vec - np.hstack(2 * [self.mesh.gridCC[:, 2]])\n        )\n\n\nclass TestAveE2CC(tests.OrderTest):\n    name = \"aveE2CC\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        fun = lambda r, t, z: np.sin(2.0 * np.pi * z) * np.sin(np.pi * r)\n\n        E = call3(fun, self.M.gridEy)\n\n        aveE = self.M.aveE2CC * E\n        aveE_ana = call3(fun, self.M.gridCC)\n\n        err = np.linalg.norm((aveE - aveE_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestAveE2CCV(tests.OrderTest):\n    name = \"aveE2CCV\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n    meshSizes = [8, 16, 32, 64]\n\n    def getError(self):\n        fun = lambda r, t, z: np.sin(2.0 * np.pi * z) * np.sin(2 * np.pi * r)\n\n        E = call3(fun, self.M.gridEy)\n\n        aveE = self.M.aveE2CCV * E\n        aveE_ana = call3(fun, self.M.gridCC)\n\n        err = np.linalg.norm((aveE - aveE_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestAveF2CCV(tests.OrderTest):\n    name = \"aveF2CCV\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n\n    def getError(self):\n        funR = lambda r, z: np.sin(2.0 * np.pi * z) * np.sin(np.pi * r)\n        funZ = lambda r, z: np.sin(3.0 * np.pi * z) * np.sin(2.0 * np.pi * r)\n\n        Fc = cylF2(self.M, funR, funZ)\n        Fc = np.c_[Fc[:, 0], np.zeros(self.M.nF), Fc[:, 1]]\n        F = self.M.project_face_vector(Fc)\n\n        aveF = self.M.aveF2CCV * F\n\n        aveF_anaR = funR(self.M.gridCC[:, 0], self.M.gridCC[:, 2])\n        aveF_anaZ = funZ(self.M.gridCC[:, 0], self.M.gridCC[:, 2])\n\n        aveF_ana = np.hstack([aveF_anaR, aveF_anaZ])\n\n        err = np.linalg.norm((aveF - aveF_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestAveF2CC(tests.OrderTest):\n    name = \"aveF2CC\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n\n    def getError(self):\n        fun = lambda r, z: np.sin(2.0 * np.pi * z) * np.sin(np.pi * r)\n\n        Fc = cylF2(self.M, fun, fun)\n        Fc = np.c_[Fc[:, 0], np.zeros(self.M.nF), Fc[:, 1]]\n        F = self.M.project_face_vector(Fc)\n\n        aveF = self.M.aveF2CC * F\n        aveF_ana = fun(self.M.gridCC[:, 0], self.M.gridCC[:, 2])\n\n        err = np.linalg.norm((aveF - aveF_ana), np.inf)\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\n# class TestInnerProducts2D(tests.OrderTest):\n#     \"\"\"Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.\"\"\"\n#\n#     meshTypes = MESHTYPES\n#     meshDimension = 3\n#     meshSizes = [4, 8, 16, 32, 64, 128]\n#\n#     def getError(self):\n#         funR = lambda r, t, z: np.cos(2.0 * np.pi * z)\n#         funT = lambda r, t, z: 0 * t\n#         funZ = lambda r, t, z: np.sin(2.0 * np.pi * r)\n#\n\n#         call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\n\n#         sigma1 = lambda r, t, z: z+1\n#         sigma2 = lambda r, t, z: r*z+50\n#         sigma3 = lambda r, t, z: 3+t*r\n#         sigma4 = lambda r, t, z: 0.1*r*t*z\n#         sigma5 = lambda r, t, z: 0.2*z*r*t\n#         sigma6 = lambda r, t, z: 0.1*t\n\n#         Gc = self.M.gridCC\n#         if self.sigmaTest == 1:\n#             sigma = np.c_[call(sigma1, Gc)]\n#             analytic = 144877./360  # Found using sympy. z=5\n#         elif self.sigmaTest == 2:\n#             sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc)]\n#             analytic = 189959./120  # Found using sympy. z=5\n#         elif self.sigmaTest == 3:\n#             sigma = np.r_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc)]\n#             analytic = 781427./360  # Found using sympy. z=5\n\n#         if self.location == 'edges':\n#             E = call(funT, self.M.gridEy)\n#             A = self.M.get_edge_inner_product(sigma)\n#             numeric = E.T.dot(A.dot(E))\n#         elif self.location == 'faces':\n#             Fr = call(funR, self.M.gridFx)\n#             Fz = call(funZ, self.M.gridFz)\n#             A = self.M.get_face_inner_product(sigma)\n#             F = np.r_[Fr, Fz]\n#             numeric = F.T.dot(A.dot(F))\n\n#         print(numeric)\n#         err = np.abs(numeric - analytic)\n#         return err\n\n#     def test_order1_faces(self):\n#         self.name = \"2D Face Inner Product - Isotropic\"\n#         self.location = 'faces'\n#         self.sigmaTest = 1\n#         self.orderTest()\n\n\nclass TestCyl3DMesh(unittest.TestCase):\n    def setUp(self):\n        hx = np.r_[1, 1, 0.5]\n        hy = np.r_[np.pi, np.pi]\n        hz = np.r_[2, 1]\n        self.mesh = discretize.CylindricalMesh([hx, hy, hz])\n\n    def test_vectorsCC(self):\n        v = np.r_[0.5, 1.5, 2.25]\n        self.assertEqual(np.linalg.norm((v - self.mesh.cell_centers_x)), 0)\n        v = np.r_[0, np.pi] + np.pi / 2\n        self.assertEqual(np.linalg.norm((v - self.mesh.cell_centers_y)), 0)\n        v = np.r_[1, 2.5]\n        self.assertEqual(np.linalg.norm((v - self.mesh.cell_centers_z)), 0)\n\n    def test_vectorsN(self):\n        v = np.r_[0, 1, 2, 2.5]\n        self.assertEqual(np.linalg.norm((v - self.mesh.nodes_x)), 0)\n        v = np.r_[0.0, np.pi]\n        self.assertEqual(np.linalg.norm((v - self.mesh.nodes_y)), 0)\n        v = np.r_[0, 2, 3]\n        self.assertEqual(np.linalg.norm((v - self.mesh.nodes_z)), 0)\n\n\ndef test_non_sym_errors():\n    with pytest.raises(ValueError, match=r\"more than 2\\*pi.\"):\n        discretize.CylindricalMesh([5, np.ones(8), 4])\n\n    mesh = discretize.CylindricalMesh([5, 5, 5])\n    # bad cartesian setter\n    with pytest.raises(ValueError, match=\"cartesian origin\"):\n        mesh.cartesian_origin = [0, 1]\n\n    # no cell gradients\n    with pytest.raises(NotImplementedError, match=\"Cell Grad is not yet implemented.\"):\n        mesh.cell_gradient_x\n\n    # no cell gradients\n    with pytest.raises(NotImplementedError, match=\"Cell Grad is not yet implemented.\"):\n        mesh.stencil_cell_gradient_y\n\n    # no cell gradients\n    with pytest.raises(NotImplementedError, match=\"Cell Grad is not yet implemented.\"):\n        mesh.stencil_cell_gradient_z\n\n    # no cell gradients\n    with pytest.raises(NotImplementedError, match=\"Cell Grad is not yet implemented.\"):\n        mesh.stencil_cell_gradient\n\n    # no cell gradients\n    with pytest.raises(NotImplementedError, match=\"Cell Grad is not yet implemented.\"):\n        mesh.cell_gradient\n\n    # bad deflation key\n\n    # no cell gradients\n    with pytest.raises(ValueError, match=\"Location must be a grid location\"):\n        mesh._deflation_matrix(\"CCV\")\n\n    # bad location type\n    with pytest.raises(ValueError, match=\"Unrecognized location type\"):\n        mesh.get_interpolation_matrix([0, 0, 0], \"bad\")\n\n    with pytest.raises(\n        NotImplementedError, match=\"for more complicated CylindricalMeshes\"\n    ):\n        mesh.get_interpolation_matrix_cartesian_mesh([0, 0, 0], \"edge_x\")\n\n\ndef test_sym_errors():\n    mesh = discretize.CylindricalMesh([5, 1, 5])\n    # no full edge lengths for symmetric\n    with pytest.raises(NotImplementedError):\n        mesh._edge_lengths_full\n\n    # no y faces\n    with pytest.raises(\n        AttributeError, match=\"There are no y-faces on the Cyl Symmetric mesh\"\n    ):\n        mesh.face_y_areas\n\n    # no x edges\n    with pytest.raises(\n        AttributeError, match=\"There are no x-edges on a cyl symmetric mesh\"\n    ):\n        mesh.average_edge_x_to_cell\n\n    # no z edges\n    with pytest.raises(\n        AttributeError, match=\"There are no z-edges on a cyl symmetric mesh\"\n    ):\n        mesh.average_edge_z_to_cell\n\n    # no items for symmetric\n    with pytest.raises(ValueError, match=\"Symmetric CylindricalMesh does not support\"):\n        mesh.get_interpolation_matrix([0, 0, 0], \"edges_x\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/cyl/test_cyl3D.py",
    "content": "import unittest\nimport numpy as np\n\nimport discretize\nfrom discretize import utils\n\nTOL = 1e-1\n\n\nclass TestCyl3DGeometries(unittest.TestCase):\n    def setUp(self):\n        hx = utils.unpack_widths([(1, 1)])\n        htheta = utils.unpack_widths([(1.0, 4)])\n        htheta = htheta * 2 * np.pi / htheta.sum()\n        hz = hx\n\n        self.mesh = discretize.CylindricalMesh([hx, htheta, hz])\n\n    def test_areas(self):\n        area = self.mesh.face_areas\n        self.assertTrue(self.mesh.nF == len(area))\n        self.assertTrue(\n            area[: self.mesh.vnF[0]].sum()\n            == 2 * np.pi * self.mesh.h[0] * self.mesh.h[2]\n        )\n        self.assertTrue(\n            np.all(\n                area[self.mesh.vnF[0] : self.mesh.vnF[1]]\n                == self.mesh.h[0] * self.mesh.h[2]\n            )\n        )\n        self.assertTrue(\n            np.all(\n                area[sum(self.mesh.vnF[:2]) :]\n                == np.pi * self.mesh.h[0] ** 2 / self.mesh.shape_cells[1]\n            )\n        )\n\n    def test_edges(self):\n        edge = self.mesh.edge_lengths\n        self.assertTrue(self.mesh.nE == len(edge))\n        self.assertTrue(np.all(edge[: self.mesh.vnF[0]] == self.mesh.h[0]))\n        self.assertTrue(\n            np.all(\n                self.mesh.edge_lengths[self.mesh.vnE[0] : sum(self.mesh.vnE[:2])]\n                == np.kron(\n                    np.ones(self.mesh.shape_cells[2] + 1),\n                    self.mesh.h[0] * self.mesh.h[1],\n                )\n            )\n        )\n        self.assertTrue(\n            np.all(\n                self.mesh.edge_lengths[self.mesh.vnE[0] : sum(self.mesh.vnE[:2])]\n                == np.kron(\n                    np.ones(self.mesh.shape_cells[2] + 1),\n                    self.mesh.h[0] * self.mesh.h[1],\n                )\n            )\n        )\n        self.assertTrue(\n            np.all(\n                self.mesh.edge_lengths[sum(self.mesh.vnE[:2]) :]\n                == np.kron(self.mesh.h[2], np.ones(self.mesh.shape_cells[1] + 1))\n            )\n        )\n\n    def test_vol(self):\n        self.assertTrue(\n            self.mesh.cell_volumes.sum() == np.pi * self.mesh.h[0] ** 2 * self.mesh.h[2]\n        )\n        self.assertTrue(\n            np.all(\n                self.mesh.cell_volumes\n                == np.pi\n                * self.mesh.h[0] ** 2\n                * self.mesh.h[2]\n                / self.mesh.shape_cells[1]\n            )\n        )\n\n\ndef test_boundary_items():\n    mesh = discretize.CylindricalMesh([3, 4, 5])\n\n    # Nodes\n    is_bn = (mesh.nodes[:, 0] == 1) | (mesh.nodes[:, 2] == 0) | (mesh.nodes[:, 2] == 1)\n    np.testing.assert_equal(mesh.boundary_nodes, mesh.nodes[is_bn])\n    P_bn = mesh.project_node_to_boundary_node\n    np.testing.assert_equal(mesh.boundary_nodes, P_bn @ mesh.nodes)\n\n    # Edges\n    is_be = (mesh.edges[:, 0] == 1) | (mesh.edges[:, 2] == 0) | (mesh.edges[:, 2] == 1)\n    np.testing.assert_equal(mesh.boundary_edges, mesh.edges[is_be])\n    P_be = mesh.project_edge_to_boundary_edge\n    np.testing.assert_equal(mesh.boundary_edges, P_be @ mesh.edges)\n\n    # Faces\n    is_bf = (mesh.faces[:, 0] == 1) | (mesh.faces[:, 2] == 0) | (mesh.faces[:, 2] == 1)\n    np.testing.assert_equal(mesh.boundary_faces, mesh.faces[is_bf])\n    P_bf = mesh.project_face_to_boundary_face\n    np.testing.assert_equal(mesh.boundary_faces, P_bf @ mesh.faces)\n\n\n# ----------------------- Test Grids and Counting --------------------------- #\n\n\nclass Cyl3DGrid(unittest.TestCase):\n    def setUp(self):\n        self.mesh = discretize.CylindricalMesh([2, 4, 1])\n\n    def test_counting(self):\n        mesh = self.mesh\n\n        # cell centers\n        self.assertEqual(mesh.nC, 8)\n        self.assertEqual(mesh.shape_cells[0], 2)\n        self.assertEqual(mesh.shape_cells[1], 4)\n        self.assertEqual(mesh.shape_cells[2], 1)\n        self.assertEqual(mesh.vnC, (2, 4, 1))\n\n        # faces\n        self.assertEqual(mesh.nFx, 8)\n        self.assertEqual(mesh.nFy, 8)\n        self.assertEqual(mesh.nFz, 16)\n        self.assertEqual(mesh.nF, 32)\n        self.assertEqual(mesh.vnFx, (2, 4, 1))\n        self.assertEqual(mesh.vnFy, (2, 4, 1))\n        self.assertEqual(mesh.vnFz, (2, 4, 2))\n        self.assertEqual(mesh.vnF, (8, 8, 16))\n\n        # edges\n        self.assertEqual(mesh.nEx, 16)\n        self.assertEqual(mesh.nEy, 16)\n        self.assertEqual(mesh.nEz, 9)  # there is an edge at the center\n        self.assertEqual(mesh.nE, 41)\n        self.assertEqual(mesh.vnEx, (2, 4, 2))\n        self.assertEqual(mesh.vnEy, (2, 4, 2))\n        self.assertEqual(mesh.vnEz, (3, 4, 1))\n        self.assertEqual(mesh.vnE, (16, 16, 9))\n        self.assertNotEqual(np.prod(mesh.vnEz), mesh.nEz)  # periodic boundary condition\n\n        # nodes\n        self.assertEqual(mesh.shape_nodes[0], 3)\n        self.assertEqual(mesh.shape_nodes[1], 4)\n        self.assertEqual(mesh.shape_nodes[2], 2)\n        self.assertEqual(mesh.vnN, (3, 4, 2))\n        self.assertEqual(mesh.nN, 18)\n        self.assertNotEqual(mesh.nN, np.prod(mesh.vnN))  # periodic boundary condition\n\n    def test_gridCC(self):\n        mesh = self.mesh\n\n        # Cell centers\n        self.assertTrue((mesh.cell_centers_x == [0.25, 0.75]).all())\n        self.assertTrue(\n            (\n                mesh.cell_centers_y\n                == 2.0 * np.pi * np.r_[1.0 / 8.0, 3.0 / 8.0, 5.0 / 8.0, 7.0 / 8.0]\n            ).all()\n        )\n        self.assertTrue(mesh.cell_centers_z == 0.5)\n\n        self.assertTrue((mesh.gridCC[:, 0] == 4 * [0.25, 0.75]).all())\n        self.assertTrue(\n            (\n                mesh.gridCC[:, 1]\n                == 2.0\n                * np.pi\n                * np.r_[\n                    1.0 / 8.0,\n                    1.0 / 8.0,\n                    3.0 / 8.0,\n                    3.0 / 8.0,\n                    5.0 / 8.0,\n                    5.0 / 8.0,\n                    7.0 / 8.0,\n                    7.0 / 8.0,\n                ]\n            ).all()\n        )\n        self.assertTrue((mesh.gridCC[:, 2] == 8 * [0.5]).all())\n\n    def test_gridN(self):\n        mesh = self.mesh\n\n        # Nodes\n        self.assertTrue((mesh.nodes_x == [0.0, 0.5, 1.0]).all())\n        self.assertTrue((mesh.nodes_y == 2 * np.pi * np.r_[0.0, 0.25, 0.5, 0.75]).all())\n        self.assertTrue((mesh.nodes_z == np.r_[0.0, 1.0]).all())\n\n        self.assertTrue(\n            (\n                mesh.gridN[:, 0] == 2 * [0.0, 0.5, 1.0, 0.5, 1.0, 0.5, 1.0, 0.5, 1.0]\n            ).all()\n        )\n        self.assertTrue(\n            (\n                mesh.gridN[:, 1]\n                == 2\n                * np.pi\n                * np.hstack(\n                    2 * [3 * [0.0], 2 * [1.0 / 4.0], 2 * [1.0 / 2.0], 2 * [3.0 / 4.0]]\n                )\n            ).all()\n        )\n        self.assertTrue((mesh.gridN[:, 2] == 9 * [0.0] + 9 * [1.0]).all())\n\n    def test_gridFx(self):\n        mesh = self.mesh\n\n        # x-faces\n        self.assertTrue((mesh.gridFx[:, 0] == 4 * [0.5, 1.0]).all())\n        self.assertTrue(\n            (\n                mesh.gridFx[:, 1]\n                == 2\n                * np.pi\n                * np.hstack(\n                    [2 * [1.0 / 8.0], 2 * [3.0 / 8.0], 2 * [5.0 / 8.0], 2 * [7.0 / 8.0]]\n                )\n            ).all()\n        )\n        self.assertTrue((mesh.gridFx[:, 2] == 8 * [0.5]).all())\n\n    def test_gridFy(self):\n        mesh = self.mesh\n\n        # y-faces\n        self.assertTrue((mesh.gridFy[:, 0] == 4 * [0.25, 0.75]).all())\n        self.assertTrue(\n            (\n                mesh.gridFy[:, 1]\n                == 2\n                * np.pi\n                * np.hstack(\n                    [2 * [0.0], 2 * [1.0 / 4.0], 2 * [1.0 / 2.0], 2 * [3.0 / 4.0]]\n                )\n            ).all()\n        )\n        self.assertTrue((mesh.gridFy[:, 2] == 8 * [0.5]).all())\n\n    def test_gridFz(self):\n        mesh = self.mesh\n\n        # z-faces\n        self.assertTrue((mesh.gridFz[:, 0] == 8 * [0.25, 0.75]).all())\n        self.assertTrue(\n            (\n                mesh.gridFz[:, 1]\n                == 2\n                * np.pi\n                * np.hstack(\n                    2\n                    * [\n                        2 * [1.0 / 8.0],\n                        2 * [3.0 / 8.0],\n                        2 * [5.0 / 8.0],\n                        2 * [7.0 / 8.0],\n                    ]\n                )\n            ).all()\n        )\n        self.assertTrue((mesh.gridFz[:, 2] == np.hstack([8 * [0.0], 8 * [1.0]])).all())\n\n    def test_gridEx(self):\n        mesh = self.mesh\n\n        # x-edges\n        self.assertTrue((mesh.gridEx[:, 0] == 8 * [0.25, 0.75]).all())\n        self.assertTrue(\n            (\n                mesh.gridEx[:, 1]\n                == 2\n                * np.pi\n                * np.hstack(\n                    2 * [2 * [0.0], 2 * [1.0 / 4.0], 2 * [1.0 / 2.0], 2 * [3.0 / 4.0]]\n                )\n            ).all()\n        )\n        self.assertTrue((mesh.gridEx[:, 2] == np.hstack([8 * [0.0], 8 * [1.0]])).all())\n\n    def test_gridEy(self):\n        mesh = self.mesh\n\n        # y-edges\n        self.assertTrue((mesh.gridEy[:, 0] == 8 * [0.5, 1.0]).all())\n        self.assertTrue(\n            (\n                mesh.gridEy[:, 1]\n                == 2\n                * np.pi\n                * np.hstack(\n                    2\n                    * [\n                        2 * [1.0 / 8.0],\n                        2 * [3.0 / 8.0],\n                        2 * [5.0 / 8.0],\n                        2 * [7.0 / 8.0],\n                    ]\n                )\n            ).all()\n        )\n        self.assertTrue((mesh.gridEy[:, 2] == np.hstack([8 * [0.0], 8 * [1.0]])).all())\n\n    def test_gridEz(self):\n        mesh = self.mesh\n\n        # z-edges\n        self.assertTrue(\n            (mesh.gridEz[:, 0] == np.hstack([[0.0, 0.5, 1.0] + 3 * [0.5, 1.0]])).all()\n        )\n        self.assertTrue(\n            (\n                mesh.gridEz[:, 1]\n                == 2\n                * np.pi\n                * np.hstack(\n                    [3 * [0.0], 2 * [1.0 / 4.0], 2 * [1.0 / 2.0], 2 * [3.0 / 4.0]]\n                )\n            ).all()\n        )\n        self.assertTrue((mesh.gridEz[:, 2] == 9 * [0.5]).all())\n\n\n# ------------------- Test conversion to Cartesian ----------------------- #\n\n\nclass TestCartesianGrid(unittest.TestCase):\n    def test_cartesianGrid(self):\n        mesh = discretize.CylindricalMesh([1, 4, 1])\n\n        root2over2 = np.sqrt(2.0) / 2.0\n\n        # cell centers\n        cartCC = mesh.cartesian_grid(\"CC\")\n        self.assertTrue(\n            np.allclose(cartCC[:, 0], 0.5 * root2over2 * np.r_[1.0, -1.0, -1.0, 1.0])\n        )\n        self.assertTrue(\n            np.allclose(cartCC[:, 1], 0.5 * root2over2 * np.r_[1.0, 1.0, -1.0, -1.0])\n        )\n        self.assertTrue(np.allclose(cartCC[:, 2], 0.5 * np.ones(4)))\n\n        # nodes\n        cartN = mesh.cartesian_grid(\"N\")\n        self.assertTrue(\n            np.allclose(cartN[:, 0], np.hstack(2 * [0.0, 1.0, 0.0, -1.0, 0.0]))\n        )\n        self.assertTrue(\n            np.allclose(cartN[:, 1], np.hstack(2 * [0.0, 0.0, 1.0, 0.0, -1.0]))\n        )\n        self.assertTrue(np.allclose(cartN[:, 2], np.hstack(5 * [0.0] + 5 * [1.0])))\n\n\nclass Deflation(unittest.TestCase):\n    def test_areas(self):\n        mesh = discretize.CylindricalMesh([1, 2, 1])\n\n        areas = np.hstack([[np.pi] * 2, [1] * 2, [np.pi / 2] * 4])\n        self.assertTrue(np.all(mesh.face_areas == areas))\n\n        edges = np.hstack([[1] * 4, [np.pi] * 4, [1] * 3])\n        self.assertTrue(np.all(mesh.edge_lengths == edges))\n\n        mesh = discretize.CylindricalMesh([2, 5, 3])\n\n        hangingF = np.hstack(\n            [\n                getattr(mesh, \"_ishanging_faces_{}\".format(dim))\n                for dim in [\"x\", \"y\", \"z\"]\n            ]\n        )\n        self.assertTrue(np.all(mesh._face_areas_full[~hangingF] == mesh.face_areas))\n        hangingE = np.hstack(\n            [\n                getattr(mesh, \"_ishanging_edges_{}\".format(dim))\n                for dim in [\"x\", \"y\", \"z\"]\n            ]\n        )\n        self.assertTrue(np.all(mesh._edge_lengths_full[~hangingE] == mesh.edge_lengths))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/cyl/test_cylOperators.py",
    "content": "import unittest\nimport numpy as np\nimport sympy\nfrom sympy.abc import r, t, z\n\nimport discretize\nfrom discretize import tests\n\nTOL = 1e-1\n\n\n# ----------------------------- Test Operators ------------------------------ #\n\n\nMESHTYPES = [\"uniformCylMesh\"]\ncall2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 2])\ncall3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\ncyl_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)]\ncyl_row3 = lambda g, xfun, yfun, zfun: np.c_[\n    call3(xfun, g), call3(yfun, g), call3(zfun, g)\n]\ncylF2 = lambda M, fx, fy: np.vstack(\n    (cyl_row2(M.gridFx, fx, fy), cyl_row2(M.gridFz, fx, fy))\n)\ncylF3 = lambda M, fx, fy, fz: np.vstack(\n    (\n        cyl_row3(M.gridFx, fx, fy, fz),\n        cyl_row3(M.gridFy, fx, fy, fz),\n        cyl_row3(M.gridFz, fx, fy, fz),\n    )\n)\ncylE3 = lambda M, ex, ey, ez: np.vstack(\n    (\n        cyl_row3(M.gridEx, ex, ey, ez),\n        cyl_row3(M.gridEy, ex, ey, ez),\n        cyl_row3(M.gridEz, ex, ey, ez),\n    )\n)\n\n\nclass FaceInnerProductFctsIsotropic(object):\n    def fcts(self):\n        j = sympy.Matrix(\n            [\n                r**2 * sympy.sin(t) * z,\n                r * sympy.sin(t) * z,\n                r * sympy.sin(t) * z**2,\n            ]\n        )\n\n        # Create an isotropic sigma vector\n        Sig = sympy.Matrix(\n            [\n                [1120 / (69 * sympy.pi) * (r * z) ** 2 * sympy.sin(t) ** 2, 0, 0],\n                [0, 1120 / (69 * sympy.pi) * (r * z) ** 2 * sympy.sin(t) ** 2, 0],\n                [0, 0, 1120 / (69 * sympy.pi) * (r * z) ** 2 * sympy.sin(t) ** 2],\n            ]\n        )\n\n        return j, Sig\n\n    def sol(self):\n        # Do the inner product! - we are in cyl coordinates!\n        j, Sig = self.fcts()\n        jTSj = j.T * Sig * j\n        # we are integrating in cyl coordinates\n        ans = sympy.integrate(\n            sympy.integrate(sympy.integrate(r * jTSj, (r, 0, 1)), (t, 0, 2 * sympy.pi)),\n            (z, 0, 1),\n        )[\n            0\n        ]  # The `[0]` is to make it a number rather than a matrix\n\n        return ans\n\n    def vectors(self, mesh):\n        j, Sig = self.fcts()\n\n        f_jr = sympy.lambdify((r, t, z), j[0], \"numpy\")\n        f_jt = sympy.lambdify((r, t, z), j[1], \"numpy\")\n        f_jz = sympy.lambdify((r, t, z), j[2], \"numpy\")\n\n        f_sig = sympy.lambdify((r, t, z), Sig[0], \"numpy\")\n\n        jr = f_jr(mesh.gridFx[:, 0], mesh.gridFx[:, 1], mesh.gridFx[:, 2])\n        jt = f_jt(mesh.gridFy[:, 0], mesh.gridFy[:, 1], mesh.gridFy[:, 2])\n        jz = f_jz(mesh.gridFz[:, 0], mesh.gridFz[:, 1], mesh.gridFz[:, 2])\n\n        sig = f_sig(mesh.gridCC[:, 0], mesh.gridCC[:, 1], mesh.gridCC[:, 2])\n\n        return sig, np.r_[jr, jt, jz]\n\n\nclass EdgeInnerProductFctsIsotropic(object):\n    def fcts(self):\n        h = sympy.Matrix(\n            [\n                r**2 * sympy.sin(t) * z,\n                r * sympy.sin(t) * z,\n                r * sympy.sin(t) * z**2,\n            ]\n        )\n\n        # Create an isotropic sigma vector\n        Sig = sympy.Matrix(\n            [\n                [1120 / (69 * sympy.pi) * (r * z) ** 2 * sympy.sin(t) ** 2, 0, 0],\n                [0, 1120 / (69 * sympy.pi) * (r * z) ** 2 * sympy.sin(t) ** 2, 0],\n                [0, 0, 1120 / (69 * sympy.pi) * (r * z) ** 2 * sympy.sin(t) ** 2],\n            ]\n        )\n\n        return h, Sig\n\n    def sol(self):\n        h, Sig = self.fcts()\n\n        hTSh = h.T * Sig * h\n        ans = sympy.integrate(\n            sympy.integrate(sympy.integrate(r * hTSh, (r, 0, 1)), (t, 0, 2 * sympy.pi)),\n            (z, 0, 1),\n        )[\n            0\n        ]  # The `[0]` is to make it a scalar\n\n        return ans\n\n    def vectors(self, mesh):\n        h, Sig = self.fcts()\n\n        f_hr = sympy.lambdify((r, t, z), h[0], \"numpy\")\n        f_ht = sympy.lambdify((r, t, z), h[1], \"numpy\")\n        f_hz = sympy.lambdify((r, t, z), h[2], \"numpy\")\n\n        f_sig = sympy.lambdify((r, t, z), Sig[0], \"numpy\")\n\n        hr = f_hr(mesh.gridEx[:, 0], mesh.gridEx[:, 1], mesh.gridEx[:, 2])\n        ht = f_ht(mesh.gridEy[:, 0], mesh.gridEy[:, 1], mesh.gridEy[:, 2])\n        hz = f_hz(mesh.gridEz[:, 0], mesh.gridEz[:, 1], mesh.gridEz[:, 2])\n\n        sig = f_sig(mesh.gridCC[:, 0], mesh.gridCC[:, 1], mesh.gridCC[:, 2])\n\n        return sig, np.r_[hr, ht, hz]\n\n\nclass TestCylInnerProducts_simple(unittest.TestCase):\n    def setUp(self):\n        n = 100.0\n        self.mesh = discretize.CylindricalMesh([n, n, n])\n\n    def test_FaceInnerProductIsotropic(self):\n        # Here we will make up some j vectors that vary in space\n        # j = [j_r, j_z] - to test face inner products\n\n        fcts = FaceInnerProductFctsIsotropic()\n        sig, jv = fcts.vectors(self.mesh)\n        MfSig = self.mesh.get_face_inner_product(sig)\n        numeric_ans = jv.T.dot(MfSig.dot(jv))\n\n        ans = fcts.sol()\n\n        print(\"------ Testing Face Inner Product-----------\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n    def test_EdgeInnerProduct(self):\n        # Here we will make up some j vectors that vary in space\n        # h = [h_t] - to test edge inner products\n\n        fcts = EdgeInnerProductFctsIsotropic()\n        sig, hv = fcts.vectors(self.mesh)\n        MeSig = self.mesh.get_edge_inner_product(sig)\n        numeric_ans = hv.T.dot(MeSig.dot(hv))\n\n        ans = fcts.sol()\n\n        print(\"------ Testing Edge Inner Product-----------\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n\nclass TestCylFaceInnerProducts_Order(tests.OrderTest):\n    meshTypes = [\"uniformCylMesh\"]\n    meshDimension = 3\n\n    def getError(self):\n        fct = FaceInnerProductFctsIsotropic()\n        sig, jv = fct.vectors(self.M)\n        Msig = self.M.get_face_inner_product(sig)\n        return float(fct.sol()) - jv.T.dot(Msig.dot(jv))\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCylEdgeInnerProducts_Order(tests.OrderTest):\n    meshTypes = [\"uniformCylMesh\"]\n    meshDimension = 3\n\n    def getError(self):\n        fct = EdgeInnerProductFctsIsotropic()\n        sig, hv = fct.vectors(self.M)\n        Msig = self.M.get_edge_inner_product(sig)\n        return float(fct.sol()) - hv.T.dot(Msig.dot(hv))\n\n    def test_order(self):\n        self.orderTest()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/cyl/test_cyl_counting.py",
    "content": "import numpy as np\nimport discretize\n\n\ndef test_pizza_slice_counting():\n    # pizza slice includes r=0, but doesn't wrap around azimuth\n    # i.e. domain is a triangular pizza slice.\n    mesh = discretize.CylindricalMesh([4, 3 * [np.pi / 6], 2])\n\n    assert not mesh.is_wrapped\n    assert mesh.includes_zero\n    assert not mesh.is_symmetric\n\n    assert mesh.shape_nodes == (5, 4, 3)\n    assert mesh.n_nodes == 4 * 4 * 3 + 3\n    assert mesh._shape_total_nodes == (5, 4, 3)\n    assert mesh._n_total_nodes == 5 * 4 * 3\n\n    assert mesh.shape_faces_x == (4, 3, 2)\n    assert mesh.n_faces_x == 4 * 3 * 2\n    assert mesh._shape_total_faces_x == (5, 3, 2)\n    assert mesh._n_total_faces_x == 5 * 3 * 2\n    assert mesh._n_hanging_faces_x == 3 * 2\n\n    hang_f_dict = mesh._hanging_faces_x\n    assert len(hang_f_dict) == mesh._n_hanging_faces_x\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_x)[0], list(hang_f_dict.keys())\n    )\n    assert all(v is None for v in hang_f_dict.values())\n\n    assert mesh.shape_faces_y == (4, 4, 2)\n    assert mesh.n_faces_y == 4 * 4 * 2\n    assert mesh._shape_total_faces_y == (4, 4, 2)\n    assert mesh._n_total_faces_y == 4 * 4 * 2\n    assert mesh._n_hanging_faces_y == 0\n\n    hang_f_dict = mesh._hanging_faces_y\n    assert len(hang_f_dict) == mesh._n_hanging_faces_y\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_y)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_faces_z == (4, 3, 3)\n    assert mesh.n_faces_z == 4 * 3 * 3\n    assert mesh._shape_total_faces_z == (4, 3, 3)\n    assert mesh._n_total_faces_z == 4 * 3 * 3\n    assert mesh._n_hanging_faces_z == 0\n\n    hang_f_dict = mesh._hanging_faces_z\n    assert len(hang_f_dict) == mesh._n_hanging_faces_z\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_z)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_edges_x == (4, 4, 3)\n    assert mesh.n_edges_x == 4 * 4 * 3\n    assert mesh._shape_total_edges_x == (4, 4, 3)\n    assert mesh._n_total_edges_x == 4 * 4 * 3\n\n    n_hanging_ex = mesh._n_total_edges_x - mesh.n_edges_x\n    hang_e_dict = mesh._hanging_edges_x\n    assert len(hang_e_dict) == n_hanging_ex\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_x)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.shape_edges_y == (4, 3, 3)\n    assert mesh.n_edges_y == 4 * 3 * 3\n    assert mesh._shape_total_edges_y == (5, 3, 3)\n    assert mesh._n_total_edges_y == 5 * 3 * 3\n\n    n_hanging_ey = mesh._n_total_edges_y - mesh.n_edges_y\n    hang_e_dict = mesh._hanging_edges_y\n    assert len(hang_e_dict) == n_hanging_ey\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_y)[0], list(hang_e_dict.keys())\n    )\n    assert all(v is None for v in hang_e_dict.values())\n\n    assert mesh.shape_edges_z == (5, 4, 2)\n    assert mesh.n_edges_z == 4 * 4 * 2 + 2\n    assert mesh._shape_total_edges_z == (5, 4, 2)\n    assert mesh._n_total_edges_z == 5 * 4 * 2\n\n    n_hanging_ez = mesh._n_total_edges_z - mesh.n_edges_z\n    hang_e_dict = mesh._hanging_edges_z\n    assert len(hang_e_dict) == n_hanging_ez\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_z)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.n_nodes == mesh.nodes.shape[0]\n    assert mesh.n_faces == mesh.n_faces_x + mesh.n_faces_y + mesh.n_faces_z\n    assert mesh.n_faces == mesh.faces.shape[0]\n    assert mesh.n_edges == mesh.n_edges_x + mesh.n_edges_y + mesh.n_edges_z\n    assert mesh.n_edges == mesh.edges.shape[0]\n\n    assert mesh.n_faces == mesh.face_areas.shape[0]\n    assert mesh.n_edges == mesh.edge_lengths.shape[0]\n    assert mesh.n_cells == mesh.cell_volumes.shape[0]\n\n\ndef test_ring_counting():\n    # domain fully discretizes the azimuthal dimension, but\n    # does not include zero\n    mesh = discretize.CylindricalMesh([4, 3, 2], origin=[1, 0, 0])\n\n    assert mesh.is_wrapped\n    assert not mesh.includes_zero\n    assert not mesh.is_symmetric\n\n    assert mesh.shape_nodes == (5, 3, 3)\n    assert mesh.n_nodes == 5 * 3 * 3\n    assert mesh._shape_total_nodes == (5, 4, 3)\n    assert mesh._n_total_nodes == 5 * 4 * 3\n\n    assert mesh.shape_faces_x == (5, 3, 2)\n    assert mesh.n_faces_x == 5 * 3 * 2\n    assert mesh._shape_total_faces_x == (5, 3, 2)\n    assert mesh._n_total_faces_x == 5 * 3 * 2\n    assert mesh._n_hanging_faces_x == 0\n\n    hang_f_dict = mesh._hanging_faces_x\n    assert len(hang_f_dict) == mesh._n_hanging_faces_x\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_x)[0], list(hang_f_dict.keys())\n    )\n    assert all(v is None for v in hang_f_dict.values())\n\n    assert mesh.shape_faces_y == (4, 3, 2)\n    assert mesh.n_faces_y == 4 * 3 * 2\n    assert mesh._shape_total_faces_y == (4, 4, 2)\n    assert mesh._n_total_faces_y == 4 * 4 * 2\n    assert mesh._n_hanging_faces_y == 4 * 2\n\n    hang_f_dict = mesh._hanging_faces_y\n    assert len(hang_f_dict) == mesh._n_hanging_faces_y\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_y)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_faces_z == (4, 3, 3)\n    assert mesh.n_faces_z == 4 * 3 * 3\n    assert mesh._shape_total_faces_z == (4, 3, 3)\n    assert mesh._n_total_faces_z == 4 * 3 * 3\n    assert mesh._n_hanging_faces_z == 0\n\n    hang_f_dict = mesh._hanging_faces_z\n    assert len(hang_f_dict) == mesh._n_hanging_faces_z\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_z)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_edges_x == (4, 3, 3)\n    assert mesh.n_edges_x == 4 * 3 * 3\n    assert mesh._shape_total_edges_x == (4, 4, 3)\n    assert mesh._n_total_edges_x == 4 * 4 * 3\n\n    n_hanging_ex = mesh._n_total_edges_x - mesh.n_edges_x\n    hang_e_dict = mesh._hanging_edges_x\n    assert len(hang_e_dict) == n_hanging_ex\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_x)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.shape_edges_y == (5, 3, 3)\n    assert mesh.n_edges_y == 5 * 3 * 3\n    assert mesh._shape_total_edges_y == (5, 3, 3)\n    assert mesh._n_total_edges_y == 5 * 3 * 3\n\n    n_hanging_ey = mesh._n_total_edges_y - mesh.n_edges_y\n    hang_e_dict = mesh._hanging_edges_y\n    assert len(hang_e_dict) == n_hanging_ey\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_y)[0], list(hang_e_dict.keys())\n    )\n    assert all(v is None for v in hang_e_dict.values())\n\n    assert mesh.shape_edges_z == (5, 3, 2)\n    assert mesh.n_edges_z == 5 * 3 * 2\n    assert mesh._shape_total_edges_z == (5, 4, 2)\n    assert mesh._n_total_edges_z == 5 * 4 * 2\n\n    n_hanging_ez = mesh._n_total_edges_z - mesh.n_edges_z\n    hang_e_dict = mesh._hanging_edges_z\n    assert len(hang_e_dict) == n_hanging_ez\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_z)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.n_nodes == mesh.nodes.shape[0]\n    assert mesh.n_faces == mesh.n_faces_x + mesh.n_faces_y + mesh.n_faces_z\n    assert mesh.n_faces == mesh.faces.shape[0]\n    assert mesh.n_edges == mesh.n_edges_x + mesh.n_edges_y + mesh.n_edges_z\n    assert mesh.n_edges == mesh.edges.shape[0]\n\n    assert mesh.n_faces == mesh.face_areas.shape[0]\n    assert mesh.n_edges == mesh.edge_lengths.shape[0]\n    assert mesh.n_cells == mesh.cell_volumes.shape[0]\n\n\ndef test_cyl_tensor_counting():\n    mesh = discretize.CylindricalMesh([4, 3 * [np.pi / 6], 2], origin=[1, 0, 0])\n\n    assert not mesh.is_wrapped\n    assert not mesh.includes_zero\n    assert not mesh.is_symmetric\n\n    assert mesh.shape_nodes == (5, 4, 3)\n    assert mesh.n_nodes == 5 * 4 * 3\n    assert mesh._shape_total_nodes == (5, 4, 3)\n    assert mesh._n_total_nodes == 5 * 4 * 3\n\n    assert mesh.shape_faces_x == (5, 3, 2)\n    assert mesh.n_faces_x == 5 * 3 * 2\n    assert mesh._shape_total_faces_x == (5, 3, 2)\n    assert mesh._n_total_faces_x == 5 * 3 * 2\n    assert mesh._n_hanging_faces_x == 0\n\n    hang_f_dict = mesh._hanging_faces_x\n    assert len(hang_f_dict) == mesh._n_hanging_faces_x\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_x)[0], list(hang_f_dict.keys())\n    )\n    assert all(v is None for v in hang_f_dict.values())\n\n    assert mesh.shape_faces_y == (4, 4, 2)\n    assert mesh.n_faces_y == 4 * 4 * 2\n    assert mesh._shape_total_faces_y == (4, 4, 2)\n    assert mesh._n_total_faces_y == 4 * 4 * 2\n    assert mesh._n_hanging_faces_y == 0\n\n    hang_f_dict = mesh._hanging_faces_y\n    assert len(hang_f_dict) == mesh._n_hanging_faces_y\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_y)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_faces_z == (4, 3, 3)\n    assert mesh.n_faces_z == 4 * 3 * 3\n    assert mesh._shape_total_faces_z == (4, 3, 3)\n    assert mesh._n_total_faces_z == 4 * 3 * 3\n    assert mesh._n_hanging_faces_z == 0\n\n    hang_f_dict = mesh._hanging_faces_z\n    assert len(hang_f_dict) == mesh._n_hanging_faces_z\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_z)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_edges_x == (4, 4, 3)\n    assert mesh.n_edges_x == 4 * 4 * 3\n    assert mesh._shape_total_edges_x == (4, 4, 3)\n    assert mesh._n_total_edges_x == 4 * 4 * 3\n\n    n_hanging_ex = mesh._n_total_edges_x - mesh.n_edges_x\n    hang_e_dict = mesh._hanging_edges_x\n    assert len(hang_e_dict) == n_hanging_ex\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_x)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.shape_edges_y == (5, 3, 3)\n    assert mesh.n_edges_y == 5 * 3 * 3\n    assert mesh._shape_total_edges_y == (5, 3, 3)\n    assert mesh._n_total_edges_y == 5 * 3 * 3\n\n    n_hanging_ey = mesh._n_total_edges_y - mesh.n_edges_y\n    hang_e_dict = mesh._hanging_edges_y\n    assert len(hang_e_dict) == n_hanging_ey\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_y)[0], list(hang_e_dict.keys())\n    )\n    assert all(v is None for v in hang_e_dict.values())\n\n    assert mesh.shape_edges_z == (5, 4, 2)\n    assert mesh.n_edges_z == 5 * 4 * 2\n    assert mesh._shape_total_edges_z == (5, 4, 2)\n    assert mesh._n_total_edges_z == 5 * 4 * 2\n\n    n_hanging_ez = mesh._n_total_edges_z - mesh.n_edges_z\n    hang_e_dict = mesh._hanging_edges_z\n    assert len(hang_e_dict) == n_hanging_ez\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_z)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.n_nodes == mesh.nodes.shape[0]\n    assert mesh.n_faces == mesh.n_faces_x + mesh.n_faces_y + mesh.n_faces_z\n    assert mesh.n_faces == mesh.faces.shape[0]\n    assert mesh.n_edges == mesh.n_edges_x + mesh.n_edges_y + mesh.n_edges_z\n    assert mesh.n_edges == mesh.edges.shape[0]\n\n    assert mesh.n_faces == mesh.face_areas.shape[0]\n    assert mesh.n_edges == mesh.edge_lengths.shape[0]\n    assert mesh.n_cells == mesh.cell_volumes.shape[0]\n\n\ndef test_sym_ring_counting():\n    # domain fully discretizes the azimuthal dimension, but\n    # does not include zero\n    mesh = discretize.CylindricalMesh([4, 1, 2], origin=[1, 0, 0])\n\n    assert mesh.is_wrapped\n    assert not mesh.includes_zero\n    assert mesh.is_symmetric\n\n    assert mesh.shape_nodes == (5, 0, 3)\n    assert mesh.n_nodes == 0\n    assert mesh._shape_total_nodes == (5, 1, 3)\n    assert mesh._n_total_nodes == 5 * 1 * 3\n\n    assert mesh.shape_faces_x == (5, 1, 2)\n    assert mesh.n_faces_x == 5 * 1 * 2\n    assert mesh._shape_total_faces_x == (5, 1, 2)\n    assert mesh._n_total_faces_x == 5 * 1 * 2\n    assert mesh._n_hanging_faces_x == 0\n\n    assert mesh.shape_faces_y == (4, 0, 2)\n    assert mesh.n_faces_y == 0\n    assert mesh._shape_total_faces_y == (4, 1, 2)\n    assert mesh._n_total_faces_y == 4 * 1 * 2\n    assert mesh._n_hanging_faces_y == 4 * 2\n\n    assert mesh.shape_faces_z == (4, 1, 3)\n    assert mesh.n_faces_z == 4 * 1 * 3\n    assert mesh._shape_total_faces_z == (4, 1, 3)\n    assert mesh._n_total_faces_z == 4 * 1 * 3\n    assert mesh._n_hanging_faces_z == 0\n\n    assert mesh.shape_edges_x == (4, 0, 3)\n    assert mesh.n_edges_x == 0\n    assert mesh._shape_total_edges_x == (4, 1, 3)\n    assert mesh._n_total_edges_x == 4 * 1 * 3\n\n    assert mesh.shape_edges_y == (5, 1, 3)\n    assert mesh.n_edges_y == 5 * 1 * 3\n    assert mesh._shape_total_edges_y == (5, 1, 3)\n    assert mesh._n_total_edges_y == 5 * 1 * 3\n\n    assert mesh.shape_edges_z == (5, 0, 2)\n    assert mesh.n_edges_z == 0\n    assert mesh._shape_total_edges_z == (5, 1, 2)\n    assert mesh._n_total_edges_z == 5 * 1 * 2\n\n    assert mesh._n_total_nodes == mesh.nodes.shape[0]\n    assert mesh.n_faces == mesh.n_faces_x + mesh.n_faces_y + mesh.n_faces_z\n    assert mesh.n_faces == mesh.faces.shape[0]\n    assert mesh.n_edges == mesh.n_edges_x + mesh.n_edges_y + mesh.n_edges_z\n    assert mesh.n_edges == mesh.edges.shape[0]\n\n    assert mesh.n_faces == mesh.face_areas.shape[0]\n    assert mesh.n_edges == mesh.edge_lengths.shape[0]\n    assert mesh.n_cells == mesh.cell_volumes.shape[0]\n\n\ndef test_sym_full_counting():\n    mesh = discretize.CylindricalMesh([3, 1, 2])\n\n    assert mesh.dim == 3\n    assert mesh.shape_cells == (3, 1, 2)\n\n    assert mesh.is_wrapped\n    assert mesh.includes_zero\n    assert mesh.is_symmetric\n\n    assert mesh.shape_nodes == (3, 0, 3)\n    assert mesh.n_nodes == 0\n    assert mesh._shape_total_nodes == (3, 1, 3)\n    assert mesh._n_total_nodes == 3 * 1 * 3\n\n    assert mesh.shape_faces_x == (3, 1, 2)\n    assert mesh.n_faces_x == 3 * 1 * 2\n    assert mesh._shape_total_faces_x == (3, 1, 2)\n    assert mesh._n_total_faces_x == 3 * 1 * 2\n    assert mesh._n_hanging_faces_x == 1 * 2\n\n    assert mesh.shape_faces_y == (3, 0, 2)\n    assert mesh.n_faces_y == 3 * 0 * 2\n    assert mesh._shape_total_faces_y == (3, 1, 2)\n    assert mesh._n_total_faces_y == 3 * 1 * 2\n    assert mesh._n_hanging_faces_y == 3 * 1 * 2\n\n    assert mesh.shape_faces_z == (3, 1, 3)\n    assert mesh.n_faces_z == 3 * 1 * 3\n    assert mesh._shape_total_faces_z == (3, 1, 3)\n    assert mesh._n_total_faces_z == 3 * 1 * 3\n    assert mesh._n_hanging_faces_z == 0\n\n    assert mesh.shape_edges_x == (3, 0, 3)\n    assert mesh.n_edges_x == 3 * 0 * 3\n    assert mesh._shape_total_edges_x == (3, 1, 3)\n    assert mesh._n_total_edges_x == 3 * 1 * 3\n\n    assert mesh.shape_edges_y == (3, 1, 3)\n    assert mesh.n_edges_y == 3 * 1 * 3\n    assert mesh._shape_total_edges_y == (3, 1, 3)\n    assert mesh._n_total_edges_y == 3 * 1 * 3\n\n    assert mesh.shape_edges_z == (3, 0, 2)\n    assert mesh.n_edges_z == 0\n    assert mesh._shape_total_edges_z == (3, 1, 2)\n    assert mesh._n_total_edges_z == 3 * 1 * 2\n\n    assert mesh._n_total_nodes == mesh.nodes.shape[0]\n    assert mesh.n_faces == mesh.n_faces_x + mesh.n_faces_y + mesh.n_faces_z\n    assert mesh.n_faces == mesh.faces.shape[0]\n    assert mesh.n_edges == mesh.n_edges_x + mesh.n_edges_y + mesh.n_edges_z\n    assert mesh.n_edges == mesh.edges.shape[0]\n\n    assert mesh.n_faces == mesh.face_areas.shape[0]\n    assert mesh.n_edges == mesh.edge_lengths.shape[0]\n    assert mesh.n_cells == mesh.cell_volumes.shape[0]\n\n\ndef test_wrapped_counting():\n    mesh = discretize.CylindricalMesh([3, 2, 2])\n\n    assert mesh.dim == 3\n    assert mesh.shape_cells == (3, 2, 2)\n\n    assert mesh.is_wrapped\n    assert mesh.includes_zero\n    assert not mesh.is_symmetric\n\n    assert mesh.shape_nodes == (4, 2, 3)\n    assert mesh.n_nodes == 3 * 2 * 3 + 3\n    assert mesh._shape_total_nodes == (4, 3, 3)\n    assert mesh._n_total_nodes == 4 * 3 * 3\n\n    assert mesh.shape_faces_x == (3, 2, 2)\n    assert mesh.n_faces_x == 3 * 2 * 2\n    assert mesh._shape_total_faces_x == (4, 2, 2)\n    assert mesh._n_total_faces_x == 4 * 2 * 2\n    assert mesh._n_hanging_faces_x == 2 * 2\n\n    hang_f_dict = mesh._hanging_faces_x\n    assert len(hang_f_dict) == mesh._n_hanging_faces_x\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_x)[0], list(hang_f_dict.keys())\n    )\n    assert all(v is None for v in hang_f_dict.values())\n\n    assert mesh.shape_faces_y == (3, 2, 2)\n    assert mesh.n_faces_y == 3 * 2 * 2\n    assert mesh._shape_total_faces_y == (3, 3, 2)\n    assert mesh._n_total_faces_y == 3 * 3 * 2\n    assert mesh._n_hanging_faces_y == 3 * 2\n\n    hang_f_dict = mesh._hanging_faces_y\n    assert len(hang_f_dict) == mesh._n_hanging_faces_y\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_y)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_faces_z == (3, 2, 3)\n    assert mesh.n_faces_z == 3 * 2 * 3\n    assert mesh._shape_total_faces_z == (3, 2, 3)\n    assert mesh._n_total_faces_z == 3 * 2 * 3\n    assert mesh._n_hanging_faces_z == 0\n\n    hang_f_dict = mesh._hanging_faces_z\n    assert len(hang_f_dict) == mesh._n_hanging_faces_z\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_faces_z)[0], list(hang_f_dict.keys())\n    )\n\n    assert mesh.shape_edges_x == (3, 2, 3)\n    assert mesh.n_edges_x == 3 * 2 * 3\n    assert mesh._shape_total_edges_x == (3, 3, 3)\n    assert mesh._n_total_edges_x == 3 * 3 * 3\n\n    n_hanging_ex = mesh._n_total_edges_x - mesh.n_edges_x\n    hang_e_dict = mesh._hanging_edges_x\n    assert len(hang_e_dict) == n_hanging_ex\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_x)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.shape_edges_y == (3, 2, 3)\n    assert mesh.n_edges_y == 3 * 2 * 3\n    assert mesh._shape_total_edges_y == (4, 2, 3)\n    assert mesh._n_total_edges_y == 4 * 2 * 3\n\n    n_hanging_ey = mesh._n_total_edges_y - mesh.n_edges_y\n    hang_e_dict = mesh._hanging_edges_y\n    assert len(hang_e_dict) == n_hanging_ey\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_y)[0], list(hang_e_dict.keys())\n    )\n    assert all(v is None for v in hang_e_dict.values())\n\n    assert mesh.shape_edges_z == (4, 2, 2)\n    assert mesh.n_edges_z == 3 * 2 * 2 + 2\n    assert mesh._shape_total_edges_z == (4, 3, 2)\n    assert mesh._n_total_edges_z == 4 * 3 * 2\n\n    n_hanging_ez = mesh._n_total_edges_z - mesh.n_edges_z\n    hang_e_dict = mesh._hanging_edges_z\n    assert len(hang_e_dict) == n_hanging_ez\n    np.testing.assert_equal(\n        np.where(mesh._ishanging_edges_z)[0], list(hang_e_dict.keys())\n    )\n\n    assert mesh.n_nodes == mesh.nodes.shape[0]\n    assert mesh.n_faces == mesh.n_faces_x + mesh.n_faces_y + mesh.n_faces_z\n    assert mesh.n_faces == mesh.faces.shape[0]\n    assert mesh.n_edges == mesh.n_edges_x + mesh.n_edges_y + mesh.n_edges_z\n    assert mesh.n_edges == mesh.edges.shape[0]\n\n    assert mesh.n_faces == mesh.face_areas.shape[0]\n    assert mesh.n_edges == mesh.edge_lengths.shape[0]\n    assert mesh.n_cells == mesh.cell_volumes.shape[0]\n"
  },
  {
    "path": "tests/cyl/test_cyl_innerproducts.py",
    "content": "import discretize\nfrom discretize import tests\nimport numpy as np\nimport sympy\nfrom sympy.abc import r, t, z\nimport unittest\nimport scipy.sparse as sp\n\nTOL = 1e-1\nTOLD = 0.7  # tolerance on deriv checks\n\nrng = np.random.default_rng(99)\n\n\nclass FaceInnerProductFctsIsotropic(object):\n    \"\"\"Some made up face functions to test the face inner product\"\"\"\n\n    def fcts(self):\n        j = sympy.Matrix([r**2 * z, r * z**2])\n\n        # Create an isotropic sigma vector\n        Sig = sympy.Matrix(\n            [\n                [420 / (sympy.pi) * (r * z) ** 2, 0],\n                [0, 420 / (sympy.pi) * (r * z) ** 2],\n            ]\n        )\n\n        return j, Sig\n\n    def sol(self):\n        # Do the inner product! - we are in cyl coordinates!\n        j, Sig = self.fcts()\n        jTSj = j.T * Sig * j\n        # we are integrating in cyl coordinates\n        ans = sympy.integrate(\n            sympy.integrate(sympy.integrate(r * jTSj, (r, 0, 1)), (t, 0, 2 * sympy.pi)),\n            (z, 0, 1),\n        )[\n            0\n        ]  # The `[0]` is to make it an int.\n\n        return ans\n\n    def vectors(self, mesh):\n        \"\"\"Get Vectors sig, sr. jx from sympy\"\"\"\n        j, Sig = self.fcts()\n\n        f_jr = sympy.lambdify((r, z), j[0], \"numpy\")\n        f_jz = sympy.lambdify((r, z), j[1], \"numpy\")\n        f_sigr = sympy.lambdify((r, z), Sig[0], \"numpy\")\n        # f_sigz = sympy.lambdify((r,z), Sig[1], 'numpy')\n\n        jr = f_jr(mesh.gridFx[:, 0], mesh.gridFx[:, 2])\n        jz = f_jz(mesh.gridFz[:, 0], mesh.gridFz[:, 2])\n        sigr = f_sigr(mesh.gridCC[:, 0], mesh.gridCC[:, 2])\n\n        return sigr, np.r_[jr, jz]\n\n\nclass FaceInnerProductFunctionsDiagAnisotropic(FaceInnerProductFctsIsotropic):\n    \"\"\"\n    Some made up face functions to test the diagonally anisotropic face\n    inner product\n    \"\"\"\n\n    def fcts(self):\n        j = sympy.Matrix([r**2 * z, r * z**2])\n\n        # Create an isotropic sigma vector\n        Sig = sympy.Matrix(\n            [\n                [120 / (sympy.pi) * (r * z) ** 2, 0],\n                [0, 420 / (sympy.pi) * (r * z) ** 2],\n            ]\n        )\n\n        return j, Sig\n\n    def vectors(self, mesh):\n        \"\"\"Get Vectors sig, sr. jx from sympy\"\"\"\n        j, Sig = self.fcts()\n\n        f_jr = sympy.lambdify((r, z), j[0], \"numpy\")\n        f_jz = sympy.lambdify((r, z), j[1], \"numpy\")\n        f_sigr = sympy.lambdify((r, z), Sig[0], \"numpy\")\n        f_sigz = sympy.lambdify((r, z), Sig[3], \"numpy\")\n\n        jr = f_jr(mesh.gridFx[:, 0], mesh.gridFx[:, 2])\n        jz = f_jz(mesh.gridFz[:, 0], mesh.gridFz[:, 2])\n        sigr = f_sigr(mesh.gridCC[:, 0], mesh.gridCC[:, 2])\n        sigz = f_sigz(mesh.gridCC[:, 0], mesh.gridCC[:, 2])\n\n        return np.c_[sigr, sigr, sigz], np.r_[jr, jz]\n\n\nclass EdgeInnerProductFctsIsotropic(object):\n    \"\"\"Some made up edge functions to test the edge inner product\"\"\"\n\n    def fcts(self):\n        h = sympy.Matrix([r**2 * z])\n\n        # Create an isotropic sigma vector\n        Sig = sympy.Matrix([200 / (sympy.pi) * (r * z) ** 2])\n\n        return h, Sig\n\n    def sol(self):\n        h, Sig = self.fcts()\n        # Do the inner product! - we are in cyl coordinates!\n        hTSh = h.T * Sig * h\n        ans = sympy.integrate(\n            sympy.integrate(sympy.integrate(r * hTSh, (r, 0, 1)), (t, 0, 2 * sympy.pi)),\n            (z, 0, 1),\n        )[\n            0\n        ]  # The `[0]` is to make it an int.\n        return ans\n\n    def vectors(self, mesh):\n        \"\"\"Get Vectors sig, sr. jx from sympy\"\"\"\n        h, Sig = self.fcts()\n\n        f_h = sympy.lambdify((r, z), h[0], \"numpy\")\n        f_sig = sympy.lambdify((r, z), Sig[0], \"numpy\")\n\n        ht = f_h(mesh.gridEy[:, 0], mesh.gridEy[:, 2])\n        sig = f_sig(mesh.gridCC[:, 0], mesh.gridCC[:, 2])\n\n        return sig, np.r_[ht]\n\n\nclass EdgeInnerProductFunctionsDiagAnisotropic(EdgeInnerProductFctsIsotropic):\n    \"\"\"\n    Some made up edge functions to test the diagonally anisotropic edge\n    inner product\n    \"\"\"\n\n    def vectors(self, mesh):\n        h, Sig = self.fcts()\n\n        f_h = sympy.lambdify((r, z), h[0], \"numpy\")\n        f_sig = sympy.lambdify((r, z), Sig[0], \"numpy\")\n\n        ht = f_h(mesh.gridEy[:, 0], mesh.gridEy[:, 2])\n        sig = f_sig(mesh.gridCC[:, 0], mesh.gridCC[:, 2])\n\n        return np.c_[sig, sig, sig], np.r_[ht]\n\n\nclass FaceInnerProductFctsFacePropertiesIsotropic(object):\n    \"\"\"Some made up face functions to test the face inner product\"\"\"\n\n    def fcts(self):\n        r_plane = 0.5\n        z_plane = 0.5\n\n        j_r = (r_plane**2) * z  # radial component\n        j_z = r * z_plane  # vertical component\n\n        # Create an isotropic sigma vector\n        tau_r = (r_plane + z) ** 2  # r-faces\n        tau_z = r + z_plane**2  # z_faces\n\n        return j_r, j_z, tau_r, tau_z\n\n    def sol(self):\n        r_plane = 0.5\n\n        # Do the inner product! - we are in cyl coordinates!\n        j_r, j_z, tau_r, tau_z = self.fcts()\n\n        # we are integrating in cyl coordinates\n        int_r = sympy.integrate(r_plane * j_r**2 * tau_r, (z, 0, 1), (t, 0, 2 * np.pi))\n        int_z = sympy.integrate(r * j_z**2 * tau_z, (r, 0, 1), (t, 0, 2 * np.pi))\n\n        # return int_z(z_plane)\n        return int_r + int_z\n\n    def vectors(self, mesh):\n        r_plane = 0.5\n        z_plane = 0.5\n\n        \"\"\"Get Vectors sig, sr. jx from sympy\"\"\"\n        j_r, j_z, tau_r, tau_z = self.fcts()\n\n        fun_j_r = sympy.lambdify(z, j_r, \"numpy\")\n        fun_j_z = sympy.lambdify(r, j_z, \"numpy\")\n        fun_tau_r = sympy.lambdify(z, tau_r, \"numpy\")\n        fun_tau_z = sympy.lambdify(r, tau_z, \"numpy\")\n\n        eval_j_r = fun_j_r(mesh.gridFx[:, 2])\n        eval_j_z = fun_j_z(mesh.gridFz[:, 0])\n        eval_tau_r = 1e-12 * np.ones(mesh.nFx)\n        eval_tau_z = 1e-12 * np.ones(mesh.nFz)\n\n        k_r = np.isclose(mesh.faces_x[:, 0], r_plane)\n        k_z = np.isclose(mesh.faces_z[:, 2], z_plane)\n\n        eval_tau_r[k_r] = fun_tau_r(mesh.gridFx[k_r, 2])\n        eval_tau_z[k_z] = fun_tau_z(mesh.gridFz[k_z, 0])\n\n        return np.r_[eval_tau_r, eval_tau_z], np.r_[eval_j_r, eval_j_z]\n\n\nclass EdgeInnerProductFctsFacePropertiesIsotropic(object):\n    \"\"\"Some made up face functions to test the face inner product\"\"\"\n\n    def fcts(self):\n        r_plane = 0.5\n        z_plane = 0.5\n\n        j_t = 0.5 * r * z  # azimuthal\n\n        # Create an isotropic sigma vector\n        tau_r = (r_plane + z) ** 2  # r-faces\n        tau_z = r + z_plane**2  # z_faces\n\n        return j_t, tau_r, tau_z\n\n    def sol(self):\n        r_plane = 0.5\n        z_plane = 0.5\n\n        # Do the inner product! - we are in cyl coordinates!\n        j_t, tau_r, tau_z = self.fcts()\n\n        # we are integrating in cyl coordinates\n        int_r = sympy.lambdify(\n            r,\n            sympy.integrate(r * j_t**2 * tau_r, (z, 0, 1), (t, 0, 2 * np.pi)),\n            \"numpy\",\n        )\n\n        int_z = sympy.lambdify(\n            z,\n            sympy.integrate(r * j_t**2 * tau_z, (r, 0, 1), (t, 0, 2 * np.pi)),\n            \"numpy\",\n        )\n\n        return int_r(r_plane) + int_z(z_plane)\n\n    def vectors(self, mesh):\n        r_plane = 0.5\n        z_plane = 0.5\n\n        \"\"\"Get Vectors sig, sr. jx from sympy\"\"\"\n        j_t, tau_r, tau_z = self.fcts()\n\n        fun_j_t = sympy.lambdify((r, z), j_t, \"numpy\")\n        fun_tau_r = sympy.lambdify(z, tau_r, \"numpy\")\n        fun_tau_z = sympy.lambdify(r, tau_z, \"numpy\")\n\n        eval_j_t = fun_j_t(mesh.gridEy[:, 0], mesh.gridEy[:, 2])\n        eval_tau_r = 1e-8 * np.ones(mesh.nFx)\n        eval_tau_z = 1e-8 * np.ones(mesh.nFz)\n\n        k_r = np.isclose(mesh.faces_x[:, 0], r_plane)\n        k_z = np.isclose(mesh.faces_z[:, 2], z_plane)\n\n        eval_tau_r[k_r] = fun_tau_r(mesh.faces_x[k_r, 2])\n        eval_tau_z[k_z] = fun_tau_z(mesh.faces_z[k_z, 0])\n\n        return np.r_[eval_tau_r, eval_tau_z], eval_j_t\n\n\nclass TestCylInnerProducts_simple(unittest.TestCase):\n    def setUp(self):\n        n = 100.0\n        self.mesh = discretize.CylindricalMesh([n, 1, n])\n\n    def test_FaceInnerProductIsotropic(self):\n        # Here we will make up some j vectors that vary in space\n        # j = [j_r, j_z] - to test face inner products\n\n        fcts = FaceInnerProductFctsIsotropic()\n        sig, jv = fcts.vectors(self.mesh)\n        MfSig = self.mesh.get_face_inner_product(sig)\n        numeric_ans = jv.T.dot(MfSig.dot(jv))\n\n        ans = fcts.sol()\n\n        print(\"------ Testing Face Inner Product-----------\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n    def test_FaceInnerProductDiagAnisotropic(self):\n        # Here we will make up some j vectors that vary in space\n        # j = [j_r, j_z] - to test face inner products\n\n        fcts = FaceInnerProductFunctionsDiagAnisotropic()\n        sig, jv = fcts.vectors(self.mesh)\n        MfSig = self.mesh.get_face_inner_product(sig)\n        numeric_ans = jv.T.dot(MfSig.dot(jv))\n\n        ans = fcts.sol()\n\n        print(\"------ Testing Face Inner Product Anisotropic -----------\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n    def test_EdgeInnerProduct(self):\n        # Here we will make up some j vectors that vary in space\n        # h = [h_t] - to test edge inner products\n\n        fcts = EdgeInnerProductFctsIsotropic()\n        sig, hv = fcts.vectors(self.mesh)\n        MeSig = self.mesh.get_edge_inner_product(sig)\n        numeric_ans = hv.T.dot(MeSig.dot(hv))\n\n        ans = fcts.sol()\n\n        print(\"------ Testing Edge Inner Product-----------\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n    def test_EdgeInnerProductDiagAnisotropic(self):\n        # Here we will make up some j vectors that vary in space\n        # h = [h_t] - to test edge inner products\n\n        fcts = EdgeInnerProductFunctionsDiagAnisotropic()\n\n        sig, hv = fcts.vectors(self.mesh)\n        MeSig = self.mesh.get_edge_inner_product(sig)\n        numeric_ans = hv.T.dot(MeSig.dot(hv))\n\n        ans = fcts.sol()\n\n        print(\"------ Testing Edge Inner Product Anisotropic -----------\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n    def test_FaceInnerProductFacePropertiesIsotropic(self):\n        # Here we will make up some j vectors that vary in space\n        # j = [j_r, j_z] - to test face inner products\n\n        fcts = FaceInnerProductFctsFacePropertiesIsotropic()\n        tau, jv = fcts.vectors(self.mesh)\n        Mftau = self.mesh.get_face_inner_product_surface(tau)\n        numeric_ans = jv.T.dot(Mftau.dot(jv))\n\n        ans = fcts.sol()\n\n        print(\"--- Testing Face Inner Product (face properties) ---\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n    def test_EdgeInnerProductFacePropertiesIsotropic(self):\n        # Here we will make up some j vectors that vary in space\n        # j = [j_r, j_z] - to test face inner products\n\n        fcts = EdgeInnerProductFctsFacePropertiesIsotropic()\n        tau, jv = fcts.vectors(self.mesh)\n        Metau = self.mesh.get_edge_inner_product_surface(tau)\n        numeric_ans = jv.T.dot(Metau.dot(jv))\n\n        ans = fcts.sol()\n\n        print(\"--- Testing Edge Inner Product (face properties) ---\")\n        print(\n            \" Analytic: {analytic}, Numeric: {numeric}, \"\n            \"ratio (num/ana): {ratio}\".format(\n                analytic=ans, numeric=numeric_ans, ratio=float(numeric_ans) / ans\n            )\n        )\n        assert np.abs(ans - numeric_ans) < TOL\n\n\nclass TestCylFaceInnerProducts_Order(tests.OrderTest):\n    meshTypes = [\"uniform_symmetric_CylMesh\"]\n    meshDimension = 3\n\n    def getError(self):\n        fct = FaceInnerProductFctsIsotropic()\n        sig, jv = fct.vectors(self.M)\n        Msig = self.M.get_face_inner_product(sig)\n        return float(fct.sol()) - jv.T.dot(Msig.dot(jv))\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCylEdgeInnerProducts_Order(tests.OrderTest):\n    meshTypes = [\"uniform_symmetric_CylMesh\"]\n    meshDimension = 3\n\n    def getError(self):\n        fct = EdgeInnerProductFctsIsotropic()\n        sig, ht = fct.vectors(self.M)\n        Msig = self.M.get_edge_inner_product(sig)\n        return float(fct.sol()) - ht.T.dot(Msig.dot(ht))\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCylFaceInnerProductsDiagAnisotropic_Order(tests.OrderTest):\n    meshTypes = [\"uniform_symmetric_CylMesh\"]\n    meshDimension = 3\n\n    def getError(self):\n        fct = FaceInnerProductFunctionsDiagAnisotropic()\n        sig, jv = fct.vectors(self.M)\n        Msig = self.M.get_face_inner_product(sig)\n        return float(fct.sol()) - jv.T.dot(Msig.dot(jv))\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCylFaceInnerProductsFaceProperties_Order(tests.OrderTest):\n    meshTypes = [\"uniform_symmetric_CylMesh\"]\n    meshDimension = 3\n\n    def getError(self):\n        fct = FaceInnerProductFctsFacePropertiesIsotropic()\n        tau, jv = fct.vectors(self.M)\n        Mtau = self.M.get_face_inner_product_surface(tau)\n        return float(fct.sol()) - jv.T.dot(Mtau.dot(jv))\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCylEdgeInnerProductsFaceProperties_Order(tests.OrderTest):\n    meshTypes = [\"uniform_symmetric_CylMesh\"]\n    meshDimension = 3\n\n    def getError(self):\n        fct = EdgeInnerProductFctsFacePropertiesIsotropic()\n        tau, jv = fct.vectors(self.M)\n        Mtau = self.M.get_edge_inner_product_surface(tau)\n        return float(fct.sol()) - jv.T.dot(Mtau.dot(jv))\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestCylInnerProducts_Deriv(unittest.TestCase):\n    def setUp(self):\n        n = 2\n        self.mesh = discretize.CylindricalMesh([n, 1, n])\n        self.face_vec = rng.random(self.mesh.nF)\n        self.edge_vec = rng.random(self.mesh.nE)\n        # make up a smooth function\n        self.x0 = 2 * self.mesh.gridCC[:, 0] ** 2 + self.mesh.gridCC[:, 2] ** 4\n\n    def test_FaceInnerProductIsotropicDeriv(self):\n        def fun(x):\n            MfSig = self.mesh.get_face_inner_product(x)\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(self.x0)\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec)\n\n        print(\"Testing FaceInnerProduct Isotropic\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=532\n            )\n        )\n\n    def test_FaceInnerProductIsotropicDerivInvProp(self):\n        def fun(x):\n            MfSig = self.mesh.get_face_inner_product(x, invert_model=True)\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(\n                self.x0, invert_model=True\n            )\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec)\n\n        print(\"Testing FaceInnerProduct Isotropic InvProp\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=75\n            )\n        )\n\n    def test_FaceInnerProductIsotropicDerivInvMat(self):\n        def fun(x):\n            MfSig = self.mesh.get_face_inner_product(x, invert_matrix=True)\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(\n                self.x0, invert_matrix=True\n            )\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec)\n\n        print(\"Testing FaceInnerProduct Isotropic InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=1\n            )\n        )\n\n    def test_FaceInnerProductIsotropicDerivInvPropInvMat(self):\n        def fun(x):\n            MfSig = self.mesh.get_face_inner_product(\n                x, invert_model=True, invert_matrix=True\n            )\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(\n                self.x0, invert_model=True, invert_matrix=True\n            )\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec)\n\n        print(\"Testing FaceInnerProduct Isotropic InvProp InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=74\n            )\n        )\n\n    def test_EdgeInnerProductIsotropicDeriv(self):\n        def fun(x):\n            MeSig = self.mesh.get_edge_inner_product(x)\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(self.x0)\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec)\n\n        print(\"Testing EdgeInnerProduct Isotropic\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=345\n            )\n        )\n\n    def test_EdgeInnerProductIsotropicDerivInvProp(self):\n        def fun(x):\n            MeSig = self.mesh.get_edge_inner_product(x, invert_model=True)\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(\n                self.x0, invert_model=True\n            )\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec)\n\n        print(\"Testing EdgeInnerProduct Isotropic InvProp\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=643\n            )\n        )\n\n    def test_EdgeInnerProductIsotropicDerivInvMat(self):\n        def fun(x):\n            MeSig = self.mesh.get_edge_inner_product(x, invert_matrix=True)\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(\n                self.x0, invert_matrix=True\n            )\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec)\n\n        print(\"Testing EdgeInnerProduct Isotropic InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=363\n            )\n        )\n\n    def test_EdgeInnerProductIsotropicDerivInvPropInvMat(self):\n        def fun(x):\n            MeSig = self.mesh.get_edge_inner_product(\n                x, invert_model=True, invert_matrix=True\n            )\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(\n                self.x0, invert_model=True, invert_matrix=True\n            )\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec)\n\n        print(\"Testing EdgeInnerProduct Isotropic InvProp InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=773\n            )\n        )\n\n\nclass TestCylInnerProductsAnisotropic_Deriv(unittest.TestCase):\n    def setUp(self):\n        n = 60\n        self.mesh = discretize.CylindricalMesh([n, 1, n])\n        self.face_vec = rng.random(self.mesh.nF)\n        self.edge_vec = rng.random(self.mesh.nE)\n        # make up a smooth function\n        self.x0 = np.array(\n            [2 * self.mesh.gridCC[:, 0] ** 2 + self.mesh.gridCC[:, 2] ** 4]\n        )\n\n    def test_FaceInnerProductAnisotropicDeriv(self):\n        def fun(x):\n            # fake anisotropy (tests anistropic implementation with isotropic\n            # vector). First order behavior expected for fully anisotropic\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([eye, zero, eye])])\n\n            MfSig = self.mesh.get_face_inner_product(x)\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(x0)\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec) * P.T\n\n        print(\"Testing FaceInnerProduct Anisotropic\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=2436\n            )\n        )\n\n    def test_FaceInnerProductAnisotropicDerivInvProp(self):\n        def fun(x):\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([eye, zero, eye])])\n\n            MfSig = self.mesh.get_face_inner_product(x, invert_model=True)\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(x0, invert_model=True)\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec) * P.T\n\n        print(\"Testing FaceInnerProduct Anisotropic InvProp\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=634\n            )\n        )\n\n    def test_FaceInnerProductAnisotropicDerivInvMat(self):\n        def fun(x):\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([eye, zero, eye])])\n\n            MfSig = self.mesh.get_face_inner_product(x, invert_matrix=True)\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(x0, invert_matrix=True)\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec) * P.T\n\n        print(\"Testing FaceInnerProduct Anisotropic InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=222\n            )\n        )\n\n    def test_FaceInnerProductAnisotropicDerivInvPropInvMat(self):\n        def fun(x):\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([eye, zero, eye])])\n\n            MfSig = self.mesh.get_face_inner_product(\n                x, invert_model=True, invert_matrix=True\n            )\n            MfSigDeriv = self.mesh.get_face_inner_product_deriv(\n                x0, invert_model=True, invert_matrix=True\n            )\n            return MfSig * self.face_vec, MfSigDeriv(self.face_vec) * P.T\n\n        print(\"Testing FaceInnerProduct Anisotropic InvProp InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=654\n            )\n        )\n\n    def test_EdgeInnerProductAnisotropicDeriv(self):\n        def fun(x):\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([zero, eye, zero])])\n\n            MeSig = self.mesh.get_edge_inner_product(x.reshape(self.mesh.nC, 3))\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(x0)\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec) * P.T\n\n        print(\"Testing EdgeInnerProduct Anisotropic\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=7754\n            )\n        )\n\n    def test_EdgeInnerProductAnisotropicDerivInvProp(self):\n        def fun(x):\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([zero, eye, zero])])\n\n            MeSig = self.mesh.get_edge_inner_product(x, invert_model=True)\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(x0, invert_model=True)\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec) * P.T\n\n        print(\"Testing EdgeInnerProduct Anisotropic InvProp\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=1164\n            )\n        )\n\n    def test_EdgeInnerProductAnisotropicDerivInvMat(self):\n        def fun(x):\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([zero, eye, zero])])\n\n            MeSig = self.mesh.get_edge_inner_product(x, invert_matrix=True)\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(x0, invert_matrix=True)\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec) * P.T\n\n        print(\"Testing EdgeInnerProduct Anisotropic InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=643\n            )\n        )\n\n    def test_EdgeInnerProductAnisotropicDerivInvPropInvMat(self):\n        def fun(x):\n            x = np.repeat(np.atleast_2d(x), 3, axis=0).T\n            x0 = np.repeat(self.x0, 3, axis=0).T\n\n            zero = sp.csr_matrix((self.mesh.nC, self.mesh.nC))\n            eye = sp.eye(self.mesh.nC)\n            P = sp.vstack([sp.hstack([zero, eye, zero])])\n\n            MeSig = self.mesh.get_edge_inner_product(\n                x, invert_model=True, invert_matrix=True\n            )\n            MeSigDeriv = self.mesh.get_edge_inner_product_deriv(\n                x0, invert_model=True, invert_matrix=True\n            )\n            return MeSig * self.edge_vec, MeSigDeriv(self.edge_vec) * P.T\n\n        print(\"Testing EdgeInnerProduct Anisotropic InvProp InvMat\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=8654\n            )\n        )\n\n\nclass TestCylInnerProductsFaceProperties_Deriv(unittest.TestCase):\n    def setUp(self):\n        n = 2\n        self.mesh = discretize.CylindricalMesh([n, 1, n])\n        self.face_vec = rng.random(self.mesh.nF)\n        self.edge_vec = rng.random(self.mesh.nE)\n        # make up a smooth function\n        self.x0 = np.r_[\n            2 * self.mesh.gridFx[:, 0] ** 2 + self.mesh.gridFx[:, 2] ** 4,\n            2 * self.mesh.gridFz[:, 0] ** 2 + self.mesh.gridFz[:, 2] ** 4,\n        ]\n\n    def test_FaceInnerProductIsotropicDeriv(self):\n        def fun(x):\n            MfTau = self.mesh.get_face_inner_product_surface(x)\n            MfTauDeriv = self.mesh.get_face_inner_product_surface_deriv(self.x0)\n            return MfTau * self.face_vec, MfTauDeriv(self.face_vec)\n\n        print(\"Testing FaceInnerProduct Isotropic (Face Properties)\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=234\n            )\n        )\n\n    def test_FaceInnerProductIsotropicDerivInvProp(self):\n        def fun(x):\n            MfTau = self.mesh.get_face_inner_product_surface(x, invert_model=True)\n            MfTauDeriv = self.mesh.get_face_inner_product_surface_deriv(\n                self.x0, invert_model=True\n            )\n            return MfTau * self.face_vec, MfTauDeriv(self.face_vec)\n\n        print(\"Testing FaceInnerProduct Isotropic InvProp (Face Properties)\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=7543\n            )\n        )\n\n    def test_FaceInnerProductIsotropicDerivInvMat(self):\n        def fun(x):\n            MfTau = self.mesh.get_face_inner_product_surface(x, invert_matrix=True)\n            MfTauDeriv = self.mesh.get_face_inner_product_surface_deriv(\n                self.x0, invert_matrix=True\n            )\n            return MfTau * self.face_vec, MfTauDeriv(self.face_vec)\n\n        print(\"Testing FaceInnerProduct Isotropic InvMat (Face Properties)\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=2745725\n            )\n        )\n\n    def test_EdgeInnerProductIsotropicDeriv(self):\n        def fun(x):\n            MeTau = self.mesh.get_edge_inner_product_surface(x)\n            MeTauDeriv = self.mesh.get_edge_inner_product_surface_deriv(self.x0)\n            return MeTau * self.edge_vec, MeTauDeriv(self.edge_vec)\n\n        print(\"Testing EdgeInnerProduct Isotropic (Face Properties)\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=6654\n            )\n        )\n\n    def test_EdgeInnerProductIsotropicDerivInvProp(self):\n        def fun(x):\n            MeTau = self.mesh.get_edge_inner_product_surface(x, invert_model=True)\n            MeTauDeriv = self.mesh.get_edge_inner_product_surface_deriv(\n                self.x0, invert_model=True\n            )\n            return MeTau * self.edge_vec, MeTauDeriv(self.edge_vec)\n\n        print(\"Testing EdgeInnerProduct Isotropic InvProp (Face Properties)\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=4564\n            )\n        )\n\n    def test_EdgeInnerProductIsotropicDerivInvMat(self):\n        def fun(x):\n            MeTau = self.mesh.get_edge_inner_product_surface(x, invert_matrix=True)\n            MeTauDeriv = self.mesh.get_edge_inner_product_surface_deriv(\n                self.x0, invert_matrix=True\n            )\n            return MeTau * self.edge_vec, MeTauDeriv(self.edge_vec)\n\n        print(\"Testing EdgeInnerProduct Isotropic InvMat (Face Properties)\")\n        return self.assertTrue(\n            tests.check_derivative(\n                fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=2355\n            )\n        )\n"
  },
  {
    "path": "tests/cyl/test_cyl_io.py",
    "content": "import discretize\nimport pytest\nimport os\nimport numpy as np\n\ntry:\n    import matplotlib.pyplot as plt\nexcept ImportError:\n    plt = None\n\ntry:\n    import vtk\nexcept ImportError:\n    vtk = None\n\n\n@pytest.mark.skipif(vtk is None, reason=\"Requires vtk\")\ndef test_convert_zero_wrapped_to_vtk():\n    mesh = discretize.CylindricalMesh([20, 15, 10])\n    mesh.to_vtk()\n\n\n@pytest.mark.skipif(vtk is None, reason=\"Requires vtk\")\ndef test_convert_zero_nonwrapped_to_vtk():\n    mesh = discretize.CylindricalMesh([20, np.ones(3), 10])\n    mesh.to_vtk()\n\n\n@pytest.mark.skipif(vtk is None, reason=\"Requires vtk\")\ndef test_convert_nonzero_wrapped_to_vtk():\n    mesh = discretize.CylindricalMesh([20, 15, 10], origin=[1, 0, 0])\n    mesh.to_vtk()\n\n\n@pytest.mark.skipif(vtk is None, reason=\"Requires vtk\")\ndef test_convert_nonzero_nonwrapped_to_vtk():\n    mesh = discretize.CylindricalMesh([20, np.ones(3), 10], origin=[1, 0, 0])\n    mesh.to_vtk()\n\n\n@pytest.mark.skipif(plt is None, reason=\"Requires matplotlib\")\ndef test_convert_zero_wrapped_plot_grid():\n    mesh = discretize.CylindricalMesh([20, 15, 10])\n    mesh.plot_grid()\n\n\n@pytest.mark.skipif(plt is None, reason=\"Requires matplotlib\")\ndef test_convert_zero_nonwrapped_plot_grid():\n    mesh = discretize.CylindricalMesh([20, np.ones(3), 10])\n    mesh.plot_grid()\n\n\n@pytest.mark.skipif(plt is None, reason=\"Requires matplotlib\")\ndef test_convert_nonzero_wrapped_plot_grid():\n    mesh = discretize.CylindricalMesh([20, 15, 10], origin=[1, 0, 0])\n    mesh.plot_grid()\n\n\n@pytest.mark.skipif(plt is None, reason=\"Requires matplotlib\")\ndef test_convert_nonzero_nonwrapped_plot_grid():\n    mesh = discretize.CylindricalMesh([20, np.ones(3), 10], origin=[1, 0, 0])\n    mesh.plot_grid()\n\n\n@pytest.fixture(autouse=True)\ndef cleanup_files(monkeypatch):\n    files = [\"test.cyl\"]\n    yield\n    for file in files:\n        try:\n            os.remove(file)\n        except FileNotFoundError:\n            pass\n    if plt is not None:\n        plt.close(\"all\")\n"
  },
  {
    "path": "tests/cyl/test_cyl_operators.py",
    "content": "import numpy as np\nimport discretize\nimport discretize.tests as tests\nimport sympy as sp\nfrom sympy.vector import CoordSys3D, gradient, divergence, curl\nimport pytest\nimport scipy.sparse as spr\n\nC = CoordSys3D(\n    \"C\",\n    transformation=\"cylindrical\",\n    vector_names=list(\"rtz\"),\n    variable_names=list(\"RTZ\"),\n)\n\nrng = np.random.default_rng(563279)\n\n\ndef lambdify_vector(variabls, u_vecs, func):\n    funcs = [sp.lambdify(variabls, func.coeff(u_hat), \"numpy\") for u_hat in u_vecs]\n    return lambda *args: np.stack(\n        [g_func(*args) * np.ones_like(args[0]) for g_func in funcs], axis=-1\n    )\n\n\nu = C.R * (sp.sin(sp.pi * C.Z) * sp.sin(sp.pi * C.R) * sp.cos(C.T) + 1)\nsu = C.R * (sp.sin(sp.pi * C.Z) * sp.sin(sp.pi * C.R) + 1)\ngrad_u = gradient(u)\ngrad_su = gradient(su)\n\nw = (\n    sp.sin(2 * sp.pi * C.Z) * sp.sin(sp.pi * C.R) * sp.sin(C.T) * C.r\n    + sp.cos(sp.pi * C.Z) * sp.sin(sp.pi * C.R) * sp.sin(C.T) * C.t\n    + sp.sin(sp.pi * C.R) * sp.sin(C.T) * C.z\n)\nsw = (\n    sp.sin(2 * sp.pi * C.Z) * sp.sin(sp.pi * C.R) * C.r\n    + sp.cos(sp.pi * C.Z) * sp.sin(sp.pi * C.R) * C.t\n    + sp.sin(sp.pi * C.R) * C.z\n)\ndiv_w = divergence(w)\ncurl_w = curl(w)\n\ndiv_sw = divergence(sw)\ncurl_sw = curl(sw)\n\nu_func = sp.lambdify((C.R, C.T, C.Z), u, \"numpy\")\ngu_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), grad_u)\n\nw_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), w)\ndw_func = sp.lambdify((C.R, C.T, C.Z), div_w, \"numpy\")\ncw_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), curl_w)\n\nsu_func = sp.lambdify((C.R, C.T, C.Z), su, \"numpy\")\ngsu_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), grad_su)\n\nsw_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), sw)\ndsw_func = sp.lambdify((C.R, C.T, C.Z), div_sw, \"numpy\")\ncsw_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), curl_sw)\n\n\ndef setup_mesh(mesh_type, n):\n    if mesh_type == \"sym_full\":\n        mesh = discretize.CylindricalMesh([n, 1, n])\n        max_h = max(mesh.h[0].max(), mesh.h[2].max())\n    elif mesh_type == \"sym_ring\":\n        mesh = discretize.CylindricalMesh([n, 1, n], origin=[1, 0, 0])\n        max_h = max(mesh.h[0].max(), mesh.h[2].max())\n    elif mesh_type == \"quarter_pizza\":\n        ht = n * [np.pi / (4 * n)]\n        mesh = discretize.CylindricalMesh([n, ht, n])\n        max_h = max([np.max(hi) for hi in mesh.h])\n    elif mesh_type == \"full\":\n        mesh = discretize.CylindricalMesh([n, n, n])\n        max_h = max([np.max(hi) for hi in mesh.h])\n    elif mesh_type == \"ring\":\n        mesh = discretize.CylindricalMesh([n, n, n], origin=[1, 0, 0])\n        max_h = max([np.max(hi) for hi in mesh.h])\n    else:\n        # tensor styled (1/4 ring)\n        ht = n * [np.pi / (4 * n)]\n        mesh = discretize.CylindricalMesh([n, ht, n], origin=[1, 0, 0])\n        max_h = max([np.max(hi) for hi in mesh.h])\n    return mesh, max_h\n\n\nSYMMETRIC = {\n    \"sym_full\",\n    \"sym_ring\",\n}\n\nNONSYMMETRIC = {\n    \"quarter_pizza\",\n    \"full\",\n    \"ring\",\n    \"cyl_tensor\",\n}\n\nZEROSTART = {\n    \"sym_full\",\n    \"quarter_pizza\",\n    \"full\",\n}\n\nPARTAZIMUTH = {\n    \"cyl_tensor\",\n    \"quarter_pizza\",\n}\n\nMESHTYPES = SYMMETRIC | NONSYMMETRIC\n\n\ndef get_integration_limits(mesh_type):\n    if mesh_type in ZEROSTART:\n        r_lims = [0, 1]\n    else:\n        r_lims = [1, 2]\n\n    if mesh_type in PARTAZIMUTH:\n        t_lims = [0, sp.pi / 4]\n    else:\n        t_lims = [0, 2 * sp.pi]\n    z_lims = [0, 1]\n    return r_lims, t_lims, z_lims\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_edge_curl(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n\n        if mesh_type in SYMMETRIC:\n            w = sw_func\n            cw = csw_func\n        else:\n            w = w_func\n            cw = cw_func\n\n        e_vec = mesh.project_edge_vector(w(*mesh.edges.T))\n        C = mesh.edge_curl\n\n        num = C @ e_vec\n        ana = mesh.project_face_vector(cw(*mesh.faces.T))\n\n        err = np.linalg.norm((num - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_nodal_gradient(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n\n        u = u_func\n        gu = gu_func\n\n        n_vec = u(*mesh.nodes.T)\n        G = mesh.nodal_gradient\n\n        num = G @ n_vec\n        ana = mesh.project_edge_vector(gu(*mesh.edges.T))\n\n        err = np.linalg.norm((num - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_face_divergence(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n\n        if mesh_type in SYMMETRIC:\n            w = sw_func\n            dw = dsw_func\n        else:\n            w = w_func\n            dw = dw_func\n\n        f_vec = mesh.project_face_vector(w(*mesh.faces.T))\n        D = mesh.face_divergence\n\n        num = D @ f_vec\n        ana = dw(*mesh.cell_centers.T)\n\n        err = np.linalg.norm((num - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_ave_edge_to_face(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            u = su_func\n        else:\n            u = u_func\n        Ee = u(*mesh.edges.T)\n\n        ave = mesh.average_edge_to_face @ Ee\n        ana = u(*mesh.faces.T)\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_ave_edge_to_cell(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            u = su_func\n        else:\n            u = u_func\n        Ee = u(*mesh.edges.T)\n\n        ave = mesh.average_edge_to_cell @ Ee\n        ana = u(*mesh.cell_centers.T)\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_ave_face_to_cell(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            u = su_func\n        else:\n            u = u_func\n        Ee = u(*mesh.faces.T)\n\n        ave = mesh.average_face_to_cell @ Ee\n        ana = u(*mesh.cell_centers.T)\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_ave_cell_to_face(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            u = su_func\n        else:\n            u = u_func\n        Ee = u(*mesh.cell_centers.T)\n\n        ave = (mesh.average_cell_to_face @ Ee)[~mesh._is_boundary_face]\n        ana = u(*mesh.faces.T)[~mesh._is_boundary_face]\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_ave_node_to_cell(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            u = su_func\n        else:\n            u = u_func\n        Ee = u(*mesh.nodes.T)\n\n        ave = mesh.average_node_to_cell @ Ee\n        ana = u(*mesh.cell_centers.T)\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_ave_node_to_face(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            u = su_func\n        else:\n            u = u_func\n        Ee = u(*mesh.nodes.T)\n\n        ave = mesh.average_node_to_face @ Ee\n        ana = u(*mesh.faces.T)\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_ave_face_to_cell_vector(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            w = sw_func\n        else:\n            w = w_func\n\n        Ee = mesh.project_face_vector(w(*mesh.faces.T))\n\n        ave = mesh.average_face_to_cell_vector @ Ee\n        ana = w(*mesh.cell_centers.T)\n        if mesh_type in SYMMETRIC:\n            ana = ana[:, [0, 2]]\n        ana = ana.reshape(-1, order=\"F\")\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_ave_edge_to_cell_vector(mesh_type):\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if mesh_type in SYMMETRIC:\n            w = sw_func\n        else:\n            w = w_func\n\n        Ee = mesh.project_edge_vector(w(*mesh.edges.T))\n\n        ave = mesh.average_edge_to_cell_vector @ Ee\n        ana = w(*mesh.cell_centers.T)\n        if mesh_type in SYMMETRIC:\n            ana = ana[:, [1]]\n        ana = ana.reshape(-1, order=\"F\")\n        err = np.linalg.norm((ave - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_mimetic_div_curl(mesh_type):\n    mesh, _ = setup_mesh(mesh_type, 10)\n\n    v = rng.random(mesh.n_edges)\n    divcurlv = mesh.face_divergence @ (mesh.edge_curl @ v)\n    np.testing.assert_allclose(divcurlv, 0, atol=1e-11)\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_mimetic_curl_grad(mesh_type):\n    mesh, _ = setup_mesh(mesh_type, 10)\n\n    v = rng.random(mesh.n_nodes)\n    divcurlv = mesh.edge_curl @ (mesh.nodal_gradient @ v)\n    np.testing.assert_allclose(divcurlv, 0, atol=1e-11)\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_simple_edge_inner_product(mesh_type):\n    r_lims, t_lims, z_lims = get_integration_limits(mesh_type)\n\n    if mesh_type in SYMMETRIC:\n        # only theta edges\n        e_ana = C.R * C.t\n    else:\n        e_ana = 1 * C.r + C.R * C.t + 1 * C.z\n    e_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), e_ana)\n\n    ana = float(\n        sp.integrate(\n            e_ana.dot(e_ana) * C.R, (C.R, *r_lims), (C.T, *t_lims), (C.Z, *z_lims)\n        )\n    )\n\n    mesh, _ = setup_mesh(mesh_type, 10)\n\n    e = mesh.project_edge_vector(e_func(*mesh.edges.T))\n    Me = mesh.get_edge_inner_product()\n    num = e.T @ Me @ e\n    np.testing.assert_allclose(num, ana)\n\n\n@pytest.mark.parametrize(\"mesh_type\", MESHTYPES)\ndef test_simple_face_inner_product(mesh_type):\n    r_lims, t_lims, z_lims = get_integration_limits(mesh_type)\n\n    if mesh_type in SYMMETRIC:\n        # no theta faces\n        f_ana = C.R * C.r + 1 * C.z\n    else:\n        f_ana = C.R * C.r + 1 * C.t + 1 * C.z\n    f_func = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), f_ana)\n\n    ana = float(\n        sp.integrate(\n            f_ana.dot(f_ana) * C.R, (C.R, *r_lims), (C.T, *t_lims), (C.Z, *z_lims)\n        )\n    )\n\n    mesh, _ = setup_mesh(mesh_type, 10)\n\n    f = mesh.project_face_vector(f_func(*mesh.faces.T))\n    Mf = mesh.get_face_inner_product()\n    num = f.T @ Mf @ f\n    np.testing.assert_allclose(num, ana)\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_simple_edge_ave(mesh_type):\n    func = lambda r, t, z: np.c_[r, r, z]\n    mesh, _ = setup_mesh(mesh_type, 10)\n    e_ana = mesh.project_edge_vector(func(*mesh.edges.T))\n    ave_e = mesh.aveE2CCV @ e_ana\n\n    ana_cc = func(*mesh.cell_centers.T).reshape(-1, order=\"F\")\n\n    np.testing.assert_allclose(ave_e, ana_cc)\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_simple_face_ave(mesh_type):\n    func = lambda r, t, z: np.c_[r, r, z]\n    mesh, _ = setup_mesh(mesh_type, 10)\n    f_ana = mesh.project_face_vector(func(*mesh.faces.T))\n    ave_f = mesh.aveF2CCV @ f_ana\n\n    ana_cc = func(*mesh.cell_centers.T).reshape(-1, order=\"F\")\n\n    np.testing.assert_allclose(ave_f, ana_cc)\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_gradient_boundary_integral(mesh_type):\n    u_simp = C.R**2 + sp.cos(C.T) + C.Z**2\n    gu_simp = gradient(u_simp)\n    v_simp = C.R**2 * C.r + C.R * C.t + C.R * C.Z * C.z\n\n    u_sfunc = sp.lambdify((C.R, C.T, C.Z), u_simp, \"numpy\")\n    v_sfunc = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), v_simp)\n\n    # int_V grad_u dot w dV\n    r_lims, t_lims, z_lims = get_integration_limits(mesh_type)\n\n    ana = float(\n        sp.integrate(\n            gu_simp.dot(v_simp) * C.R, (C.R, *r_lims), (C.T, *t_lims), (C.Z, *z_lims)\n        )\n    )\n\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n\n        u_cc = u_sfunc(*mesh.cell_centers.T)\n        w_f = mesh.project_face_vector(v_sfunc(*mesh.faces.T))\n        u_bf = u_sfunc(*mesh.boundary_faces.T)\n\n        D = mesh.face_divergence\n        M_c = spr.diags(mesh.cell_volumes)\n        M_bf = mesh.boundary_face_scalar_integral\n\n        d1 = (w_f.T @ D.T) @ M_c @ u_cc\n        d2 = w_f.T @ M_bf @ u_bf\n\n        discrete_val = -d1 + d2\n        err = np.abs(ana - discrete_val)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_div_boundary_integral(mesh_type):\n    u_simp = C.R**2 + sp.cos(C.T) + C.Z**2\n    v_simp = C.R**2 * C.r + C.R * C.t + C.R * C.Z * C.z\n    dv_simp = divergence(v_simp)\n\n    u_sfunc = sp.lambdify((C.R, C.T, C.Z), u_simp, \"numpy\")\n    v_sfunc = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), v_simp)\n\n    r_lims, t_lims, z_lims = get_integration_limits(mesh_type)\n\n    ana = float(\n        sp.integrate(\n            u_simp * dv_simp * C.R, (C.R, *r_lims), (C.T, *t_lims), (C.Z, *z_lims)\n        )\n    )\n\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n\n        u_n = u_sfunc(*mesh.nodes.T)\n        v_e = mesh.project_edge_vector(v_sfunc(*mesh.edges.T))\n        v_bn = v_sfunc(*mesh.boundary_nodes.T).reshape(-1, order=\"F\")\n\n        M_e = mesh.get_edge_inner_product()\n        G = mesh.nodal_gradient\n        M_bn = mesh.boundary_node_vector_integral\n\n        d1 = (u_n.T @ G.T) @ M_e @ v_e\n        d2 = u_n.T @ (M_bn @ v_bn)\n\n        discrete_val = -d1 + d2\n        err = np.abs(ana - discrete_val)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_curl_boundary_integral(mesh_type):\n    w_simp = C.R**2 * C.Z * C.r + sp.sin(C.R) * C.Z * C.t + C.R**3 * C.z\n    v_simp = C.R**2 * C.r + C.R * C.t + C.R * C.Z * C.z\n    cw_simp = curl(w_simp)\n\n    w_sfunc = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), w_simp)\n    v_sfunc = lambdify_vector((C.R, C.T, C.Z), (C.r, C.t, C.z), v_simp)\n\n    r_lims, t_lims, z_lims = get_integration_limits(mesh_type)\n\n    ana = float(\n        sp.integrate(\n            cw_simp.dot(v_simp) * C.R, (C.R, *r_lims), (C.T, *t_lims), (C.Z, *z_lims)\n        )\n    )\n\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n\n        w_f = mesh.project_face_vector(w_sfunc(*mesh.faces.T))\n        v_e = mesh.project_edge_vector(v_sfunc(*mesh.edges.T))\n        w_be = w_sfunc(*mesh.boundary_edges.T).reshape(-1, order=\"F\")\n\n        M_f = mesh.get_face_inner_product()\n        Curl = mesh.edge_curl\n        M_be = mesh.boundary_edge_vector_integral\n\n        d1 = (v_e.T @ Curl.T) @ M_f @ w_f\n        d2 = v_e.T @ (M_be @ w_be)\n\n        discrete_val = d1 - d2\n        err = np.abs(ana - discrete_val)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n\n\n@pytest.mark.parametrize(\n    \"location_type\",\n    [\n        \"cell_centers\",\n        \"nodes\",\n        \"edges_x\",\n        \"edges_y\",\n        \"edges_z\",\n        \"faces_x\",\n        \"faces_y\",\n        \"faces_z\",\n    ],\n)\n@pytest.mark.parametrize(\"mesh_type\", NONSYMMETRIC)\ndef test_interpolation(mesh_type, location_type):\n    u_func = lambda x, y, z: x**2 + y**2 + z**2\n\n    interp_points = (\n        np.mgrid[0.3:0.8:5j, np.pi / 10 : np.pi / 5 : 5j, 0.3:0.8:5j].reshape(3, -1).T\n    )\n    interp_points.shape\n\n    if mesh_type not in ZEROSTART:\n        interp_points[:, 0] += 1\n    ana = u_func(*interp_points.T)\n\n    def get_error(n_cells):\n        mesh, h = setup_mesh(mesh_type, n_cells)\n        if \"edges\" in location_type:\n            grid = mesh.edges\n        elif \"faces\" in location_type:\n            grid = mesh.faces\n        else:\n            grid = getattr(mesh, location_type)\n        A = mesh.get_interpolation_matrix(interp_points, location_type=location_type)\n\n        num = A @ u_func(*grid.T)\n        err = np.linalg.norm((num - ana), np.inf)\n        return err, h\n\n    tests.assert_expected_order(get_error, [10, 20, 30])\n"
  },
  {
    "path": "tests/simplex/__init__.py",
    "content": "if __name__ == \"__main__\":\n    import glob\n    import unittest\n\n    test_file_strings = glob.glob(\"test_*.py\")\n    module_strings = [strng[0 : len(strng) - 3] for strng in test_file_strings]\n    suites = [\n        unittest.defaultTestLoader.loadTestsFromName(strng) for strng in module_strings\n    ]\n    testSuite = unittest.TestSuite(suites)\n\n    unittest.TextTestRunner(verbosity=2).run(testSuite)\n"
  },
  {
    "path": "tests/simplex/test_inner_products.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\nimport scipy.sparse as sp\nfrom discretize.utils import example_simplex_mesh\n\nrng = np.random.default_rng(4421)\n\n\ndef u(*args):\n    if len(args) == 1:\n        x = args[0]\n        return x**3\n    if len(args) == 2:\n        x, y = args\n        return x**3 + y**2\n    x, y, z = args\n    return x**3 + y**2 + z**4\n\n\ndef v(*args):\n    if len(args) == 1:\n        x = args[0]\n        return 2 * x**2\n    if len(args) == 2:\n        x, y = args\n        return np.c_[2 * x**2, 3 * y**3]\n    x, y, z = args\n    return np.c_[2 * x**2, 3 * y**3, -4 * z**2]\n\n\ndef w(*args):\n    if len(args) == 2:\n        x, y = args\n        return np.c_[(y - 2) ** 2, (x + 2) ** 2]\n    x, y, z = args\n    return np.c_[(y - 2) ** 2 + z**2, (x + 2) ** 2 - (z - 4) ** 2, y**2 - x**2]\n\n\nclass TestInnerProducts2D(discretize.tests.OrderTest):\n    meshSizes = [8, 16, 32]\n    meshTypes = [\"uniform simplex mesh\"]\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        z = 5  # Because 5 is just such a great number.\n\n        ex = lambda x, y: x**2 + y * z\n        ey = lambda x, y: (z**2) * x + y * z\n\n        sigma1 = lambda x, y: x * y + 1\n        sigma2 = lambda x, y: x * z + 2\n        sigma3 = lambda x, y: 3 + z * y\n\n        mesh = self.M\n\n        cc = mesh.cell_centers\n        if self.sigmaTest == 1:\n            sigma = np.c_[sigma1(*cc.T)]\n            analytic = 144877.0 / 360  # Found using sympy.\n        elif self.sigmaTest == 2:\n            sigma = np.r_[sigma1(*cc.T), sigma2(*cc.T)]\n            analytic = 189959.0 / 120  # Found using sympy.\n        elif self.sigmaTest == 3:\n            sigma = np.c_[\n                sigma1(*cc.T),\n                sigma2(*cc.T),\n                sigma3(*cc.T),\n            ]\n            analytic = 781427.0 / 360  # Found using sympy.\n\n        if self.location == \"edges\":\n            p = mesh.edges\n            Ec = np.c_[ex(*p.T), ey(*p.T)]\n            E = mesh.project_edge_vector(Ec)\n            if self.invert_model:\n                sigma = discretize.utils.inverse_property_tensor(mesh, sigma)\n            A = mesh.get_edge_inner_product(sigma, invert_model=self.invert_model)\n            numeric = E.T.dot(A.dot(E))\n\n        elif self.location == \"faces\":\n            p = mesh.faces\n            Fc = np.c_[ex(*p.T), ey(*p.T)]\n            F = mesh.project_face_vector(Fc)\n\n            if self.invert_model:\n                sigma = discretize.utils.inverse_property_tensor(mesh, sigma)\n\n            A = self.M.get_face_inner_product(sigma, invert_model=self.invert_model)\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order2_edges(self):\n        self.name = \"Edge Inner Product - Anisotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 2\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order2_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Anisotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 2\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_edges(self):\n        self.name = \"Edge Inner Product - Full Tensor\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Full Tensor - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order2_faces(self):\n        self.name = \"Face Inner Product - Anisotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 2\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order2_faces_invert_model(self):\n        self.name = \"Face Inner Product - Anisotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 2\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_faces(self):\n        self.name = \"Face Inner Product - Full Tensor\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_faces_invert_model(self):\n        self.name = \"Face Inner Product - Full Tensor - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProducts3D(discretize.tests.OrderTest):\n    meshSizes = [8, 16, 32]\n    meshTypes = [\"uniform simplex mesh\"]\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        ex = lambda x, y, z: x**2 + y * z\n        ey = lambda x, y, z: (z**2) * x + y * z\n        ez = lambda x, y, z: y**2 + x * z\n\n        sigma1 = lambda x, y, z: x * y + 1\n        sigma2 = lambda x, y, z: x * z + 2\n        sigma3 = lambda x, y, z: 3 + z * y\n        sigma4 = lambda x, y, z: 0.1 * x * y * z\n        sigma5 = lambda x, y, z: 0.2 * x * y\n        sigma6 = lambda x, y, z: 0.1 * z\n\n        mesh = self.M\n\n        cc = mesh.cell_centers\n        if self.sigmaTest == 1:\n            sigma = np.c_[sigma1(*cc.T)]\n            analytic = 647.0 / 360  # Found using sympy.\n        elif self.sigmaTest == 3:\n            sigma = np.r_[sigma1(*cc.T), sigma2(*cc.T), sigma3(*cc.T)]\n            analytic = 37.0 / 12  # Found using sympy.\n        elif self.sigmaTest == 6:\n            sigma = np.c_[\n                sigma1(*cc.T),\n                sigma2(*cc.T),\n                sigma3(*cc.T),\n                sigma4(*cc.T),\n                sigma5(*cc.T),\n                sigma6(*cc.T),\n            ]\n            analytic = 69881.0 / 21600  # Found using sympy.\n\n        if self.location == \"edges\":\n            cart = lambda g: np.c_[ex(*g.T), ey(*g.T), ez(*g.T)]\n            Ec = cart(mesh.edges)\n            E = mesh.project_edge_vector(Ec)\n            if self.invert_model:\n                sigma = discretize.utils.inverse_property_tensor(mesh, sigma)\n            A = mesh.get_edge_inner_product(sigma, invert_model=self.invert_model)\n            numeric = E.T.dot(A.dot(E))\n\n        elif self.location == \"faces\":\n            cart = lambda g: np.c_[ex(*g.T), ey(*g.T), ez(*g.T)]\n            Fc = cart(mesh.faces)\n            F = mesh.project_face_vector(Fc)\n\n            if self.invert_model:\n                sigma = discretize.utils.inverse_property_tensor(mesh, sigma)\n\n            A = self.M.get_face_inner_product(sigma, invert_model=self.invert_model)\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_edges(self):\n        self.name = \"Edge Inner Product - Anisotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Anisotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order6_edges(self):\n        self.name = \"Edge Inner Product - Full Tensor\"\n        self.location = \"edges\"\n        self.sigmaTest = 6\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order6_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Full Tensor - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 6\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_faces(self):\n        self.name = \"Face Inner Product - Anisotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_faces_invert_model(self):\n        self.name = \"Face Inner Product - Anisotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order6_faces(self):\n        self.name = \"Face Inner Product - Full Tensor\"\n        self.location = \"faces\"\n        self.sigmaTest = 6\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order6_faces_invert_model(self):\n        self.name = \"Face Inner Product - Full Tensor - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 6\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsDerivs(unittest.TestCase):\n    def doTestFace(self, h, rep):\n        nodes, simplices = example_simplex_mesh(h)\n        mesh = discretize.SimplexMesh(nodes, simplices)\n        v = rng.random(mesh.n_faces)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep)\n\n        def fun(sig):\n            M = mesh.get_face_inner_product(sig)\n            Md = mesh.get_face_inner_product_deriv(sig)\n            return M * v, Md(v)\n\n        print(\"Face\", rep)\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=5352\n        )\n\n    def doTestEdge(self, h, rep):\n        nodes, simplices = example_simplex_mesh(h)\n        mesh = discretize.SimplexMesh(nodes, simplices)\n        v = rng.random(mesh.n_edges)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep)\n\n        def fun(sig):\n            M = mesh.get_edge_inner_product(sig)\n            Md = mesh.get_edge_inner_product_deriv(sig)\n            return M * v, Md(v)\n\n        print(\"Edge\", rep)\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=532\n        )\n\n    def test_FaceIP_2D_float(self):\n        self.assertTrue(self.doTestFace([10, 4], 0))\n\n    def test_FaceIP_3D_float(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 0))\n\n    def test_FaceIP_2D_isotropic(self):\n        self.assertTrue(self.doTestFace([10, 4], 1))\n\n    def test_FaceIP_3D_isotropic(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 1))\n\n    def test_FaceIP_2D_anisotropic(self):\n        self.assertTrue(self.doTestFace([10, 4], 2))\n\n    def test_FaceIP_3D_anisotropic(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 3))\n\n    def test_FaceIP_2D_tensor(self):\n        self.assertTrue(self.doTestFace([10, 4], 3))\n\n    def test_FaceIP_3D_tensor(self):\n        self.assertTrue(self.doTestFace([10, 4, 5], 6))\n\n    def test_EdgeIP_2D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4], 0))\n\n    def test_EdgeIP_3D_float(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 0))\n\n    def test_EdgeIP_2D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4], 1))\n\n    def test_EdgeIP_3D_isotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 1))\n\n    def test_EdgeIP_2D_anisotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4], 2))\n\n    def test_EdgeIP_3D_anisotropic(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 3))\n\n    def test_EdgeIP_2D_tensor(self):\n        self.assertTrue(self.doTestEdge([10, 4], 3))\n\n    def test_EdgeIP_3D_tensor(self):\n        self.assertTrue(self.doTestEdge([10, 4, 5], 6))\n\n\nclass Test2DBoundaryIntegral(discretize.tests.OrderTest):\n    meshSizes = [8, 16, 32]\n    meshTypes = [\"uniform simplex mesh\"]\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        mesh = self.M\n        if self.myTest == \"cell_grad\":\n            # Functions:\n            u_cc = u(*mesh.cell_centers.T)\n            v_f = mesh.project_face_vector(v(*mesh.faces.T))\n            u_bf = u(*mesh.boundary_faces.T)\n\n            D = mesh.face_divergence\n            M_c = sp.diags(mesh.cell_volumes)\n            M_bf = mesh.boundary_face_scalar_integral\n\n            discrete_val = -(v_f.T @ D.T) @ M_c @ u_cc + v_f.T @ (M_bf @ u_bf)\n            true_val = 12 / 5\n        elif self.myTest == \"edge_div\":\n            u_n = u(*mesh.nodes.T)\n            v_e = mesh.project_edge_vector(v(*mesh.edges.T))\n            v_bn = v(*mesh.boundary_nodes.T).reshape(-1, order=\"F\")\n\n            M_e = mesh.get_edge_inner_product()\n            G = mesh.nodal_gradient\n            M_bn = mesh.boundary_node_vector_integral\n\n            discrete_val = -(u_n.T @ G.T) @ M_e @ v_e + u_n.T @ (M_bn @ v_bn)\n            true_val = 241 / 60\n        elif self.myTest == \"face_curl\":\n            w_e = mesh.project_edge_vector(w(*mesh.edges.T))\n            u_c = u(*mesh.cell_centers.T)\n            u_be = u(*mesh.boundary_edges.T)\n\n            M_c = sp.diags(mesh.cell_volumes)\n            Curl = mesh.edge_curl\n            M_be = mesh.boundary_edge_vector_integral\n\n            discrete_val = (w_e.T @ Curl.T) @ M_c @ u_c - w_e.T @ (M_be @ u_be)\n            true_val = -173 / 30\n\n        return np.abs(discrete_val - true_val)\n\n    def test_orderWeakCellGradIntegral(self):\n        self.name = \"2D - weak cell gradient integral w/boundary\"\n        self.myTest = \"cell_grad\"\n        self.orderTest()\n\n    def test_orderWeakEdgeDivIntegral(self):\n        self.name = \"2D - weak edge divergence integral w/boundary\"\n        self.myTest = \"edge_div\"\n        self.orderTest()\n\n    def test_orderWeakFaceCurlIntegral(self):\n        self.name = \"2D - weak face curl integral w/boundary\"\n        self.myTest = \"face_curl\"\n        self.orderTest()\n\n\nclass Test3DBoundaryIntegral(discretize.tests.OrderTest):\n    meshSizes = [8, 16, 32]\n    meshTypes = [\"uniform simplex mesh\"]\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        mesh = self.M\n        if self.myTest == \"cell_grad\":\n            # Functions:\n            u_cc = u(*mesh.cell_centers.T)\n            v_f = mesh.project_face_vector(v(*mesh.faces.T))\n            u_bf = u(*mesh.boundary_faces.T)\n\n            D = mesh.face_divergence\n            M_c = sp.diags(mesh.cell_volumes)\n            M_bf = mesh.boundary_face_scalar_integral\n\n            discrete_val = -(v_f.T @ D.T) @ M_c @ u_cc + v_f.T @ (M_bf @ u_bf)\n\n            true_val = -4 / 15\n        elif self.myTest == \"edge_div\":\n            u_n = u(*mesh.nodes.T)\n            v_e = mesh.project_edge_vector(v(*mesh.edges.T))\n            v_bn = v(*mesh.boundary_nodes.T).reshape(-1, order=\"F\")\n\n            M_e = mesh.get_edge_inner_product()\n            G = mesh.nodal_gradient\n            M_bn = mesh.boundary_node_vector_integral\n\n            discrete_val = -(u_n.T @ G.T) @ M_e @ v_e + u_n.T @ (M_bn @ v_bn)\n            true_val = 27 / 20\n\n        elif self.myTest == \"face_curl\":\n            w_f = mesh.project_face_vector(w(*mesh.faces.T))\n            v_e = mesh.project_edge_vector(v(*mesh.edges.T))\n            w_be = w(*mesh.boundary_edges.T).reshape(-1, order=\"F\")\n\n            M_f = mesh.get_face_inner_product()\n            Curl = mesh.edge_curl\n            M_be = mesh.boundary_edge_vector_integral\n\n            discrete_val = (v_e.T @ Curl.T) @ M_f @ w_f - v_e.T @ (M_be @ w_be)\n            true_val = -79 / 6\n\n        return np.abs(discrete_val - true_val)\n\n    def test_orderWeakCellGradIntegral(self):\n        self.name = \"3D - weak cell gradient integral w/boundary\"\n        self.myTest = \"cell_grad\"\n        self.orderTest()\n\n    def test_orderWeakEdgeDivIntegral(self):\n        self.name = \"3D - weak edge divergence integral w/boundary\"\n        self.myTest = \"edge_div\"\n        self.orderTest()\n\n    def test_orderWeakFaceCurlIntegral(self):\n        self.name = \"3D - weak face curl integral w/boundary\"\n        self.myTest = \"face_curl\"\n        self.orderTest()\n\n\nclass TestBadModels(unittest.TestCase):\n    def setUp(self):\n        n = 8\n        points, simplices = example_simplex_mesh((n, n))\n        self.mesh = discretize.SimplexMesh(points, simplices)\n\n    def test_bad_model_size(self):\n        mesh = self.mesh\n        bad_model = rng.random((mesh.n_cells, 5))\n        with self.assertRaises(ValueError):\n            mesh.get_face_inner_product(bad_model)\n        with self.assertRaises(ValueError):\n            mesh.get_face_inner_product_deriv(bad_model)\n\n    def test_cant_invert(self):\n        mesh = self.mesh\n        good_model = rng.random(mesh.n_cells)\n        with self.assertRaises(NotImplementedError):\n            mesh.get_face_inner_product(good_model, invert_matrix=True)\n        with self.assertRaises(NotImplementedError):\n            mesh.get_face_inner_product_deriv(good_model, invert_matrix=True)\n        with self.assertRaises(NotImplementedError):\n            mesh.get_face_inner_product_deriv(good_model, invert_model=True)\n        with self.assertRaises(NotImplementedError):\n            mesh.get_edge_inner_product(good_model, invert_matrix=True)\n        with self.assertRaises(NotImplementedError):\n            mesh.get_edge_inner_product_deriv(good_model, invert_matrix=True)\n        with self.assertRaises(NotImplementedError):\n            mesh.get_edge_inner_product_deriv(good_model, invert_model=True)\n"
  },
  {
    "path": "tests/simplex/test_interpolation.py",
    "content": "import numpy as np\nimport discretize\nfrom discretize.utils import example_simplex_mesh\n\n\nclass TestInterpolation2d(discretize.tests.OrderTest):\n    name = \"Interpolation 2D\"\n    meshSizes = [8, 16, 32, 64]\n    meshTypes = [\"uniform simplex mesh\"]\n    interp_points = np.stack(np.mgrid[0.25:0.75:32j, 0.25:0.75:32j], axis=-1).reshape(\n        -1, 2\n    )\n    meshDimension = 2\n    expectedOrders = 1\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        funX = lambda x, y: -(y**2)\n        funY = lambda x, y: x**2\n\n        if \"x\" in self.type:\n            ana = funX(*self.interp_points.T)\n        elif \"y\" in self.type:\n            ana = funY(*self.interp_points.T)\n        else:\n            ana = funX(*self.interp_points.T) + funY(*self.interp_points.T)\n\n        mesh = self.M\n        if \"F\" in self.type:\n            Fc = np.c_[funX(*mesh.faces.T), funY(*mesh.faces.T)]\n            grid = mesh.project_face_vector(Fc)\n        elif \"E\" in self.type:\n            Ec = np.c_[funX(*mesh.edges.T), funY(*mesh.edges.T)]\n            grid = mesh.project_edge_vector(Ec)\n        elif \"CC\" == self.type:\n            grid = funX(*mesh.cell_centers.T) + funY(*mesh.cell_centers.T)\n        elif \"N\" == self.type:\n            grid = funX(*mesh.nodes.T) + funY(*mesh.nodes.T)\n\n        comp = mesh.get_interpolation_matrix(self.interp_points, self.type) * grid\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_orderCC(self):\n        self.type = \"CC\"\n        self.expectedOrders = 2\n        self.name = \"Interpolation 2D: CC\"\n        self.expectedOrders = 1\n        self.orderTest()\n\n    def test_orderN(self):\n        self.type = \"N\"\n        self.expectedOrders = 2\n        self.name = \"Interpolation 2D: N\"\n        self.orderTest()\n        self.expectedOrders = 1\n\n    def test_orderFx(self):\n        self.type = \"Fx\"\n        self.name = \"Interpolation 2D: Fx\"\n        self.orderTest()\n\n    def test_orderFy(self):\n        self.type = \"Fy\"\n        self.name = \"Interpolation 2D: Fy\"\n        self.orderTest()\n\n    def test_orderEx(self):\n        self.type = \"Ex\"\n        self.name = \"Interpolation 2D: Ex\"\n        self.orderTest()\n\n    def test_orderEy(self):\n        self.type = \"Ey\"\n        self.name = \"Interpolation 2D: Ey\"\n        self.orderTest()\n\n\nclass TestInterpolation3d(discretize.tests.OrderTest):\n    name = \"Interpolation 3D\"\n    meshSizes = [5, 10, 20, 40]\n    meshTypes = [\"uniform simplex mesh\"]\n    interp_points = np.stack(\n        np.mgrid[0.25:0.75:32j, 0.25:0.75:32j, 0.25:0.75:32j], axis=-1\n    ).reshape(-1, 3)\n    meshDimension = 3\n    expectedOrders = 1\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        funX = lambda x, y, z: np.cos(2 * np.pi * y)\n        funY = lambda x, y, z: np.cos(2 * np.pi * z)\n        funZ = lambda x, y, z: np.cos(2 * np.pi * x)\n\n        if \"x\" in self.type:\n            ana = funX(*self.interp_points.T)\n        elif \"y\" in self.type:\n            ana = funY(*self.interp_points.T)\n        elif \"z\" in self.type:\n            ana = funZ(*self.interp_points.T)\n        else:\n            ana = (\n                funX(*self.interp_points.T)\n                + funY(*self.interp_points.T)\n                + funZ(*self.interp_points.T)\n            )\n\n        mesh = self.M\n        if \"F\" in self.type:\n            Fc = np.c_[funX(*mesh.faces.T), funY(*mesh.faces.T), funZ(*mesh.faces.T)]\n            grid = mesh.project_face_vector(Fc)\n        elif \"E\" in self.type:\n            Ec = np.c_[funX(*mesh.edges.T), funY(*mesh.edges.T), funZ(*mesh.edges.T)]\n            grid = mesh.project_edge_vector(Ec)\n        elif \"CC\" == self.type:\n            grid = (\n                funX(*mesh.cell_centers.T)\n                + funY(*mesh.cell_centers.T)\n                + funZ(*mesh.cell_centers.T)\n            )\n        elif \"N\" == self.type:\n            grid = funX(*mesh.nodes.T) + funY(*mesh.nodes.T) + funZ(*mesh.nodes.T)\n\n        comp = mesh.get_interpolation_matrix(self.interp_points, self.type) * grid\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_orderCC(self):\n        self.type = \"CC\"\n        self.expectedOrders = 2\n        self.name = \"Interpolation 3D: CC\"\n        self.orderTest()\n        self.expectedOrders = 1\n\n    def test_orderN(self):\n        self.type = \"N\"\n        self.expectedOrders = 2\n        self.name = \"Interpolation 3D: N\"\n        self.orderTest()\n        self.expectedOrders = 1\n\n    def test_orderFx(self):\n        self.type = \"Fx\"\n        self.name = \"Interpolation 3D: Fx\"\n        self.orderTest()\n\n    def test_orderFy(self):\n        self.type = \"Fy\"\n        self.name = \"Interpolation 3D: Fy\"\n        self.orderTest()\n\n    def test_orderFz(self):\n        self.type = \"Fz\"\n        self.name = \"Interpolation 3D: Fz\"\n        self.orderTest()\n\n    def test_orderEx(self):\n        self.type = \"Ex\"\n        self.name = \"Interpolation 3D: Ex\"\n        self.orderTest()\n\n    def test_orderEy(self):\n        self.type = \"Ey\"\n        self.name = \"Interpolation 3D: Ey\"\n        self.orderTest()\n\n    def test_orderEz(self):\n        self.type = \"Ez\"\n        self.name = \"Interpolation 3D: Ez\"\n        self.orderTest()\n\n\nclass TestAveraging(discretize.tests.OrderTest):\n    meshSizes = [8, 16, 32]\n    meshTypes = [\"uniform simplex mesh\"]\n\n    def setupMesh(self, n):\n        dim = self.meshDimension\n        points, simplices = example_simplex_mesh(dim * (n,))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        mesh = self.M\n        if mesh.dim == 2:\n            func = lambda x, y: np.cos(x**2) - np.sin(y**2) + 2 * x * y\n        else:\n            func = (\n                lambda x, y, z: np.cos(x**2)\n                - np.sin(y**2)\n                + 2 * x * y\n                + np.cos(2 * z) ** 2\n                + z**3\n            )\n\n        if self.target_type == \"CC\":\n            interp_points = mesh.cell_centers\n        elif self.target_type == \"N\":\n            interp_points = mesh.nodes\n        elif self.target_type == \"E\":\n            interp_points = mesh.edges\n        elif self.target_type == \"F\":\n            interp_points = mesh.faces\n        ana = func(*interp_points.T)\n\n        if self.source_type == \"CC\":\n            source_points = mesh.cell_centers\n        elif self.source_type == \"N\":\n            source_points = mesh.nodes\n        elif self.source_type == \"E\":\n            source_points = mesh.edges\n        elif self.source_type == \"F\":\n            source_points = mesh.faces\n\n        v = func(*source_points.T)\n\n        if self.source_type == \"N\":\n            if self.target_type == \"CC\":\n                avg_mat = mesh.average_node_to_cell\n            elif self.target_type == \"E\":\n                avg_mat = mesh.average_node_to_edge\n            elif self.target_type == \"F\":\n                avg_mat = mesh.average_node_to_face\n        elif self.source_type == \"CC\":\n            if self.target_type == \"N\":\n                avg_mat = mesh.average_cell_to_node\n            elif self.target_type == \"E\":\n                avg_mat = mesh.average_cell_to_edge\n            elif self.target_type == \"F\":\n                avg_mat = mesh.average_cell_to_face\n        elif self.source_type == \"E\":\n            if self.target_type == \"CC\":\n                avg_mat = mesh.average_edge_to_cell\n            elif self.target_type == \"F\":\n                avg_mat = mesh.average_edge_to_face\n        elif self.source_type == \"F\":\n            if self.target_type == \"CC\":\n                avg_mat = mesh.average_face_to_cell\n\n        comp = avg_mat @ v\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    # 2D\n    def test_AvgN2CC_2D(self):\n        self.source_type = \"N\"\n        self.target_type = \"CC\"\n        self.name = \"average_node_to_cell 2D\"\n        self.meshDimension = 2\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgN2F_2D(self):\n        self.source_type = \"N\"\n        self.target_type = \"F\"\n        self.name = \"average_node_to_face 2D\"\n        self.meshDimension = 2\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgN2E_2D(self):\n        self.source_type = \"N\"\n        self.target_type = \"E\"\n        self.name = \"average_node_to_edge 2D\"\n        self.meshDimension = 2\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgCC2E_2D(self):\n        self.source_type = \"CC\"\n        self.target_type = \"E\"\n        self.name = \"average_cell_to_edge 2D\"\n        self.meshDimension = 2\n        self.expectedOrders = 1\n        self.orderTest()\n\n    def test_AvgE2CC_2D(self):\n        self.source_type = \"E\"\n        self.target_type = \"CC\"\n        self.name = \"average_edge_to_cell 2D\"\n        self.meshDimension = 2\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgF2CC_2D(self):\n        self.source_type = \"F\"\n        self.target_type = \"CC\"\n        self.name = \"average_face_to_cell 2D\"\n        self.meshDimension = 2\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgCC2F_2D(self):\n        self.source_type = \"CC\"\n        self.target_type = \"F\"\n        self.name = \"average_cell_to_face 2D\"\n        self.meshDimension = 2\n        self.expectedOrders = 1\n        self.orderTest()\n\n    # 3D\n    def test_AvgN2CC_3D(self):\n        self.source_type = \"N\"\n        self.target_type = \"CC\"\n        self.name = \"average_node_to_cell 3D\"\n        self.meshDimension = 3\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgN2F_3D(self):\n        self.source_type = \"N\"\n        self.target_type = \"F\"\n        self.name = \"average_node_to_face 3D\"\n        self.meshDimension = 3\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgN2E_3D(self):\n        self.source_type = \"N\"\n        self.target_type = \"E\"\n        self.name = \"average_node_to_edge 3D\"\n        self.meshDimension = 3\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgCC2N_3D(self):\n        self.source_type = \"CC\"\n        self.target_type = \"N\"\n        self.name = \"average_cell_to_node 3D\"\n        self.meshDimension = 3\n        self.expectedOrders = 1\n        self.orderTest()\n\n    def test_AvgE2CC_3D(self):\n        self.source_type = \"E\"\n        self.target_type = \"CC\"\n        self.name = \"average_edge_to_cell 3D\"\n        self.meshDimension = 3\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgF2CC_3D(self):\n        self.source_type = \"F\"\n        self.target_type = \"CC\"\n        self.name = \"average_face_to_cell 3D\"\n        self.meshDimension = 3\n        self.expectedOrders = 2\n        self.orderTest()\n\n    def test_AvgCC2F_3D(self):\n        self.source_type = \"CC\"\n        self.target_type = \"F\"\n        self.name = \"average_cell_to_face 3D\"\n        self.meshDimension = 3\n        self.expectedOrders = 1\n        self.orderTest()\n\n\nclass TestVectorAveraging2D(discretize.tests.OrderTest):\n    name = \"Averaging 2D\"\n    meshSizes = [8, 16, 32, 64]\n    meshTypes = [\"uniform simplex mesh\"]\n    meshDimension = 2\n    expectedOrders = 1\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        funX = lambda x, y: -(y**2)\n        funY = lambda x, y: x**2\n        mesh = self.M\n        ana = np.c_[funX(*mesh.cell_centers.T), funY(*mesh.cell_centers.T)].reshape(\n            -1, order=\"F\"\n        )\n\n        if self.source_type == \"F\":\n            Fc = np.c_[funX(*mesh.faces.T), funY(*mesh.faces.T)]\n            grid = mesh.project_face_vector(Fc)\n            comp = mesh.average_face_to_cell_vector @ grid\n        elif self.source_type == \"E\":\n            Ec = np.c_[funX(*mesh.edges.T), funY(*mesh.edges.T)]\n            grid = mesh.project_edge_vector(Ec)\n            comp = mesh.average_edge_to_cell_vector @ grid\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_AvgE2CCV(self):\n        self.source_type = \"E\"\n        self.name = \"average_edge_to_cell_vector 2D\"\n        self.orderTest()\n\n    def test_AvgF2CCV(self):\n        self.source_type = \"F\"\n        self.name = \"average_face_to_cell_vector 2D\"\n        self.orderTest()\n\n\nclass TestVectorAveraging3D(discretize.tests.OrderTest):\n    name = \"Averaging 3D\"\n    meshSizes = [8, 16, 32]\n    meshTypes = [\"uniform simplex mesh\"]\n    meshDimension = 3\n    expectedOrders = 1\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        funX = lambda x, y, z: np.cos(2 * np.pi * y)\n        funY = lambda x, y, z: np.cos(2 * np.pi * z)\n        funZ = lambda x, y, z: np.cos(2 * np.pi * x)\n        mesh = self.M\n        ana = np.c_[\n            funX(*mesh.cell_centers.T),\n            funY(*mesh.cell_centers.T),\n            funZ(*mesh.cell_centers.T),\n        ].reshape(-1, order=\"F\")\n\n        if self.source_type == \"F\":\n            Fc = np.c_[funX(*mesh.faces.T), funY(*mesh.faces.T), funZ(*mesh.faces.T)]\n            grid = mesh.project_face_vector(Fc)\n            comp = mesh.average_face_to_cell_vector @ grid\n        elif self.source_type == \"E\":\n            Ec = np.c_[funX(*mesh.edges.T), funY(*mesh.edges.T), funZ(*mesh.edges.T)]\n            grid = mesh.project_edge_vector(Ec)\n            comp = mesh.average_edge_to_cell_vector @ grid\n\n        err = np.linalg.norm((comp - ana), np.inf)\n        return err\n\n    def test_AvgE2CCV(self):\n        self.source_type = \"E\"\n        self.name = \"average_edge_to_cell_vector 2D\"\n        self.expectedOrders = 1\n        self.orderTest()\n\n    def test_AvgF2CCV(self):\n        self.source_type = \"F\"\n        self.name = \"average_face_to_cell_vector 2D\"\n        self.expectedOrders = 1\n        self.orderTest()\n\n\ndef test_cell_to_face_extrap():\n    # 2D\n    points, simplices = example_simplex_mesh((10, 10))\n    mesh = discretize.SimplexMesh(points, simplices)\n\n    v = np.ones(len(mesh))\n\n    Fv = mesh.average_cell_to_face @ v\n\n    np.testing.assert_equal(1.0, Fv)\n\n    # 3D\n    points, simplices = example_simplex_mesh((4, 4, 4))\n    mesh = discretize.SimplexMesh(points, simplices)\n\n    v = np.ones(len(mesh))\n\n    Fv = mesh.average_cell_to_face @ v\n\n    np.testing.assert_equal(1.0, Fv)\n\n\ndef test_zeros_outside():\n    points, simplices = example_simplex_mesh((8, 8))\n    mesh = discretize.SimplexMesh(points, simplices)\n\n    outside_point = [-0.1, -0.1]\n    Q1 = mesh.get_interpolation_matrix(\n        outside_point, location_type=\"cell_centers\", zeros_outside=True\n    )\n\n    assert Q1.nnz == 0\n    Q2 = mesh.get_interpolation_matrix(\n        outside_point, location_type=\"nodes\", zeros_outside=True\n    )\n    np.testing.assert_equal(Q2.data, 0)\n"
  },
  {
    "path": "tests/simplex/test_operators.py",
    "content": "import numpy as np\nimport pytest\n\nimport discretize\nfrom discretize import SimplexMesh\nfrom discretize.utils import example_simplex_mesh\n\n\nclass TestOperators2D(discretize.tests.OrderTest):\n    meshSizes = [8, 16, 32, 64]\n    meshTypes = [\"uniform simplex mesh\"]\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        mesh = self.M\n        if self._test_type == \"Curl\":\n            C = mesh.edge_curl\n\n            ex = lambda x, y: np.cos(y)\n            ey = lambda x, y: np.cos(x)\n            sol = lambda x, y: -np.sin(x) + np.sin(y)\n\n            Ev = np.c_[ex(*mesh.edges.T), ey(*mesh.edges.T)]\n            Ep = mesh.project_edge_vector(Ev)\n            ana = sol(*mesh.cell_centers.T)\n            test = C @ Ep\n            err = np.linalg.norm(mesh.cell_volumes * (test - ana))\n        elif self._test_type == \"Div\":\n            D = mesh.face_divergence\n\n            fx = lambda x, y: np.sin(2 * np.pi * x)\n            fy = lambda x, y: np.sin(2 * np.pi * y)\n            sol = (\n                lambda x, y: 2 * np.pi * (np.cos(2 * np.pi * x) + np.cos(2 * np.pi * y))\n            )\n\n            f = mesh.project_face_vector(np.c_[fx(*mesh.faces.T), fy(*mesh.faces.T)])\n\n            ana = sol(*mesh.cell_centers.T)\n            test = D @ f\n            err = np.linalg.norm(mesh.cell_volumes * (test - ana))\n        elif self._test_type == \"Grad\":\n            G = mesh.nodal_gradient\n\n            phi = lambda x, y: np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y)\n\n            dphi_dx = (\n                lambda x, y: 2 * np.pi * np.cos(2 * np.pi * x) * np.sin(2 * np.pi * y)\n            )\n            dphi_dy = (\n                lambda x, y: 2 * np.pi * np.cos(2 * np.pi * y) * np.sin(2 * np.pi * x)\n            )\n\n            p = phi(*mesh.nodes.T)\n\n            ana = mesh.project_edge_vector(\n                np.c_[dphi_dx(*mesh.edges.T), dphi_dy(*mesh.edges.T)]\n            )\n            test = G @ p\n            err = np.linalg.norm(test - ana, np.inf)\n        return err\n\n    def test_curl_order(self):\n        self.name = \"SimplexMesh curl order test\"\n        self._test_type = \"Curl\"\n        self.orderTest()\n\n    def test_div_order(self):\n        self.name = \"SimplexMesh div order test\"\n        self._test_type = \"Div\"\n        self.orderTest()\n\n    def test_grad_order(self):\n        self.name = \"SimplexMesh grad order test\"\n        self._test_type = \"Grad\"\n        self.orderTest()\n\n    def test_cell_gradient_stencil(self):\n        n = 4\n        points, simplices = example_simplex_mesh((n, n))\n        mesh = discretize.SimplexMesh(points, simplices)\n\n        G_sten = mesh.stencil_cell_gradient\n        mod = np.zeros(mesh.n_cells)\n\n        test_cell = 21\n        mod[test_cell] = 1.0\n\n        diff = G_sten @ mod\n        np.testing.assert_equal(\n            np.where(diff != 0.0)[0], np.sort(mesh._simplex_faces[21])\n        )\n        np.testing.assert_equal(diff[diff != 0.0], [-1, 1, -1])\n\n\nclass TestOperators3D(discretize.tests.OrderTest):\n    meshSizes = [8, 16, 32]\n    meshTypes = [\"uniform simplex mesh\"]\n\n    def setupMesh(self, n):\n        points, simplices = example_simplex_mesh((n, n, n))\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n        self.M = discretize.SimplexMesh(points, simplices)\n        return 1.0 / n\n\n    def getError(self):\n        mesh = self.M\n        if self._test_type == \"Curl\":\n            C = mesh.edge_curl\n\n            ex = lambda x, y, z: np.cos(2 * np.pi * y)\n            ey = lambda x, y, z: np.cos(2 * np.pi * z)\n            ez = lambda x, y, z: np.cos(2 * np.pi * x)\n\n            fx = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * z)\n            fy = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * x)\n            fz = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * y)\n\n            Ev = np.c_[ex(*mesh.edges.T), ey(*mesh.edges.T), ez(*mesh.edges.T)]\n            Ep = mesh.project_edge_vector(Ev)\n\n            Fv = np.c_[fx(*mesh.faces.T), fy(*mesh.faces.T), fz(*mesh.faces.T)]\n            ana = mesh.project_face_vector(Fv)\n            test = C @ Ep\n\n            err = np.linalg.norm(test - ana) / mesh.n_faces\n        elif self._test_type == \"Div\":\n            D = mesh.face_divergence\n\n            fx = lambda x, y, z: np.sin(2 * np.pi * x)\n            fy = lambda x, y, z: np.sin(2 * np.pi * y)\n            fz = lambda x, y, z: np.sin(2 * np.pi * z)\n            sol = lambda x, y, z: (\n                2 * np.pi * np.cos(2 * np.pi * x)\n                + 2 * np.pi * np.cos(2 * np.pi * y)\n                + 2 * np.pi * np.cos(2 * np.pi * z)\n            )\n\n            f = mesh.project_face_vector(\n                np.c_[fx(*mesh.faces.T), fy(*mesh.faces.T), fz(*mesh.faces.T)]\n            )\n\n            ana = sol(*mesh.cell_centers.T)\n            test = D @ f\n\n            err = np.linalg.norm(mesh.cell_volumes * (test - ana))\n        elif self._test_type == \"Grad\":\n            G = mesh.nodal_gradient\n\n            phi = lambda x, y, z: (np.cos(x) + np.cos(y) + np.cos(z))\n            # i (sin(x)) + j (sin(y)) + k (sin(z))\n            ex = lambda x, y, z: -np.sin(x)\n            ey = lambda x, y, z: -np.sin(y)\n            ez = lambda x, y, z: -np.sin(z)\n\n            p = phi(*mesh.nodes.T)\n\n            ana = mesh.project_edge_vector(\n                np.c_[ex(*mesh.edges.T), ey(*mesh.edges.T), ez(*mesh.edges.T)]\n            )\n            test = G @ p\n            err = np.linalg.norm(test - ana, np.inf)\n        return err\n\n    def test_curl_order(self):\n        self.name = \"SimplexMesh curl order test\"\n        self._test_type = \"Curl\"\n        self.orderTest()\n\n    def test_div_order(self):\n        self.name = \"SimplexMesh div order test\"\n        self._test_type = \"Div\"\n        self.orderTest()\n\n    def test_grad_order(self):\n        self.name = \"SimplexMesh grad order test\"\n        self._test_type = \"Grad\"\n        self.orderTest()\n\n\n@pytest.mark.parametrize(\"i_type\", [\"E\", \"F\"])\ndef test_simplex_projection_caching(i_type):\n    n = 5\n    mesh = SimplexMesh(*example_simplex_mesh((n, n)))\n    P1 = mesh._SimplexMesh__get_inner_product_projection_matrices(\n        i_type, with_volume=False, return_pointers=False\n    )\n    P2 = mesh._SimplexMesh__get_inner_product_projection_matrices(\n        i_type, with_volume=True, return_pointers=False\n    )\n    assert P1 is not P2\n"
  },
  {
    "path": "tests/simplex/test_utils.py",
    "content": "import unittest\nimport pytest\nimport numpy as np\nimport discretize\nfrom discretize.utils import example_simplex_mesh\nimport os\nimport pickle\n\ntry:\n    import matplotlib.pyplot as plt\nexcept ImportError:\n    plt = None\n\ntry:\n    import vtk  # NOQA F401\nexcept ImportError:\n    vtk = None\n\nrng = np.random.default_rng(87916253)\n\n\nclass SimplexTests(unittest.TestCase):\n    def test_init_errors(self):\n        bad_nodes = np.array(\n            [\n                [0, 0, 0],\n                [1, 0, 0],\n                [0, 1, 0],\n                [2, 2, 0],\n            ]\n        )\n        simplices = np.array([[0, 1, 2, 3]])\n        with self.assertRaises(ValueError):\n            discretize.SimplexMesh(bad_nodes, simplices)\n\n        good_nodes = np.array(\n            [\n                [0, 0, 0],\n                [1, 0, 0],\n                [0, 1, 0],\n                [0, 0, 1],\n            ]\n        )\n        with self.assertRaises(ValueError):\n            # pass incompatible shaped nodes and simplices\n            discretize.SimplexMesh(good_nodes, simplices[:, :-1])\n\n        with self.assertRaises(ValueError):\n            # pass bad dimensionality\n            discretize.SimplexMesh(np.ones((10, 4)), simplices[:, :-1])\n\n    def test_find_containing(self):\n        n = 4\n        points, simplices = example_simplex_mesh((n, n))\n        mesh = discretize.SimplexMesh(points, simplices)\n\n        x = np.array([[0.1, 0.2], [0.3, 0.4]])\n\n        inds = mesh.point2index(x)\n        np.testing.assert_equal(inds, [16, 5])\n\n    def test_pickle2D(self):\n        n = 5\n        points, simplices = discretize.utils.example_simplex_mesh((n, n))\n        mesh0 = discretize.SimplexMesh(points, simplices)\n\n        byte_string = pickle.dumps(mesh0)\n        mesh1 = pickle.loads(byte_string)\n\n        np.testing.assert_equal(mesh0.nodes, mesh1.nodes)\n        np.testing.assert_equal(mesh0._simplices, mesh1._simplices)\n\n    def test_pickle3D(self):\n        n = 5\n        points, simplices = discretize.utils.example_simplex_mesh((n, n, n))\n        mesh0 = discretize.SimplexMesh(points, simplices)\n\n        byte_string = pickle.dumps(mesh0)\n        mesh1 = pickle.loads(byte_string)\n\n        np.testing.assert_equal(mesh0.nodes, mesh1.nodes)\n        np.testing.assert_equal(mesh0._simplices, mesh1._simplices)\n\n    @pytest.mark.skipif(plt is None, reason=\"Requires matplotlib\")\n    def test_image_plotting(self):\n        n = 5\n        points, simplices = discretize.utils.example_simplex_mesh((n, n))\n        mesh = discretize.SimplexMesh(points, simplices)\n\n        cc_dat = rng.random(mesh.n_cells)\n        n_dat = rng.random(mesh.n_nodes)\n        f_dat = rng.random(mesh.n_faces)\n        e_dat = rng.random(mesh.n_edges)\n        ccv_dat = rng.random((mesh.n_cells, 2))\n\n        mesh.plot_image(cc_dat)\n        mesh.plot_image(ccv_dat, v_type=\"CCv\", view=\"vec\")\n\n        mesh.plot_image(n_dat)\n\n        mesh.plot_image(f_dat, v_type=\"Fx\")\n        mesh.plot_image(f_dat, v_type=\"Fy\")\n        mesh.plot_image(f_dat, v_type=\"F\")\n        mesh.plot_image(f_dat, v_type=\"F\", view=\"vec\")\n\n        mesh.plot_image(e_dat, v_type=\"Ex\")\n        mesh.plot_image(e_dat, v_type=\"Ey\")\n        mesh.plot_image(e_dat, v_type=\"E\")\n        mesh.plot_image(e_dat, v_type=\"E\", view=\"vec\")\n\n        with self.assertRaises(NotImplementedError):\n            points, simplices = discretize.utils.example_simplex_mesh((n, n, n))\n            mesh = discretize.SimplexMesh(points, simplices)\n\n            cc_dat = rng.random(mesh.n_cells)\n            mesh.plot_image(cc_dat)\n\n        plt.close(\"all\")\n\n    @pytest.mark.skipif(plt is None, reason=\"Requires matplotlib\")\n    def test_plot_grid(self):\n        n = 5\n        points, simplices = discretize.utils.example_simplex_mesh((n, n))\n        mesh = discretize.SimplexMesh(points, simplices)\n        mesh.plot_grid(nodes=True, faces=True, edges=True, centers=True)\n\n        points, simplices = discretize.utils.example_simplex_mesh((n, n, n))\n        mesh = discretize.SimplexMesh(points, simplices)\n        mesh.plot_grid(nodes=True, faces=True, edges=True, centers=True)\n        plt.close(\"all\")\n\n    @pytest.mark.skipif(vtk is None, reason=\"Requires vtk\")\n    def test_2D_vtk(self):\n        n = 5\n        points, simplices = discretize.utils.example_simplex_mesh((n, n))\n        mesh = discretize.SimplexMesh(points, simplices)\n        cc_dat = rng.random(mesh.n_cells)\n\n        vtk_obj = mesh.to_vtk(models={\"info\": cc_dat})\n\n        mesh2, models = discretize.SimplexMesh.vtk_to_simplex_mesh(vtk_obj)\n\n        np.testing.assert_equal(mesh.nodes, mesh2.nodes)\n        np.testing.assert_equal(mesh._simplices, mesh2._simplices)\n        np.testing.assert_equal(cc_dat, models[\"info\"])\n\n        mesh.write_vtk(\"test.vtu\", models={\"info\": cc_dat})\n        mesh2, models = discretize.SimplexMesh.read_vtk(\"test.vtu\")\n\n        np.testing.assert_equal(mesh.nodes, mesh2.nodes)\n        np.testing.assert_equal(mesh._simplices, mesh2._simplices)\n        np.testing.assert_equal(cc_dat, models[\"info\"])\n\n    @pytest.mark.skipif(vtk is None, reason=\"Requires vtk\")\n    def test_3D_vtk(self):\n        n = 5\n        points, simplices = discretize.utils.example_simplex_mesh((n, n, n))\n        mesh = discretize.SimplexMesh(points, simplices)\n        cc_dat = rng.random(mesh.n_cells)\n\n        vtk_obj = mesh.to_vtk(models={\"info\": cc_dat})\n\n        mesh2, models = discretize.SimplexMesh.vtk_to_simplex_mesh(vtk_obj)\n\n        np.testing.assert_equal(mesh.nodes, mesh2.nodes)\n        np.testing.assert_equal(mesh._simplices, mesh2._simplices)\n        np.testing.assert_equal(cc_dat, models[\"info\"])\n\n        mesh.write_vtk(\"test.vtu\", models={\"info\": cc_dat})\n        mesh2, models = discretize.SimplexMesh.read_vtk(\"test.vtu\")\n\n        np.testing.assert_equal(mesh.nodes, mesh2.nodes)\n        np.testing.assert_equal(mesh._simplices, mesh2._simplices)\n        np.testing.assert_equal(cc_dat, models[\"info\"])\n\n    def tearDown(self):\n        try:\n            os.remove(\"test.vtu\")\n        except FileNotFoundError:\n            pass\n"
  },
  {
    "path": "tests/tree/__init__.py",
    "content": "if __name__ == \"__main__\":\n    import glob\n    import unittest\n\n    test_file_strings = glob.glob(\"test_*.py\")\n    module_strings = [strng[0 : len(strng) - 3] for strng in test_file_strings]\n    suites = [\n        unittest.defaultTestLoader.loadTestsFromName(strng) for strng in module_strings\n    ]\n    testSuite = unittest.TestSuite(suites)\n\n    unittest.TextTestRunner(verbosity=2).run(testSuite)\n"
  },
  {
    "path": "tests/tree/test_intersections.py",
    "content": "import discretize\nimport numpy as np\nimport pytest\nfrom discretize.tests import assert_cell_intersects_geometric\n\npytestmark = pytest.mark.parametrize(\"dim\", [2, 3])\n\n\ndef test_point_locator(dim):\n    point = [0.44] * dim\n    mesh = discretize.TreeMesh([32] * dim)\n\n    mesh.insert_cells(point, -1)\n\n    ind = mesh.get_containing_cells(point)\n    cell = mesh[ind]\n    assert_cell_intersects_geometric(cell, point)\n\n\ndef test_ball_locator(dim):\n    center = [0.44] * dim\n    radius = 0.12\n\n    mesh = discretize.TreeMesh([32] * dim)\n    mesh.refine_ball(center, radius, -1)\n\n    cells = mesh.get_cells_in_ball(center, radius)\n\n    r2 = radius * radius\n\n    def ball_intersects(cell):\n        a = cell.origin\n        b = a + cell.h\n        dr = np.maximum(a, np.minimum(center, b)) - center\n        r2_test = np.sum(dr * dr)\n        return r2_test < r2\n\n    # ensure it found all of the cells by using a brute force search\n    test = []\n    for cell in mesh:\n        if ball_intersects(cell):\n            test.append(cell.index)\n    np.testing.assert_array_equal(test, cells)\n\n\ndef test_line_locator(dim):\n    segment = np.array([[0.12, 0.33, 0.19], [0.32, 0.93, 0.68]])[:, :dim]\n\n    mesh = discretize.TreeMesh([32] * dim)\n    mesh.refine_line(segment, -1)\n\n    cell_inds = mesh.get_cells_on_line(segment)\n\n    # ensure it found all of the cells by using a brute force search\n    test = []\n    for cell in mesh:\n        try:\n            assert_cell_intersects_geometric(cell, segment, edges=[0, 1])\n            test.append(cell.index)\n        except AssertionError:\n            pass\n    np.testing.assert_array_equal(test, cell_inds)\n\n\ndef test_box_locator(dim):\n    xmin = [0.2] * dim\n    xmax = [0.4] * dim\n    points = np.stack([xmin, xmax])\n\n    mesh = discretize.TreeMesh([32] * dim)\n    mesh.refine_box(xmin, xmax, -1)\n\n    cell_inds = mesh.get_cells_in_aabb(xmin, xmax)\n\n    # ensure it found all of the cells by using a brute force search\n    test = []\n    for cell in mesh:\n        try:\n            assert_cell_intersects_geometric(cell, points)\n            test.append(cell.index)\n        except AssertionError:\n            pass\n    np.testing.assert_array_equal(test, cell_inds)\n\n\ndef test_plane_locator(dim):\n    if dim == 2:\n        p0 = [2, 2]\n        normal = [-1, 1]\n        p1 = [-2, -2]\n        points = np.stack([p0, p1])\n        edges = [0, 1]\n        faces = None\n    elif dim == 3:\n        p0 = [20, 20, 20]\n        normal = [-1, -1, 2]\n        # define 4 corner points (including p0) of a plane to create triangles\n        # to verify the refine functionallity\n        p1 = [20, -20, 0]\n        p2 = [-20, 20, 0]\n        p3 = [-20, -20, -20]\n        points = np.stack([p0, p1, p2, p3])\n        edges = [[0, 1], [0, 2]]\n        faces = [[0, 1, 2]]\n\n    mesh = discretize.TreeMesh([16] * dim)\n    mesh.refine_plane(p0, normal, -1)\n\n    cell_inds = mesh.get_cells_on_plane(p0, normal)\n\n    # ensure it found all of the cells by using a brute force search\n    test = []\n    for cell in mesh:\n        try:\n            assert_cell_intersects_geometric(cell, points, edges=edges, faces=faces)\n            test.append(cell.index)\n        except AssertionError:\n            pass\n    np.testing.assert_array_equal(test, cell_inds)\n\n\ndef test_triangle_locator(dim):\n    triangle = np.array([[0.14, 0.31, 0.23], [0.32, 0.96, 0.41], [0.23, 0.87, 0.72]])[\n        :, :dim\n    ]\n    edges = [[0, 1], [0, 2], [1, 2]]\n    faces = [0, 1, 2]\n\n    mesh = discretize.TreeMesh([32] * dim)\n    mesh.refine_triangle(triangle, -1)\n\n    cell_inds = mesh.get_cells_in_triangle(triangle)\n\n    # test it throws an error without giving enough points to triangle.\n    with pytest.raises(ValueError):\n        mesh.get_cells_in_triangle(triangle[:-1])\n\n    # ensure it found all of the cells by using a brute force search\n    test = []\n    for cell in mesh:\n        try:\n            assert_cell_intersects_geometric(cell, triangle, edges=edges, faces=faces)\n            test.append(cell.index)\n        except AssertionError:\n            pass\n    np.testing.assert_array_equal(test, cell_inds)\n\n\ndef test_vert_tri_prism_locator(dim):\n    xyz = np.array(\n        [\n            [0.41, 0.21, 0.11],\n            [0.21, 0.61, 0.22],\n            [0.71, 0.71, 0.31],\n        ]\n    )\n    h = 0.48\n\n    points = np.concatenate([xyz, xyz + [0, 0, h]])\n    # only need to define the unique edge tangents (minus axis-aligned ones)\n    edges = [\n        [0, 1],\n        [0, 2],\n        [1, 2],\n    ]\n    faces = [\n        [0, 1, 2],\n    ]\n\n    mesh = discretize.TreeMesh([16] * dim)\n    if dim == 2:\n        with pytest.raises(NotImplementedError):\n            mesh.refine_vertical_trianglular_prism(xyz, h, -1)\n    else:\n        mesh.refine_vertical_trianglular_prism(xyz, h, -1)\n\n        # test it throws an error on incorrect number of points for the triangle\n        with pytest.raises(ValueError):\n            mesh.get_cells_in_vertical_trianglular_prism(xyz[:-1], h)\n\n        cell_inds = mesh.get_cells_in_vertical_trianglular_prism(xyz, h)\n\n        # ensure it found all of the cells by using a brute force search\n        test = []\n        for cell in mesh:\n            try:\n                assert_cell_intersects_geometric(cell, points, edges=edges, faces=faces)\n                test.append(cell.index)\n            except AssertionError:\n                pass\n        np.testing.assert_array_equal(test, cell_inds)\n\n\ndef test_tetrahedron_locator(dim):\n    simplex = np.array(\n        [[0.32, 0.21, 0.15], [0.82, 0.19, 0.34], [0.14, 0.82, 0.29], [0.32, 0.27, 0.83]]\n    )[: dim + 1, :dim]\n    edges = [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]][: (dim - 1) * 3]\n    faces = [\n        [0, 1, 2],\n        [0, 1, 3],\n        [0, 2, 3],\n        [1, 2, 3],\n    ][: 3 * dim - 5]\n\n    mesh = discretize.TreeMesh([16] * dim)\n    mesh.refine_tetrahedron(simplex, -1)\n\n    cell_inds = mesh.get_cells_in_tetrahedron(simplex)\n\n    # test it throws an error without giving enough points to triangle.\n    with pytest.raises(ValueError):\n        mesh.get_cells_in_tetrahedron(simplex[:-1])\n\n    # ensure it found all of the cells by using a brute force search\n    test = []\n    for cell in mesh:\n        try:\n            assert_cell_intersects_geometric(cell, simplex, edges=edges, faces=faces)\n            test.append(cell.index)\n        except AssertionError:\n            pass\n    np.testing.assert_array_equal(test, cell_inds)\n"
  },
  {
    "path": "tests/tree/test_refine.py",
    "content": "import re\nimport discretize\nimport numpy as np\nimport numpy.testing as npt\nimport pytest\nfrom discretize.tests import assert_cell_intersects_geometric\n\n\ndef test_2d_line():\n    segments = np.array([[0.12, 0.33], [0.32, 0.93]])\n\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.refine_line(segments, -1)\n\n    def refine_line(cell):\n        return assert_cell_intersects_geometric(\n            cell, segments, edges=[0, 1], as_refine=True\n        )\n\n    mesh2 = discretize.TreeMesh([64, 64])\n    mesh2.refine(refine_line)\n\n    assert mesh2.equals(mesh1)\n\n\ndef test_3d_line():\n    segments = np.array([[0.12, 0.33, 0.19], [0.32, 0.93, 0.68]])\n\n    mesh1 = discretize.TreeMesh([64, 64, 64])\n    mesh1.refine_line(segments, -1)\n\n    def refine_line(cell):\n        return assert_cell_intersects_geometric(\n            cell, segments, edges=[0, 1], as_refine=True\n        )\n\n    mesh2 = discretize.TreeMesh([64, 64, 64])\n    mesh2.refine(refine_line)\n\n    assert mesh2.equals(mesh1)\n\n\ndef test_line_errors():\n    mesh = discretize.TreeMesh([64, 64])\n    rng = np.random.default_rng(512)\n    segments2D = rng.random((5, 2))\n    segments3D = rng.random((5, 3))\n\n    # incorrect dimension\n    with pytest.raises(ValueError):\n        mesh.refine_line(segments3D, mesh.max_level, finalize=False)\n\n    # incorrect number of levels\n    # 4 segments won't broadcast to 2 levels\n    with pytest.raises(ValueError):\n        mesh.refine_line(segments2D, [mesh.max_level, 3], finalize=False)\n\n\ndef test_triangle2d():\n    triangle = np.array([[0.14, 0.31], [0.32, 0.96], [0.23, 0.87]])\n    edges = [[0, 1], [0, 2], [1, 2]]\n\n    def refine_triangle(cell):\n        return assert_cell_intersects_geometric(\n            cell, triangle, edges=edges, as_refine=True\n        )\n\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.refine(refine_triangle)\n\n    mesh2 = discretize.TreeMesh([64, 64])\n    mesh2.refine_triangle(triangle, -1)\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_triangle3d():\n    triangle = np.array([[0.14, 0.31, 0.23], [0.32, 0.96, 0.41], [0.23, 0.87, 0.72]])\n    edges = [[0, 1], [0, 2], [1, 2]]\n    faces = [0, 1, 2]\n\n    def refine_triangle(cell):\n        return assert_cell_intersects_geometric(\n            cell, triangle, edges=edges, faces=faces, as_refine=True\n        )\n\n    mesh1 = discretize.TreeMesh([64, 64, 64])\n    mesh1.refine(refine_triangle)\n\n    mesh2 = discretize.TreeMesh([64, 64, 64])\n    mesh2.refine_triangle(triangle, -1)\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_triangle_errors():\n    not_triangles_array = np.empty((4, 2, 2))\n    triangle3 = np.empty((3, 3))\n    triangles2 = np.empty((10, 3, 2))\n    levels = np.full(8, -1)\n\n    mesh1 = discretize.TreeMesh([64, 64])\n\n    # not passing 3 points on the second to last dimension to make a triangle\n    with pytest.raises(ValueError):\n        mesh1.refine_triangle(not_triangles_array, -1, finalize=False)\n\n    # incorrect dimension\n    with pytest.raises(ValueError):\n        mesh1.refine_triangle(triangle3, -1, finalize=False)\n\n    # bad number of levels and triangles\n    with pytest.raises(ValueError):\n        mesh1.refine_triangle(triangles2, levels, finalize=False)\n\n\ndef test_tetra2d():\n    # It actually calls triangle refine... just double check that works\n    # define a slower function that is surely accurate\n    triangle = np.array([[0.14, 0.31], [0.32, 0.96], [0.23, 0.87]])\n    edges = [[0, 1], [0, 2], [1, 2]]\n\n    def refine_triangle(cell):\n        return assert_cell_intersects_geometric(\n            cell, triangle, edges=edges, as_refine=True\n        )\n\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.refine(refine_triangle)\n\n    mesh2 = discretize.TreeMesh([64, 64])\n    mesh2.refine_tetrahedron(triangle, -1)\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_tetra3d():\n    simplex = np.array(\n        [[0.32, 0.21, 0.15], [0.82, 0.19, 0.34], [0.14, 0.82, 0.29], [0.32, 0.27, 0.83]]\n    )\n    edges = [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]]\n    faces = [\n        [0, 1, 2],\n        [0, 1, 3],\n        [0, 2, 3],\n        [1, 2, 3],\n    ]\n\n    def refine_simplex(cell):\n        return assert_cell_intersects_geometric(\n            cell, simplex, edges=edges, faces=faces, as_refine=True\n        )\n\n    mesh1 = discretize.TreeMesh([32, 32, 32])\n    mesh1.refine(refine_simplex)\n\n    mesh2 = discretize.TreeMesh([32, 32, 32])\n    mesh2.refine_tetrahedron(simplex, -1)\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_tetra_errors():\n    not_simplex_array = np.empty((4, 3, 3))\n    simplex = np.empty((4, 2))\n    simplices = np.empty((10, 4, 3))\n    levels = np.full(8, -1)\n\n    mesh1 = discretize.TreeMesh([32, 32, 32])\n\n    # not passing 4 points on the second to last dimension to make a simplex\n    with pytest.raises(ValueError):\n        mesh1.refine_tetrahedron(not_simplex_array, -1, finalize=False)\n\n    # incorrect dimension\n    with pytest.raises(ValueError):\n        mesh1.refine_tetrahedron(simplex, -1, finalize=False)\n\n    # bad number of levels and triangles\n    with pytest.raises(ValueError):\n        mesh1.refine_tetrahedron(simplices, levels, finalize=False)\n\n\ndef test_box_errors():\n    mesh = discretize.TreeMesh([64, 64])\n    rng = np.random.default_rng(32)\n    x0s = rng.random((3, 2))\n    x0s2d = 0.5 * rng.random((2, 2))\n    x1s2d = 0.5 * rng.random((2, 2)) + 0.5\n\n    x0s3d = 0.5 * rng.random((2, 3))\n    x1s3d = 0.5 * rng.random((2, 3)) + 0.5\n\n    # incorrect dimension on x0\n    with pytest.raises(ValueError):\n        mesh.refine_box(x0s3d, x1s3d, [5, 5], finalize=False)\n\n    # incorrect dimension on x1\n    with pytest.raises(ValueError):\n        mesh.refine_box(x0s2d, x1s3d, [5, 5], finalize=False)\n\n    # incompatible shapes\n    with pytest.raises(ValueError):\n        mesh.refine_box(x0s, x1s2d, [5, 5], finalize=False)\n\n    # incorrect number of levels\n    with pytest.raises(ValueError):\n        mesh.refine_box(x0s2d, x1s2d, [-1, -1, -1], finalize=False)\n\n\ndef test_ball_errors():\n    mesh = discretize.TreeMesh([64, 64])\n    x0s2d = np.array([[0.1, 0.1], [0.5, 0.5]])\n    x0s3d = np.array([[0.1, 0.1, 0.1], [0.5, 0.5, 0.5]])\n\n    # incorrect dimension on x0\n    with pytest.raises(ValueError):\n        mesh.refine_ball(x0s3d, [1, 1], [5, 5], finalize=False)\n\n    # incorrect number of radii\n    with pytest.raises(ValueError):\n        mesh.refine_ball(x0s2d, [1, 1, 2], [5, 5], finalize=False)\n\n    # incorrect number of levels\n    with pytest.raises(ValueError):\n        mesh.refine_ball(x0s2d, [1, 1], [5, 5, 4], finalize=False)\n\n\ndef test_insert_errors():\n    mesh = discretize.TreeMesh([64, 64])\n    x0s2d = np.array([[0.1, 0.1], [0.5, 0.5]])\n    x0s3d = np.array([[0.1, 0.1, 0.1], [0.5, 0.5, 0.5]])\n\n    # incorrect dimension on points\n    with pytest.raises(ValueError):\n        mesh.insert_cells(x0s3d, [1, 1], finalize=False)\n\n    # incorrect number of levels\n    with pytest.raises(ValueError):\n        mesh.insert_cells(x0s2d, [1, 1, 3], finalize=False)\n\n\ndef test_refine_triang_prism():\n    xyz = np.array(\n        [\n            [0.41, 0.21, 0.11],\n            [0.21, 0.61, 0.22],\n            [0.71, 0.71, 0.31],\n        ]\n    )\n    h = 0.48\n\n    all_points = np.concatenate([xyz, xyz + [0, 0, h]])\n    # only need to define the unique edge tangents (minus axis-aligned ones)\n    edges = [\n        [0, 1],\n        [0, 2],\n        [1, 2],\n    ]\n\n    # and define unique face normals (absent any face parallel to an axis,\n    # or with normal defined by an axis and an edge above.)\n    faces = [\n        [0, 1, 2],\n    ]\n\n    def refine_vert(cell):\n        return assert_cell_intersects_geometric(\n            cell, all_points, edges=edges, faces=faces, as_refine=True\n        )\n\n    mesh1 = discretize.TreeMesh([32, 32, 32])\n    mesh1.refine(refine_vert)\n\n    mesh2 = discretize.TreeMesh([32, 32, 32])\n    mesh2.refine_vertical_trianglular_prism(xyz, h, -1)\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_refine_triang_prism_errors():\n    xyz = np.array(\n        [\n            [0.41, 0.21, 0.11],\n            [0.21, 0.61, 0.22],\n            [0.71, 0.71, 0.31],\n        ]\n    )\n    h = 0.48\n\n    mesh = discretize.TreeMesh([32, 32])\n    # Not implemented for 2D\n    with pytest.raises(NotImplementedError):\n        mesh.refine_vertical_trianglular_prism(xyz, h, -1)\n\n    mesh = discretize.TreeMesh([32, 32, 32])\n    # incorrect triangle dimensions\n    with pytest.raises(ValueError):\n        mesh.refine_vertical_trianglular_prism(xyz[:, :-1], h, -1)\n\n    # incorrect levels and triangles\n    ps = np.empty((10, 3, 3))\n    with pytest.raises(ValueError):\n        mesh.refine_vertical_trianglular_prism(ps, h, [-1, -2])\n\n    # incorrect heights and triangles\n    ps = np.empty((10, 3, 3))\n    with pytest.raises(ValueError):\n        mesh.refine_vertical_trianglular_prism(ps, [h, h], -1)\n\n    # negative heights\n    ps = np.empty((10, 3, 3))\n    with pytest.raises(ValueError):\n        mesh.refine_vertical_trianglular_prism(ps, -h, -1)\n\n\ndef test_bounding_box():\n    # No padding\n    rng = np.random.default_rng(51623978)\n    xyz = rng.random((20, 2)) * 0.25 + 3 / 8\n    mesh1 = discretize.TreeMesh([32, 32])\n    mesh1.refine_bounding_box(xyz, -1, None)\n\n    x0 = xyz.min(axis=0)\n    xF = xyz.max(axis=0)\n\n    mesh2 = discretize.TreeMesh([32, 32])\n    mesh2.refine_box(x0, xF, -1)\n\n    assert mesh1.equals(mesh2)\n\n    # Padding at all levels\n    n_cell_pad = 2\n    x0 = xyz.min(axis=0)\n    xF = xyz.max(axis=0)\n\n    mesh1 = discretize.TreeMesh([32, 32])\n    mesh1.refine_bounding_box(xyz, -1, n_cell_pad)\n\n    mesh2 = discretize.TreeMesh([32, 32])\n    for lv in range(mesh2.max_level, 1, -1):\n        padding = n_cell_pad * (2 ** (mesh2.max_level - lv) / 32)\n        x0 -= padding\n        xF += padding\n        mesh2.refine_box(x0, xF, lv, finalize=False)\n    mesh2.finalize()\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_bounding_box_errors():\n    mesh1 = discretize.TreeMesh([32, 32])\n\n    xyz = np.empty((20, 3))\n    # incorrect padding shape\n    with pytest.raises(ValueError):\n        mesh1.refine_bounding_box(xyz, -1, [[2, 3, 4]])\n\n    # bad level\n    with pytest.raises(IndexError):\n        mesh1.refine_bounding_box(xyz, 20)\n\n\ndef test_refine_points():\n    mesh1 = discretize.TreeMesh([32, 32])\n    point = [0.5, 0.5]\n    level = -1\n    n_cell_pad = 3\n    mesh1.refine_points(point, level, None)\n\n    mesh2 = discretize.TreeMesh([32, 32])\n    mesh2.insert_cells(point, -1)\n\n    assert mesh1.equals(mesh2)\n\n    mesh1 = discretize.TreeMesh([32, 32])\n    mesh1.refine_points(point, level, n_cell_pad)\n\n    mesh2 = discretize.TreeMesh([32, 32])\n    ball_rad = 0.0\n    for lv in range(mesh2.max_level, 1, -1):\n        ball_rad += 2 ** (mesh2.max_level - lv) / 32 * n_cell_pad\n        mesh2.refine_ball(point, ball_rad, lv, finalize=False)\n    mesh2.finalize()\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_refine_points_errors():\n    mesh1 = discretize.TreeMesh([32, 32])\n\n    point = [0.5, 0.5]\n\n    with pytest.raises(IndexError):\n        mesh1.refine_points(point, 20)\n\n\ndef test_refine_surface2D():\n    mesh1 = discretize.TreeMesh([32, 32])\n    points = [[0.3, 0.3], [0.7, 0.3]]\n    mesh1.refine_surface(points, -1, None, pad_up=True, pad_down=True)\n\n    mesh2 = discretize.TreeMesh([32, 32])\n    x0 = [0.3, 0.3]\n    xF = [0.7, 0.3]\n    mesh2.refine_box(x0, xF, -1)\n\n    assert mesh1.equals(mesh2)\n\n    mesh1 = discretize.TreeMesh([32, 32])\n    points = [[0.3, 0.3], [0.7, 0.3]]\n    n_cell_pad = 2\n    mesh1.refine_surface(points, -1, n_cell_pad, pad_up=True, pad_down=True)\n\n    mesh2 = discretize.TreeMesh([32, 32])\n    x0 = np.r_[0.3, 0.3]\n    xF = np.r_[0.7, 0.3]\n    for lv in range(mesh2.max_level, 1, -1):\n        pad = 2 ** (mesh2.max_level - lv) / 32 * n_cell_pad\n        x0 -= pad\n        xF += pad\n        mesh2.refine_box(x0, xF, lv, finalize=False)\n    mesh2.finalize()\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_refine_surface3D():\n    mesh1 = discretize.TreeMesh([32, 32, 32])\n    points = [\n        [0.3, 0.3, 0.5],\n        [0.7, 0.3, 0.5],\n        [0.3, 0.7, 0.5],\n        [0.7, 0.7, 0.5],\n    ]\n    mesh1.refine_surface(points, -1, [[1, 2, 3]], pad_up=True, pad_down=True)\n\n    mesh2 = discretize.TreeMesh([32, 32, 32])\n    pad = np.array([1, 2, 3]) / 32\n    x0 = [0.3, 0.3, 0.5] - pad\n    xF = [0.7, 0.7, 0.5] + pad\n    mesh2.refine_box(x0, xF, -1)\n\n    assert mesh1.equals(mesh2)\n\n    mesh3 = discretize.TreeMesh([32, 32, 32])\n    simps = [[0, 1, 2], [1, 2, 3]]\n    mesh3.refine_surface((points, simps), -1, [[1, 2, 3]], pad_up=True, pad_down=True)\n\n    assert mesh1.equals(mesh3)\n\n\ndef test_refine_surface_errors():\n    mesh = discretize.TreeMesh([32, 32])\n    points = [[0.3, 0.3], [0.7, 0.3]]\n\n    with pytest.raises(ValueError):\n        mesh.refine_surface(points, -1, [[0, 1, 2, 3]])\n\n    with pytest.raises(IndexError):\n        mesh.refine_surface(points, 20)\n\n\ndef test_refine_plane2D():\n    p0 = [2, 2]\n    normal = [-1, 1]\n    p1 = [-2, -2]\n\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.refine_plane(p0, normal, -1)\n\n    mesh2 = discretize.TreeMesh([64, 64])\n    mesh2.refine_line(np.stack([p0, p1]), -1)\n\n    assert mesh1.equals(mesh2)\n\n\ndef test_refine_plane3D():\n    p0 = [20, 20, 20]\n    normal = [-1, -1, 2]\n    # define 4 corner points (including p0) of a plane to create triangles\n    # to verify the refine functionallity\n    p1 = [20, -20, 0]\n    p2 = [-20, 20, 0]\n    p3 = [-20, -20, -20]\n    tris = np.stack([[p0, p1, p2], [p1, p2, p3]])\n\n    mesh1 = discretize.TreeMesh([64, 64, 64])\n    mesh1.refine_plane(p0, normal, -1)\n\n    mesh2 = discretize.TreeMesh([64, 64, 64])\n    mesh2.refine_triangle(tris, -1)\n\n    assert mesh1.equals(mesh2)\n\n\ndef _make_quadrant_model(mesh, order):\n    shape_cells = mesh.shape_cells\n    model = np.zeros(shape_cells, order=\"F\" if order == \"flat\" else order)\n    if mesh.dim == 2:\n        model[: shape_cells[0] // 2, : shape_cells[1] // 2] = 1.0\n        model[: shape_cells[0] // 4, : shape_cells[1] // 4] = 0.5\n    else:\n        model[: shape_cells[0] // 2, : shape_cells[1] // 2, : shape_cells[2] // 2] = 1.0\n        model[: shape_cells[0] // 4, : shape_cells[1] // 4, : shape_cells[2] // 4] = 0.5\n    if order == \"flat\":\n        model = model.reshape(-1, order=\"F\")\n    return model\n\n\n@pytest.mark.parametrize(\n    \"tens_inp\",\n    [\n        dict(h=[16, 16]),\n        dict(h=[16, 32]),\n        dict(h=[32, 16]),\n        dict(h=[16, 16, 16]),\n        dict(h=[16, 16, 8]),\n        dict(h=[16, 8, 16]),\n        dict(h=[8, 16, 16]),\n        dict(h=[8, 8, 16]),\n        dict(h=[8, 16, 8]),\n        dict(h=[16, 8, 8]),\n    ],\n    ids=[\n        \"16x16\",\n        \"16x32\",\n        \"32x16\",\n        \"16x16x16\",\n        \"16x16x8\",\n        \"16x8x16\",\n        \"8x16x16\",\n        \"8x8x16\",\n        \"8x16x8\",\n        \"16x8x8\",\n    ],\n)\ndef test_refine_image_input_ordering(tens_inp):\n    base_mesh = discretize.TensorMesh(**tens_inp)\n    model_0 = _make_quadrant_model(base_mesh, order=\"flat\")\n    model_1 = _make_quadrant_model(base_mesh, order=\"C\")\n    model_2 = _make_quadrant_model(base_mesh, order=\"F\")\n\n    tree0 = discretize.TreeMesh(base_mesh.h, base_mesh.origin)\n    tree0.refine_image(model_0)\n\n    tree1 = discretize.TreeMesh(base_mesh.h, base_mesh.origin)\n    tree1.refine_image(model_1)\n\n    tree2 = discretize.TreeMesh(base_mesh.h, base_mesh.origin)\n    tree2.refine_image(model_2)\n\n    assert tree0.n_cells == tree1.n_cells == tree2.n_cells\n\n    for cell0, cell1, cell2 in zip(tree0, tree1, tree2):\n        assert cell0.nodes == cell1.nodes == cell2.nodes\n\n\n@pytest.mark.parametrize(\n    \"tens_inp\",\n    [\n        dict(h=[16, 16]),\n        dict(h=[16, 32]),\n        dict(h=[32, 16]),\n        dict(h=[16, 16, 16]),\n        dict(h=[16, 16, 8]),\n        dict(h=[16, 8, 16]),\n        dict(h=[8, 16, 16]),\n        dict(h=[8, 8, 16]),\n        dict(h=[8, 16, 8]),\n        dict(h=[16, 8, 8]),\n    ],\n    ids=[\n        \"16x16\",\n        \"16x32\",\n        \"32x16\",\n        \"16x16x16\",\n        \"16x16x8\",\n        \"16x8x16\",\n        \"8x16x16\",\n        \"8x8x16\",\n        \"8x16x8\",\n        \"16x8x8\",\n    ],\n)\n@pytest.mark.parametrize(\n    \"model_func\",\n    [\n        lambda mesh: np.zeros(mesh.n_cells),\n        lambda mesh: np.arange(mesh.n_cells, dtype=float),\n        lambda mesh: _make_quadrant_model(mesh, order=\"flat\"),\n    ],\n    ids=[\"constant\", \"full\", \"quadrant\"],\n)\ndef test_refine_image(tens_inp, model_func):\n    base_mesh = discretize.TensorMesh(**tens_inp)\n    model = model_func(base_mesh)\n    mesh = discretize.TreeMesh(base_mesh.h, base_mesh.origin, diagonal_balance=False)\n    mesh.refine_image(model)\n\n    # for every cell in the tree mesh, all aligned cells in the tensor mesh\n    # should have a single unique value.\n    # quickest way is to generate a volume interp operator and look at indices in the\n    # csr matrix\n    interp_mat = discretize.utils.volume_average(base_mesh, mesh)\n\n    # ensure in canonical form:\n    interp_mat.sum_duplicates()\n    interp_mat.sort_indices()\n    assert interp_mat.has_canonical_format\n\n    model = model.reshape(-1, order=\"F\")\n    for row in interp_mat:\n        vals = model[row.indices]\n        npt.assert_equal(vals, vals[0])\n\n\ndef test_refine_image_bad_size():\n    mesh = discretize.TreeMesh([32, 32])\n    model = np.zeros(32 * 32 + 1)\n    base_cells = np.prod(mesh.shape_cells)\n    with pytest.raises(\n        ValueError,\n        match=re.escape(\n            f\"image array size: {len(model)} must match the total number of cells in the base tensor mesh: {base_cells}\"\n        ),\n    ):\n        mesh.refine_image(model)\n\n\ndef test_refine_image_bad_shape():\n    mesh = discretize.TreeMesh([32, 32])\n    model = np.zeros((16, 64))\n    with pytest.raises(\n        ValueError,\n        match=re.escape(\n            f\"image array shape: {model.shape} must match the base cell shapes: {mesh.shape_cells}\"\n        ),\n    ):\n        mesh.refine_image(model)\n"
  },
  {
    "path": "tests/tree/test_safeguards.py",
    "content": "\"\"\"\nTest safeguards against accessing certain properties on non-finalized TreeMesh.\n\"\"\"\n\nimport re\nimport pytest\n\nfrom discretize import TreeMesh\nfrom discretize.tree_mesh import TreeMeshNotFinalizedError\n\n\nPROPERTIES = [\n    \"average_cell_to_face\",\n    \"average_cell_to_face_x\",\n    \"average_cell_to_face_y\",\n    \"average_cell_to_face_z\",\n    \"average_cell_vector_to_face\",\n    \"boundary_edges\",\n    \"boundary_face_outward_normals\",\n    \"boundary_faces\",\n    \"boundary_nodes\",\n    \"cell_centers\",\n    \"cell_volumes\",\n    \"edge_lengths\",\n    \"edges_x\",\n    \"edges_y\",\n    \"edges_z\",\n    \"face_areas\",\n    \"faces_x\",\n    \"faces_y\",\n    \"faces_z\",\n    \"h_gridded\",\n    \"hanging_edges_x\",\n    \"hanging_edges_y\",\n    \"hanging_edges_z\",\n    \"hanging_faces_x\",\n    \"hanging_faces_y\",\n    \"hanging_faces_z\",\n    \"hanging_nodes\",\n    \"nodes\",\n    \"project_edge_to_boundary_edge\",\n    \"project_face_to_boundary_face\",\n    \"project_node_to_boundary_node\",\n    \"stencil_cell_gradient_x\",\n    \"stencil_cell_gradient_y\",\n    \"stencil_cell_gradient_z\",\n    \"total_nodes\",\n]\n\nINHERITED_PROPERTIES = [\n    \"boundary_edge_vector_integral\",\n    \"boundary_face_scalar_integral\",\n    \"boundary_node_vector_integral\",\n    \"edges\",\n    \"faces\",\n    \"permute_cells\",\n    \"permute_edges\",\n    \"permute_faces\",\n    \"stencil_cell_gradient\",\n]\n\n\n@pytest.fixture\ndef mesh():\n    \"\"\"Return a sample TreeMesh\"\"\"\n    nc = 16\n    h = [nc, nc, nc]\n    origin = (-32.4, 245.4, 192.3)\n    mesh = TreeMesh(h, origin, diagonal_balance=True)\n    return mesh\n\n\ndef refine_mesh(mesh):\n    \"\"\"\n    Refine the sample tree mesh.\n\n    Don't finalize the mesh.\n    \"\"\"\n    origin = mesh.origin\n    p1 = (origin[0] + 0.4, origin[1] + 0.4, origin[2] + 0.7)\n    p2 = (origin[0] + 0.6, origin[1] + 0.6, origin[2] + 0.9)\n    mesh.refine_box(p1, p2, levels=5, finalize=False)\n\n\nclass TestSafeGuards:\n\n    @pytest.mark.parametrize(\"prop_name\", PROPERTIES)\n    @pytest.mark.parametrize(\"refine\", [True, False], ids=[\"refined\", \"non-refined\"])\n    def test_errors(self, mesh, prop_name, refine):\n        \"\"\"\n        Test error after trying to access the property before finalizing the mesh.\n\n        Run tests by accessing the property before and after mesh refinement.\n        \"\"\"\n        if refine:\n            refine_mesh(mesh)\n        msg = re.escape(f\"`TreeMesh.{prop_name}` requires a finalized mesh.\")\n        with pytest.raises(TreeMeshNotFinalizedError, match=msg):\n            getattr(mesh, prop_name)\n\n    @pytest.mark.parametrize(\"prop_name\", PROPERTIES)\n    def test_no_errors(self, mesh, prop_name):\n        \"\"\"\n        Test if no error is raised when accessing the property on finalized mesh.\n        \"\"\"\n        # Refine and finalize mesh\n        refine_mesh(mesh)\n        mesh.finalize()\n        # Accessing the property should not error out\n        getattr(mesh, prop_name)\n\n    @pytest.mark.parametrize(\"prop_name\", INHERITED_PROPERTIES)\n    @pytest.mark.parametrize(\"refine\", [True, False], ids=[\"refined\", \"non-refined\"])\n    def test_errors_inherited_properties(self, mesh, prop_name, refine):\n        \"\"\"\n        Test errors when accessing inherited properties before finalizing the mesh.\n\n        These inherited properties are the ones that ``TreeMesh`` inherit, but\n        they depend on the ones defined by it that should not be accessed\n        before finalizing the mesh (e.g. ``edges`` and ``faces``).\n\n        These inherited properties raise errors after trying to access one of\n        the other properties. Their error message does not include the name of\n        the inherited property, but the first offending property that is being\n        accessed.\n        \"\"\"\n        if refine:\n            refine_mesh(mesh)\n        msg = r\"\\`TreeMesh\\.[a-z_]+\\`\" + re.escape(\" requires a finalized mesh.\")\n        with pytest.raises(TreeMeshNotFinalizedError, match=msg):\n            getattr(mesh, prop_name)\n"
  },
  {
    "path": "tests/tree/test_tree.py",
    "content": "import numpy as np\nimport unittest\nimport pytest\nimport discretize\n\nTOL = 1e-8\n\nrng = np.random.default_rng(6234)\n\n\nclass TestSimpleQuadTree(unittest.TestCase):\n    def test_counts(self):\n        nc = 8\n        h1 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h2 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h = [hi / np.sum(hi) for hi in [h1, h2]]  # normalize\n        M = discretize.TreeMesh(h)\n        points = np.array([[0.1, 0.1]])\n        level = np.array([3])\n\n        M.insert_cells(points, level)\n        M.number()\n\n        self.assertEqual(M.nhFx, 4)\n        self.assertEqual(M.nFx, 12)\n\n        self.assertTrue(np.allclose(M.cell_volumes.sum(), 1.0))\n        # self.assertTrue(np.allclose(np.r_[M._areaFxFull, M._areaFyFull], M._deflationMatrix('F') * M.face_areas)\n\n    def test_getitem(self):\n        M = discretize.TreeMesh([4, 4])\n        M.refine(1)\n        self.assertEqual(M.nC, 4)\n        self.assertEqual(len(M), M.nC)\n        self.assertTrue(np.allclose(M[0].center, [0.25, 0.25]))\n        # actual = [[0, 0], [0.5, 0], [0, 0.5], [0.5, 0.5]]\n        # for i, n in enumerate(M[0].nodes):\n        #    self.assertTrue(np.allclose(, actual[i])\n\n    def test_getitem3D(self):\n        M = discretize.TreeMesh([4, 4, 4])\n        M.refine(1)\n        self.assertEqual(M.nC, 8)\n        self.assertEqual(len(M), M.nC)\n        self.assertTrue(np.allclose(M[0].center, [0.25, 0.25, 0.25]))\n        # actual = [[0, 0, 0], [0.5, 0, 0], [0, 0.5, 0], [0.5, 0.5, 0],\n        #          [0, 0, 0.5], [0.5, 0, 0.5], [0, 0.5, 0.5], [0.5, 0.5, 0.5]]\n        # for i, n in enumerate(M[0].nodes):\n        #    self.assertTrue(np.allclose(M._gridN[n, :], actual[i])\n\n    def test_refine(self):\n        M = discretize.TreeMesh([4, 4, 4])\n        M.refine(1)\n        self.assertEqual(M.nC, 8)\n\n    def test_h_gridded_2D(self):\n        hx, hy = np.ones(4), np.r_[1.0, 2.0, 3.0, 4.0]\n\n        M = discretize.TreeMesh([hx, hy])\n\n        def refinefcn(cell):\n            xyz = cell.center\n            d = (xyz**2).sum() ** 0.5\n            if d < 3:\n                return 2\n            return 1\n\n        M.refine(refinefcn)\n        H = M.h_gridded\n\n        test_hx = np.all(H[:, 0] == np.r_[1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0])\n        test_hy = np.all(H[:, 1] == np.r_[1.0, 1.0, 2.0, 2.0, 3.0, 7.0, 7.0])\n\n        self.assertTrue(test_hx and test_hy)\n\n    #    def test_h_gridded_updates(self):\n    #        mesh = discretize.TreeMesh([8, 8])\n    #        mesh.refine(1)\n    #\n    #        H = mesh.h_gridded\n    #        self.assertTrue(np.all(H[:, 0] == 0.5*np.ones(4)))\n    #        self.assertTrue(np.all(H[:, 1] == 0.5*np.ones(4)))\n    #\n    #        # refine the mesh and make sure h_gridded is updated\n    #        mesh.refine(2)\n    #        H = mesh.h_gridded\n    #        self.assertTrue(np.all(H[:, 0] == 0.25*np.ones(16)))\n    #        self.assertTrue(np.all(H[:, 1] == 0.25*np.ones(16)))\n\n    def test_faceDiv(self):\n        hx, hy = np.r_[1.0, 2, 3, 4], np.r_[5.0, 6, 7, 8]\n        T = discretize.TreeMesh([hx, hy], levels=2)\n        T.refine(lambda xc: 2)\n        # T.plot_grid(show_it=True)\n        M = discretize.TensorMesh([hx, hy])\n        self.assertEqual(M.nC, T.nC)\n        self.assertEqual(M.nF, T.nF)\n        self.assertEqual(M.nFx, T.nFx)\n        self.assertEqual(M.nFy, T.nFy)\n        self.assertEqual(M.nE, T.nE)\n        self.assertEqual(M.nEx, T.nEx)\n        self.assertEqual(M.nEy, T.nEy)\n\n        self.assertTrue(np.allclose(M.face_areas, T.permute_faces * T.face_areas))\n        self.assertTrue(np.allclose(M.edge_lengths, T.permute_edges * T.edge_lengths))\n        self.assertTrue(np.allclose(M.cell_volumes, T.permute_cells * T.cell_volumes))\n\n        # plt.subplot(211).spy(M.face_divergence)\n        # plt.subplot(212).spy(T.permute_cells*T.face_divergence*T.permute_faces.T)\n        # plt.show()\n\n        self.assertEqual(\n            (\n                M.face_divergence\n                - T.permute_cells * T.face_divergence * T.permute_faces.T\n            ).nnz,\n            0,\n        )\n\n    def test_serialization(self):\n        hx, hy = np.r_[1.0, 2, 3, 4], np.r_[5.0, 6, 7, 8]\n        mesh1 = discretize.TreeMesh([hx, hy], levels=2, x0=np.r_[-1, -1])\n        mesh1.refine(2)\n        mesh2 = discretize.TreeMesh.deserialize(mesh1.serialize())\n        self.assertTrue(np.all(mesh1.x0 == mesh2.x0))\n        self.assertTrue(np.all(mesh1.shape_cells == mesh2.shape_cells))\n        self.assertTrue(np.all(mesh1.gridCC == mesh2.gridCC))\n\n        mesh1.x0 = np.r_[-2.0, 2]\n        mesh2 = discretize.TreeMesh.deserialize(mesh1.serialize())\n        self.assertTrue(np.all(mesh1.x0 == mesh2.x0))\n\n\nclass TestOcTree(unittest.TestCase):\n    def test_counts(self):\n        nc = 8\n        h1 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h2 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h3 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h = [hi / np.sum(hi) for hi in [h1, h2, h3]]  # normalize\n        M = discretize.TreeMesh(h, levels=3)\n        points = np.array([[0.2, 0.1, 0.7], [0.8, 0.4, 0.2]])\n        levels = np.array([1, 2])\n        M.insert_cells(points, levels)\n        M.number()\n        # M.plot_grid(show_it=True)\n        self.assertEqual(M.nhFx, 4)\n        self.assertTrue(M.nFx, 19)\n        self.assertTrue(M.nC, 15)\n\n        self.assertTrue(np.allclose(M.cell_volumes.sum(), 1.0))\n\n        # self.assertTrue(np.allclose(M._areaFxFull, (M._deflationMatrix('F') * M.face_areas)[:M.ntFx]))\n        # self.assertTrue(np.allclose(M._areaFyFull, (M._deflationMatrix('F') * M.face_areas)[M.ntFx:(M.ntFx+M.ntFy)])\n        # self.assertTrue(np.allclose(M._areaFzFull, (M._deflationMatrix('F') * M.face_areas)[(M.ntFx+M.ntFy):])\n\n        # self.assertTrue(np.allclose(M._edgeExFull, (M._deflationMatrix('E') * M.edge_lengths)[:M.ntEx])\n        # self.assertTrue(np.allclose(M._edgeEyFull, (M._deflationMatrix('E') * M.edge_lengths)[M.ntEx:(M.ntEx+M.ntEy)])\n        # self.assertTrue(np.allclose(M._edgeEzFull, (M._deflationMatrix('E') * M.edge_lengths)[(M.ntEx+M.ntEy):]))\n\n    def test_faceDiv(self):\n        hx, hy, hz = np.r_[1.0, 2, 3, 4], np.r_[5.0, 6, 7, 8], np.r_[9.0, 10, 11, 12]\n        M = discretize.TreeMesh([hx, hy, hz], levels=2)\n        M.refine(lambda xc: 2)\n        # M.plot_grid(show_it=True)\n        Mr = discretize.TensorMesh([hx, hy, hz])\n        self.assertEqual(M.nC, Mr.nC)\n        self.assertEqual(M.nF, Mr.nF)\n        self.assertEqual(M.nFx, Mr.nFx)\n        self.assertEqual(M.nFy, Mr.nFy)\n        self.assertEqual(M.nE, Mr.nE)\n        self.assertEqual(M.nEx, Mr.nEx)\n        self.assertEqual(M.nEy, Mr.nEy)\n\n        self.assertTrue(np.allclose(Mr.face_areas, M.permute_faces * M.face_areas))\n        self.assertTrue(np.allclose(Mr.edge_lengths, M.permute_edges * M.edge_lengths))\n        self.assertTrue(np.allclose(Mr.cell_volumes, M.permute_cells * M.cell_volumes))\n\n        A = Mr.face_divergence - M.permute_cells * M.face_divergence * M.permute_faces.T\n        self.assertTrue(np.allclose(A.data, 0))\n\n    def test_edge_curl(self):\n        hx, hy, hz = np.r_[1.0, 2, 3, 4], np.r_[5.0, 6, 7, 8], np.r_[9.0, 10, 11, 12]\n        M = discretize.TreeMesh([hx, hy, hz], levels=2)\n        M.refine(lambda xc: 2)\n\n        Mr = discretize.TensorMesh([hx, hy, hz])\n\n        A = Mr.edge_curl - M.permute_faces * M.edge_curl * M.permute_edges.T\n\n        self.assertTrue(len(A.data) == 0 or np.allclose(A.data, 0))\n\n    def test_faceInnerProduct(self):\n        hx, hy, hz = np.r_[1.0, 2, 3, 4], np.r_[5.0, 6, 7, 8], np.r_[9.0, 10, 11, 12]\n        # hx, hy, hz = [[(1, 4)], [(1, 4)], [(1, 4)]]\n\n        M = discretize.TreeMesh([hx, hy, hz], levels=2)\n        M.refine(lambda xc: 2)\n        # M.plot_grid(show_it=True)\n        Mr = discretize.TensorMesh([hx, hy, hz])\n\n        # print(M.nC, M.nF, M.get_face_inner_product().shape, M.permute_faces.shape)\n        A_face = (\n            Mr.get_face_inner_product()\n            - M.permute_faces * M.get_face_inner_product() * M.permute_faces.T\n        )\n        A_edge = (\n            Mr.get_edge_inner_product()\n            - M.permute_edges * M.get_edge_inner_product() * M.permute_edges.T\n        )\n\n        self.assertTrue(len(A_face.data) == 0 or np.allclose(A_face.data, 0))\n        self.assertTrue(len(A_edge.data) == 0 or np.allclose(A_edge.data, 0))\n\n    def test_VectorIdenties(self):\n        hx, hy, hz = [[(1, 4)], [(1, 4)], [(1, 4)]]\n\n        M = discretize.TreeMesh([hx, hy, hz], levels=2)\n        Mr = discretize.TensorMesh([hx, hy, hz])\n        M.refine(2)  # Why wasn't this here before?\n\n        self.assertTrue(np.allclose((M.face_divergence * M.edge_curl).data, 0))\n\n        hx, hy, hz = np.r_[1.0, 2, 3, 4], np.r_[5.0, 6, 7, 8], np.r_[9.0, 10, 11, 12]\n\n        M = discretize.TreeMesh([hx, hy, hz], levels=2)\n        Mr = discretize.TensorMesh([hx, hy, hz])\n        M.refine(2)\n        A1 = M.face_divergence * M.edge_curl\n        A2 = Mr.face_divergence * Mr.edge_curl\n\n        self.assertTrue(len(A1.data) == 0 or np.allclose(A1.data, 0))\n        self.assertTrue(len(A2.data) == 0 or np.allclose(A2.data, 0))\n\n    def test_h_gridded_3D(self):\n        hx, hy, hz = np.ones(4), np.r_[1.0, 2.0, 3.0, 4.0], 2 * np.ones(4)\n\n        M = discretize.TreeMesh([hx, hy, hz])\n\n        def refinefcn(cell):\n            xyz = cell.center\n            d = (xyz**2).sum() ** 0.5\n            if d < 3:\n                return 2\n            return 1\n\n        M.refine(refinefcn)\n        H = M.h_gridded\n\n        test_hx = np.all(\n            H[:, 0]\n            == np.r_[\n                1.0,\n                1.0,\n                1.0,\n                1.0,\n                1.0,\n                1.0,\n                1.0,\n                1.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n            ]\n        )\n        test_hy = np.all(\n            H[:, 1]\n            == np.r_[\n                1.0,\n                1.0,\n                2.0,\n                2.0,\n                1.0,\n                1.0,\n                2.0,\n                2.0,\n                3.0,\n                7.0,\n                7.0,\n                3.0,\n                3.0,\n                7.0,\n                7.0,\n            ]\n        )\n        test_hz = np.all(\n            H[:, 2]\n            == np.r_[\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                2.0,\n                4.0,\n                4.0,\n                4.0,\n                4.0,\n                4.0,\n                4.0,\n                4.0,\n            ]\n        )\n\n        self.assertTrue(test_hx and test_hy and test_hz)\n\n    def test_cell_nodes(self):\n        # 2D\n        nc = 8\n        h1 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h2 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h = [hi / np.sum(hi) for hi in [h1, h2]]  # normalize\n        M = discretize.TreeMesh(h)\n        points = np.array([[0.2, 0.1], [0.8, 0.4]])\n        levels = np.array([1, 2])\n        M.insert_cells(points, levels, finalize=True)\n\n        cell_nodes = M.cell_nodes\n\n        cell_2 = M[2]\n        np.testing.assert_equal(cell_2.nodes, cell_nodes[2])\n\n        # 3D\n        nc = 8\n        h1 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h2 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h3 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h = [hi / np.sum(hi) for hi in [h1, h2, h3]]  # normalize\n        M = discretize.TreeMesh(h, levels=3)\n        points = np.array([[0.2, 0.1, 0.7], [0.8, 0.4, 0.2]])\n        levels = np.array([1, 2])\n        M.insert_cells(points, levels, finalize=True)\n\n        cell_nodes = M.cell_nodes\n\n        cell_2 = M[2]\n        np.testing.assert_equal(cell_2.nodes, cell_nodes[2])\n\n\nclass TestTreeMeshNodes:\n    @pytest.fixture(params=[\"2D\", \"3D\"])\n    def sample_mesh(self, request):\n        \"\"\"Return a sample TreeMesh\"\"\"\n        nc = 8\n        h1 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        h2 = rng.random(nc) * nc * 0.5 + nc * 0.5\n        if request.param == \"2D\":\n            h = [hi / np.sum(hi) for hi in [h1, h2]]  # normalize\n            mesh = discretize.TreeMesh(h)\n            points = np.array([[0.2, 0.1], [0.8, 0.4]])\n            levels = np.array([1, 2])\n            mesh.insert_cells(points, levels, finalize=True)\n        else:\n            h3 = rng.random(nc) * nc * 0.5 + nc * 0.5\n            h = [hi / np.sum(hi) for hi in [h1, h2, h3]]  # normalize\n            mesh = discretize.TreeMesh(h, levels=3)\n            points = np.array([[0.2, 0.1, 0.7], [0.8, 0.4, 0.2]])\n            levels = np.array([1, 2])\n            mesh.insert_cells(points, levels, finalize=True)\n        return mesh\n\n    def test_total_nodes(self, sample_mesh):\n        \"\"\"\n        Test if ``TreeMesh.total_nodes`` works as expected\n        \"\"\"\n        n_non_hanging_nodes = sample_mesh.n_nodes\n        # Check if total_nodes contain all non hanging nodes (in the right order)\n        np.testing.assert_equal(\n            sample_mesh.total_nodes[:n_non_hanging_nodes, :], sample_mesh.nodes\n        )\n        # Check if total_nodes contain all hanging nodes (in the right order)\n        np.testing.assert_equal(\n            sample_mesh.total_nodes[n_non_hanging_nodes:, :], sample_mesh.hanging_nodes\n        )\n\n\nclass TestTreeCellBounds:\n    \"\"\"Test ``TreeCell.bounds`` method\"\"\"\n\n    @pytest.fixture(params=[\"2D\", \"3D\"])\n    def mesh(self, request):\n        \"\"\"Return a sample TreeMesh\"\"\"\n        nc = 16\n        if request.param == \"2D\":\n            h = [nc, nc]\n            origin = (-32.4, 245.4)\n            mesh = discretize.TreeMesh(h, origin)\n            p1 = (origin[0] + 0.4, origin[1] + 0.4)\n            p2 = (origin[0] + 0.6, origin[1] + 0.6)\n            mesh.refine_box(p1, p2, levels=5, finalize=True)\n        else:\n            h = [nc, nc, nc]\n            origin = (-32.4, 245.4, 192.3)\n            mesh = discretize.TreeMesh(h, origin)\n            p1 = (origin[0] + 0.4, origin[1] + 0.4, origin[2] + 0.7)\n            p2 = (origin[0] + 0.6, origin[1] + 0.6, origin[2] + 0.9)\n            mesh.refine_box(p1, p2, levels=5, finalize=True)\n        return mesh\n\n    def test_bounds(self, mesh):\n        \"\"\"Test bounds method of one of the cells in the mesh.\"\"\"\n        cell = mesh[16]\n        nodes = mesh.nodes[cell.nodes]\n        x1, x2 = nodes[0][0], nodes[-1][0]\n        y1, y2 = nodes[0][1], nodes[-1][1]\n        if mesh.dim == 2:\n            expected_bounds = np.array([x1, x2, y1, y2])\n        else:\n            z1, z2 = nodes[0][2], nodes[-1][2]\n            expected_bounds = np.array([x1, x2, y1, y2, z1, z2])\n        np.testing.assert_equal(cell.bounds, expected_bounds)\n\n    def test_bounds_relations(self, mesh):\n        \"\"\"Test if bounds are in the right order for one cell in the mesh.\"\"\"\n        cell = mesh[16]\n        if mesh.dim == 2:\n            x1, x2, y1, y2 = cell.bounds\n            assert x1 < x2\n            assert y1 < y2\n        else:\n            x1, x2, y1, y2, z1, z2 = cell.bounds\n            assert x1 < x2\n            assert y1 < y2\n            assert z1 < z2\n\n    def test_cell_bounds(self, mesh):\n        \"\"\"Test cell_bounds method of the tree mesh.\"\"\"\n        cell_bounds = mesh.cell_bounds\n        cell_bounds_slow = np.empty((mesh.n_cells, 2 * mesh.dim))\n        for i, cell in enumerate(mesh):\n            cell_bounds_slow[i] = cell.bounds\n        np.testing.assert_equal(cell_bounds, cell_bounds_slow)\n\n\nclass TestWrapAroundLevels(unittest.TestCase):\n    def test_refine_func(self):\n        mesh1 = discretize.TreeMesh((16, 16, 16))\n        mesh2 = discretize.TreeMesh((16, 16, 16))\n\n        mesh1.refine(-1)\n        mesh2.refine(mesh2.max_level)\n\n        self.assertEqual(mesh1.nC, mesh2.nC)\n\n    def test_refine_box(self):\n        mesh1 = discretize.TreeMesh((16, 16, 16))\n        mesh2 = discretize.TreeMesh((16, 16, 16))\n\n        x0s = [[4, 4, 4]]\n        x1s = [[8, 8, 8]]\n        mesh1.refine_box(x0s, x1s, [-1])\n        mesh2.refine_box(x0s, x1s, [mesh2.max_level])\n\n        self.assertEqual(mesh1.nC, mesh2.nC)\n\n    def test_refine_ball(self):\n        mesh1 = discretize.TreeMesh((16, 16, 16))\n        mesh2 = discretize.TreeMesh((16, 16, 16))\n\n        centers = [[8, 8, 8]]\n        r_s = [3]\n        mesh1.refine_ball(centers, r_s, [-1])\n        mesh2.refine_ball(centers, r_s, [mesh2.max_level])\n\n        self.assertEqual(mesh1.nC, mesh2.nC)\n\n    def test_insert_point(self):\n        mesh1 = discretize.TreeMesh((16, 16, 16))\n        mesh2 = discretize.TreeMesh((16, 16, 16))\n\n        mesh1.insert_cells([[8, 8, 8]], [-1])\n        mesh2.insert_cells([[8, 8, 8]], [mesh2.max_level])\n\n        self.assertEqual(mesh1.nC, mesh2.nC)\n\n\n@pytest.mark.parametrize(\"dim\", [2, 3])\ndef test_cell_locator(dim):\n    mesh = discretize.TreeMesh((16, 16, 16)[:dim])\n    mesh.insert_cells([0.5, 0.5, 0.5][:dim], levels=-1)\n\n    query_point = [0.21, 0.42, 0.22][:dim]\n    identified_cell = mesh.point2index(query_point)\n    found = False\n    for i, cell in enumerate(mesh):\n        bounds = cell.bounds.reshape((-1, 2))\n\n        if np.all((bounds[:, 0] <= query_point) & (bounds[:, 1] >= query_point)):\n            found = True\n            assert i == identified_cell\n    assert found\n\n\nclass TestRepr:\n    \"\"\"\n    Test repr methods on TreeMesh.\n\n    Check if no error is raised when calling repr methods on a finalized and\n    non finalized meshes.\n    \"\"\"\n\n    @pytest.fixture(params=[\"2D\", \"3D\"])\n    def mesh(self, request):\n        \"\"\"Return a sample TreeMesh\"\"\"\n        nc = 16\n        if request.param == \"2D\":\n            h = [nc, nc]\n            origin = (-32.4, 245.4)\n        else:\n            h = [nc, nc, nc]\n            origin = (-32.4, 245.4, 192.3)\n        mesh = discretize.TreeMesh(h, origin, diagonal_balance=True)\n        return mesh\n\n    def finalize(self, mesh):\n        \"\"\"\n        Finalize the sample tree mesh.\n        \"\"\"\n        origin = mesh.origin\n        if mesh.dim == 2:\n            p1 = (origin[0] + 0.4, origin[1] + 0.4)\n            p2 = (origin[0] + 0.6, origin[1] + 0.6)\n            mesh.refine_box(p1, p2, levels=5)\n        else:\n            p1 = (origin[0] + 0.4, origin[1] + 0.4, origin[2] + 0.7)\n            p2 = (origin[0] + 0.6, origin[1] + 0.6, origin[2] + 0.9)\n            mesh.refine_box(p1, p2, levels=5)\n        mesh.finalize()\n\n    @pytest.mark.parametrize(\"finalize\", [True, False])\n    def test_repr(self, mesh, finalize):\n        \"\"\"\n        Test if __repr__ doesn't raise errors on any TreeMesh.\n        \"\"\"\n        if finalize:\n            self.finalize(mesh)\n        output = mesh.__repr__()\n        assert type(output) is str\n        assert len(output) != 0\n\n    @pytest.mark.parametrize(\"finalize\", [True, False])\n    def test_repr_html(self, mesh, finalize):\n        \"\"\"\n        Test if _repr_html_ doesn't raise errors on any TreeMesh.\n        \"\"\"\n        if finalize:\n            self.finalize(mesh)\n        output = mesh._repr_html_()\n        assert type(output) is str\n        assert len(output) != 0\n\n\n@pytest.mark.parametrize(\"attr\", [\"average_edge_to_face\"])\ndef test_caching(attr):\n    mesh = discretize.TreeMesh([4, 4, 4])\n    mesh.refine(-1)\n\n    attr1 = getattr(mesh, attr)\n    attr2 = getattr(mesh, attr)\n\n    assert attr1 is attr2\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/tree/test_tree_balancing.py",
    "content": "import discretize\nimport numpy as np\n\n\ndef check_for_diag_unbalance(mesh):\n    avg = mesh.average_node_to_cell.T.tocsr()\n    bad_nodes = []\n    for i in range(mesh.n_nodes):\n        cells_around_node = avg[i, :].indices\n        levels = np.atleast_1d(mesh.cell_levels_by_index(cells_around_node))\n        level_diff = max(levels) - min(levels)\n        if level_diff >= 2:\n            bad_nodes.append(i)\n    bad_nodes = np.asarray(bad_nodes)\n    return bad_nodes\n\n\ndef test_insert_cells_2D():\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.insert_cells([0.09, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.09, 0.91], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.91], -1)\n\n    bad_nodes = check_for_diag_unbalance(mesh1)\n    assert len(bad_nodes) == 8\n\n    mesh2 = discretize.TreeMesh([64, 64], diagonal_balance=True)\n    mesh2.insert_cells([0.09, 0.09], -1, finalize=False)\n    mesh2.insert_cells([0.09, 0.91], -1, finalize=False)\n    mesh2.insert_cells([0.91, 0.09], -1, finalize=False)\n    mesh2.insert_cells([0.91, 0.91], -1)\n\n    bad_nodes = check_for_diag_unbalance(mesh2)\n    assert len(bad_nodes) == 0\n\n\ndef test_insert_cells_3D():\n    mesh1 = discretize.TreeMesh([64, 64, 64])\n    mesh1.insert_cells([0.09, 0.09, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.09, 0.91, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.09, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.91, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.09, 0.09, 0.91], -1, finalize=False)\n    mesh1.insert_cells([0.09, 0.91, 0.91], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.09, 0.91], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.91, 0.91], -1)\n\n    bad_nodes = check_for_diag_unbalance(mesh1)\n    assert len(bad_nodes) == 64\n\n    mesh2 = discretize.TreeMesh([64, 64, 64], diagonal_balance=True)\n    mesh2.insert_cells([0.09, 0.09, 0.09], -1, finalize=False)\n    mesh2.insert_cells([0.09, 0.91, 0.09], -1, finalize=False)\n    mesh2.insert_cells([0.91, 0.09, 0.09], -1, finalize=False)\n    mesh2.insert_cells([0.91, 0.91, 0.09], -1, finalize=False)\n    mesh2.insert_cells([0.09, 0.09, 0.91], -1, finalize=False)\n    mesh2.insert_cells([0.09, 0.91, 0.91], -1, finalize=False)\n    mesh2.insert_cells([0.91, 0.09, 0.91], -1, finalize=False)\n    mesh2.insert_cells([0.91, 0.91, 0.91], -1)\n\n    bad_nodes = check_for_diag_unbalance(mesh2)\n    assert len(bad_nodes) == 0\n\n\ndef test_refine():\n    mesh1 = discretize.TreeMesh([64, 64])\n\n    def refine(cell):\n        if np.sqrt(((np.r_[cell.center] - 0.5) ** 2).sum()) < 0.2:\n            return 5\n        return 2\n\n    mesh1.refine(refine)\n    bad_nodes = check_for_diag_unbalance(mesh1)\n\n    assert len(bad_nodes) == 4\n\n    mesh2 = discretize.TreeMesh([64, 64], diagonal_balance=True)\n    mesh2.refine(refine)\n    bad_nodes = check_for_diag_unbalance(mesh2)\n\n    assert len(bad_nodes) == 0\n\n\ndef test_refine_box():\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.refine_box([0.4, 0.4], [0.6, 0.6], -1)\n    bad_nodes = check_for_diag_unbalance(mesh1)\n\n    assert len(bad_nodes) == 8\n\n    mesh2 = discretize.TreeMesh([64, 64], diagonal_balance=True)\n    mesh2.refine_box([0.4, 0.4], [0.6, 0.6], -1)\n    bad_nodes = check_for_diag_unbalance(mesh2)\n\n    assert len(bad_nodes) == 0\n\n\ndef test_refine_ball():\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.refine_ball([0.5, 0.5], [0.1], -1)\n    bad_nodes = check_for_diag_unbalance(mesh1)\n\n    assert len(bad_nodes) == 12\n\n    mesh2 = discretize.TreeMesh([64, 64], diagonal_balance=True)\n    mesh2.refine_ball([0.5, 0.5], [0.1], -1)\n    bad_nodes = check_for_diag_unbalance(mesh2)\n\n    assert len(bad_nodes) == 0\n\n\ndef test_refine_line():\n    segments = np.array([[0.1, 0.3], [0.3, 0.9], [0.8, 0.9]])\n\n    mesh1 = discretize.TreeMesh([64, 64])\n    mesh1.refine_line(segments, -1)\n    bad_nodes = check_for_diag_unbalance(mesh1)\n\n    assert len(bad_nodes) == 7\n\n    mesh2 = discretize.TreeMesh([64, 64], diagonal_balance=True)\n    mesh2.refine_line(segments, -1)\n    bad_nodes = check_for_diag_unbalance(mesh2)\n\n    assert len(bad_nodes) == 0\n\n\ndef test_refine_triangle():\n    triangle = np.array([[0.14, 0.31], [0.32, 0.96], [0.23, 0.87]])\n    mesh1 = discretize.TreeMesh([64, 64], diagonal_balance=False)\n    mesh1.refine_triangle(triangle, -1)\n    bad_nodes = check_for_diag_unbalance(mesh1)\n\n    assert len(bad_nodes) == 5\n\n    mesh2 = discretize.TreeMesh([64, 64], diagonal_balance=True)\n    mesh2.refine_triangle(triangle, -1)\n    bad_nodes = check_for_diag_unbalance(mesh2)\n\n    assert len(bad_nodes) == 0\n\n\ndef test_refine_tetra():\n    simplex = np.array(\n        [[0.32, 0.21, 0.15], [0.82, 0.19, 0.34], [0.14, 0.82, 0.29], [0.32, 0.27, 0.83]]\n    )\n    mesh1 = discretize.TreeMesh([32, 32, 32], diagonal_balance=False)\n    mesh1.refine_tetrahedron(simplex, -1)\n    bad_nodes = check_for_diag_unbalance(mesh1)\n\n    assert len(bad_nodes) == 64\n\n    mesh2 = discretize.TreeMesh([32, 32, 32], diagonal_balance=True)\n    mesh2.refine_tetrahedron(simplex, -1)\n    bad_nodes = check_for_diag_unbalance(mesh2)\n\n    assert len(bad_nodes) == 0\n\n\ndef test_balance_out_unbalance_in():\n    mesh1 = discretize.TreeMesh([64, 64], diagonal_balance=True)\n    mesh1.insert_cells([0.09, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.09, 0.91], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.09], -1, finalize=False)\n    mesh1.insert_cells([0.91, 0.91], -1)\n\n    bad_nodes = check_for_diag_unbalance(mesh1)\n    assert len(bad_nodes) == 0\n\n    srl = mesh1.to_dict()\n    mesh2 = discretize.TreeMesh.deserialize(srl)\n\n    assert mesh1.equals(mesh2)\n"
  },
  {
    "path": "tests/tree/test_tree_innerproduct_derivs.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\n\nrng = np.random.default_rng(678423)\n\n\nclass TestInnerProductsDerivsTensor(unittest.TestCase):\n    def doTestFace(\n        self, h, rep, fast, meshType, invert_model=False, invert_matrix=False\n    ):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nF)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep)\n\n        def fun(sig):\n            M = mesh.get_face_inner_product(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_face_inner_product_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n                do_fast=fast,\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Face\",\n            h,\n            rep,\n            fast,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=4421\n        )\n\n    def doTestEdge(\n        self, h, rep, fast, meshType, invert_model=False, invert_matrix=False\n    ):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nE)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep)\n\n        def fun(sig):\n            M = mesh.get_edge_inner_product(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_edge_inner_product_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,\n                do_fast=fast,\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Edge\",\n            h,\n            rep,\n            fast,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=643\n        )\n\n    def test_FaceIP_2D_float_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 0, False, \"Tree\"))\n\n    def test_FaceIP_3D_float_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 0, False, \"Tree\"))\n\n    def test_FaceIP_2D_isotropic_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 1, False, \"Tree\"))\n\n    def test_FaceIP_3D_isotropic_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 1, False, \"Tree\"))\n\n    def test_FaceIP_2D_anisotropic_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 2, False, \"Tree\"))\n\n    def test_FaceIP_3D_anisotropic_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 3, False, \"Tree\"))\n\n    def test_FaceIP_2D_tensor_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 3, False, \"Tree\"))\n\n    def test_FaceIP_3D_tensor_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 6, False, \"Tree\"))\n\n    def test_FaceIP_2D_float_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 0, True, \"Tree\"))\n\n    def test_FaceIP_3D_float_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 0, True, \"Tree\"))\n\n    def test_FaceIP_2D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 1, True, \"Tree\"))\n\n    def test_FaceIP_3D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 1, True, \"Tree\"))\n\n    def test_FaceIP_2D_anisotropic_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 2, True, \"Tree\"))\n\n    def test_FaceIP_3D_anisotropic_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 3, True, \"Tree\"))\n\n    # def test_EdgeIP_2D_float_Tree(self):\n    #     self.assertTrue(self.doTestEdge([8, 8], 0, False, 'Tree'))\n    def test_EdgeIP_3D_float_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 0, False, \"Tree\"))\n\n    # def test_EdgeIP_2D_isotropic_Tree(self):\n    #     self.assertTrue(self.doTestEdge([8, 8], 1, False, 'Tree'))\n\n    def test_EdgeIP_3D_isotropic_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 1, False, \"Tree\"))\n\n    # def test_EdgeIP_2D_anisotropic_Tree(self):\n    #     self.assertTrue(self.doTestEdge([8, 8], 2, False, 'Tree'))\n\n    def test_EdgeIP_3D_anisotropic_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 3, False, \"Tree\"))\n\n    # def test_EdgeIP_2D_tensor_Tree(self):\n    #     self.assertTrue(self.doTestEdge([8, 8], 3, False, 'Tree'))\n\n    def test_EdgeIP_3D_tensor_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 6, False, \"Tree\"))\n\n    # def test_EdgeIP_2D_float_fast_Tree(self):\n    #     self.assertTrue(self.doTestEdge([8, 8], 0, True, 'Tree'))\n    def test_EdgeIP_3D_float_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 0, True, \"Tree\"))\n\n    # def test_EdgeIP_2D_isotropic_fast_Tree(self):\n    #     self.assertTrue(self.doTestEdge([8, 8], 1, True, 'Tree'))\n\n    def test_EdgeIP_3D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 1, True, \"Tree\"))\n\n    # def test_EdgeIP_2D_anisotropic_fast_Tree(self):\n    #     self.assertTrue(self.doTestEdge([8, 8], 2, True, 'Tree'))\n\n    def test_EdgeIP_3D_anisotropic_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 3, True, \"Tree\"))\n\n\nclass TestFacePropertiesInnerProductsDerivsTensor(unittest.TestCase):\n    def doTestFace(self, h, rep, meshType, invert_model=False, invert_matrix=False):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nF)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep)\n\n        def fun(sig):\n            M = mesh.get_face_inner_product_surface(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_face_inner_product_surface_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,  # , do_fast=fast\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Face\",\n            h,\n            rep,\n            # fast,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=677\n        )\n\n    def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nE)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep)\n\n        def fun(sig):\n            M = mesh.get_edge_inner_product_surface(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_edge_inner_product_surface_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,  # , do_fast=fast\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Edge\",\n            h,\n            rep,\n            # fast,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=543\n        )\n\n    def test_FaceIP_2D_float_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 0, \"Tree\"))\n\n    def test_FaceIP_3D_float_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 0, \"Tree\"))\n\n    def test_FaceIP_2D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8], 1, \"Tree\"))\n\n    def test_FaceIP_3D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestFace([8, 8, 8], 1, \"Tree\"))\n\n    def test_EdgeIP_2D_float_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8], 0, \"Tree\"))\n\n    def test_EdgeIP_3D_float_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 0, \"Tree\"))\n\n    def test_EdgeIP_2D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8], 1, \"Tree\"))\n\n    def test_EdgeIP_3D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 1, \"Tree\"))\n\n\nclass TestEdgePropertiesInnerProductsDerivsTensor(unittest.TestCase):\n    def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False):\n        if meshType == \"Curv\":\n            hRect = discretize.utils.example_curvilinear_grid(h, \"rotate\")\n            mesh = discretize.CurvilinearMesh(hRect)\n        elif meshType == \"Tree\":\n            mesh = discretize.TreeMesh(h, levels=3)\n            mesh.refine(lambda xc: 3)\n        elif meshType == \"Tensor\":\n            mesh = discretize.TensorMesh(h)\n        v = rng.random(mesh.nE)\n        sig = rng.random(1) if rep == 0 else rng.random(mesh.nE * rep)\n\n        def fun(sig):\n            M = mesh.get_edge_inner_product_line(\n                sig, invert_model=invert_model, invert_matrix=invert_matrix\n            )\n            Md = mesh.get_edge_inner_product_line_deriv(\n                sig,\n                invert_model=invert_model,\n                invert_matrix=invert_matrix,  # , do_fast=fast\n            )\n            return M * v, Md(v)\n\n        print(\n            meshType,\n            \"Edge\",\n            h,\n            rep,\n            # fast,\n            (\"harmonic\" if invert_model and invert_matrix else \"standard\"),\n        )\n        return discretize.tests.check_derivative(\n            fun, sig, num=5, plotIt=False, random_seed=23\n        )\n\n    def test_EdgeIP_2D_float_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8], 0, \"Tree\"))\n\n    def test_EdgeIP_3D_float_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 0, \"Tree\"))\n\n    def test_EdgeIP_2D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8], 1, \"Tree\"))\n\n    def test_EdgeIP_3D_isotropic_fast_Tree(self):\n        self.assertTrue(self.doTestEdge([8, 8, 8], 1, \"Tree\"))\n"
  },
  {
    "path": "tests/tree/test_tree_interpolation.py",
    "content": "import numpy as np\nimport discretize\n\nimport pytest\n\nMESHTYPES = [\"uniformTree\"]  # ['randomTree', 'uniformTree']\ncall2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1])\ncall3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\ncart_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)]\ncart_row3 = lambda g, xfun, yfun, zfun: np.c_[\n    call3(xfun, g), call3(yfun, g), call3(zfun, g)\n]\ncartF2 = lambda M, fx, fy: np.vstack(\n    (cart_row2(M.gridFx, fx, fy), cart_row2(M.gridFy, fx, fy))\n)\ncartE2 = lambda M, ex, ey: np.vstack(\n    (cart_row2(M.gridEx, ex, ey), cart_row2(M.gridEy, ex, ey))\n)\ncartF3 = lambda M, fx, fy, fz: np.vstack(\n    (\n        cart_row3(M.gridFx, fx, fy, fz),\n        cart_row3(M.gridFy, fx, fy, fz),\n        cart_row3(M.gridFz, fx, fy, fz),\n    )\n)\ncartE3 = lambda M, ex, ey, ez: np.vstack(\n    (\n        cart_row3(M.gridEx, ex, ey, ez),\n        cart_row3(M.gridEy, ex, ey, ez),\n        cart_row3(M.gridEz, ex, ey, ez),\n    )\n)\n\n\nplotIt = False\n\nMESHTYPES = [\"uniformTree\", \"notatreeTree\"]\n\n\n@pytest.mark.parametrize(\"tree_type\", [\"uniformTree\", \"notatreeTree\"])\n@pytest.mark.parametrize(\"dim\", [2, 3])\n@pytest.mark.parametrize(\"zeros_outside\", [True, False])\n@pytest.mark.parametrize(\n    \"mesh_locs\",\n    [\n        \"cell_centers\",\n        \"nodes\",\n        \"edges_x\",\n        \"edges_y\",\n        \"edges_z\",\n        \"faces_x\",\n        \"faces_y\",\n        \"faces_z\",\n    ],\n)\ndef test_order(tree_type, dim, mesh_locs, zeros_outside):\n    if dim == 2 and \"z\" in mesh_locs:\n        pytest.skip()\n\n    locs = (\n        np.mgrid[\n            *[\n                slice(0.25, 0.75, 50j),\n            ]\n            * dim\n        ]\n        .reshape(dim, -1)\n        .transpose()\n    )\n\n    if \"notatree\" in tree_type:\n        expected_order = 2\n    elif mesh_locs == \"nodes\":\n        expected_order = 2\n    else:\n        expected_order = 1\n\n    def ana_func(locs):\n        return locs**2 * [2, -3, 4][:dim] + locs * [-4, 3, 2][:dim] + [2, 3, 4][:dim]\n\n    ana_vals = ana_func(locs)\n\n    if \"faces\" in mesh_locs:\n        source_attr = \"faces\"\n    elif \"edges\" in mesh_locs:\n        source_attr = \"edges\"\n    else:\n        source_attr = mesh_locs\n\n    def order_func(n):\n        mesh, h = discretize.tests.setup_mesh(tree_type, n, dim)\n        interp_mat = mesh.get_interpolation_matrix(\n            locs, mesh_locs, zeros_outside=zeros_outside\n        )\n        grid_vals = ana_func(getattr(mesh, source_attr))\n        interp_vals = interp_mat @ grid_vals\n\n        return np.linalg.norm(interp_vals - ana_vals), h\n\n    discretize.tests.assert_expected_order(\n        order_func,\n        [8, 16, 32],\n        expected_order=expected_order,\n        test_type=\"mean_at_least\",\n    )\n\n\n@pytest.mark.parametrize(\"dim\", [2, 3])\n@pytest.mark.parametrize(\n    \"mesh_locs\",\n    [\n        \"cell_centers\",\n        \"nodes\",\n        \"edges_x\",\n        \"edges_y\",\n        \"edges_z\",\n        \"faces_x\",\n        \"faces_y\",\n        \"faces_z\",\n    ],\n)\ndef test_zeros_outside(dim, mesh_locs):\n    if dim == 2 and \"z\" in mesh_locs:\n        pytest.skip()\n\n    locs = (\n        np.mgrid[\n            *[\n                slice(-1, 2, 3j),\n            ]\n            * dim\n        ]\n        .reshape(dim, -1)\n        .transpose()\n    )\n    mesh = discretize.TreeMesh([16, 16, 16][:dim])\n    mesh.refine(-1)\n\n    is_outside = np.any((locs < 0) | (locs > 1), axis=1)\n    locs = locs[is_outside]\n\n    interp_mat = mesh.get_interpolation_matrix(locs, mesh_locs, zeros_outside=True)\n\n    if \"faces\" in mesh_locs:\n        n = mesh.n_faces\n    elif \"edges\" in mesh_locs:\n        n = mesh.n_edges\n    elif \"nodes\" in mesh_locs:\n        n = mesh.n_nodes\n    else:\n        n = mesh.n_cells\n\n    vs = interp_mat @ np.ones(n)\n\n    np.testing.assert_equal(vs, 0)\n\n\n@pytest.mark.parametrize(\"dim\", [2, 3])\n@pytest.mark.parametrize(\n    \"mesh_locs\",\n    [\n        \"cell_centers\",\n        \"nodes\",\n        \"edges_x\",\n        \"edges_y\",\n        \"edges_z\",\n        \"faces_x\",\n        \"faces_y\",\n        \"faces_z\",\n    ],\n)\ndef test_project_outside(dim, mesh_locs):\n    if dim == 2 and \"z\" in mesh_locs:\n        pytest.skip()\n\n    locs = (\n        np.mgrid[\n            *[\n                slice(-1, 2, 3j),\n            ]\n            * dim\n        ]\n        .reshape(dim, -1)\n        .transpose()\n    )\n    mesh = discretize.TreeMesh([16, 16, 16][:dim], diagonal_balance=True)\n    mesh.refine(-1)\n\n    is_outside = np.any((locs < 0) | (locs > 1), axis=1)\n    locs = locs[is_outside]\n\n    grid_locs = getattr(mesh, mesh_locs)\n    source_bounds = [\n        grid_locs.min(axis=0),\n        grid_locs.max(axis=0),\n    ]\n    interp_mat = mesh.get_interpolation_matrix(locs, mesh_locs, zeros_outside=False)\n\n    def ana_func(locs):\n        locs = np.clip(locs, a_min=source_bounds[0], a_max=source_bounds[1])\n        return locs * [-4, 3, 2][:dim] + [2, 3, 4][:dim]\n\n    ana_vals = ana_func(locs)\n\n    # get the full list of locations associate with mesh_locs\n    if \"faces\" in mesh_locs:\n        source_attr = \"faces\"\n    elif \"edges\" in mesh_locs:\n        source_attr = \"edges\"\n    else:\n        source_attr = mesh_locs\n\n    source_locs = getattr(mesh, source_attr)\n    grid_vals = ana_func(source_locs)\n\n    vs = interp_mat @ grid_vals\n\n    np.testing.assert_equal(vs, ana_vals)\n"
  },
  {
    "path": "tests/tree/test_tree_io.py",
    "content": "import warnings\nimport numpy as np\nimport discretize\nimport pickle\nimport json\nimport pytest\n\ntry:\n    import vtk  # NOQA F401\n\n    has_vtk = True\nexcept ImportError:\n    has_vtk = False\n\n\n@pytest.fixture(params=[2, 3])\ndef mesh(request):\n    dim = request.param\n    if dim == 2:\n        mesh = discretize.TreeMesh([8, 8])\n        mesh.refine(2, finalize=False)\n        mesh.refine_ball([0.25, 0.25], 0.25, 3)\n    else:\n        h = np.ones(16)\n        mesh = discretize.TreeMesh([h, 2 * h, 3 * h])\n        cell_points = np.array([[0.5, 0.5, 0.5], [0.5, 2.5, 0.5]])\n        cell_levels = np.array([4, 4])\n        mesh.insert_cells(cell_points, cell_levels)\n    return mesh\n\n\ndef test_UBCfiles(mesh, tmp_path):\n    # Make a vector\n    vec = np.arange(mesh.n_cells)\n    # Write and read\n    mesh_file = tmp_path / \"temp.msh\"\n    model_file = tmp_path / \"arange.txt\"\n\n    mesh.write_UBC(mesh_file, {model_file: vec})\n    meshUBC = discretize.TreeMesh.read_UBC(mesh_file)\n    vecUBC = meshUBC.read_model_UBC(model_file)\n\n    assert mesh is not meshUBC\n    assert mesh.equals(meshUBC)\n    np.testing.assert_array_equal(vec, vecUBC)\n\n    # Write it again with another IO function\n    mesh.write_model_UBC([model_file], [vec])\n    vecUBC2 = mesh.read_model_UBC(model_file)\n    np.testing.assert_array_equal(vec, vecUBC2)\n\n\ndef test_ubc_files_no_warning_diagonal_balance(mesh, tmp_path):\n    \"\"\"\n    Test that reading UBC files don't trigger the diagonal balance warning.\n    \"\"\"\n    # Save the sample mesh into a UBC file\n    fname = tmp_path / \"temp.msh\"\n    mesh.write_UBC(fname)\n    # Make sure that no warning is raised when reading the mesh\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"error\")\n        discretize.TreeMesh.read_UBC(fname)\n\n\nif has_vtk:\n\n    def test_write_VTU_files(mesh, tmp_path):\n        vec = np.arange(mesh.nC)\n        mesh_file = tmp_path / \"temp.vtu\"\n        mesh.write_vtk(mesh_file, {\"arange\": vec})\n\n\ndef test_pickle(mesh):\n    byte_string = pickle.dumps(mesh)\n    mesh_pickle = pickle.loads(byte_string)\n\n    assert mesh is not mesh_pickle\n    assert mesh.equals(mesh_pickle)\n\n\ndef test_dic_serialize(mesh):\n    mesh_dict = mesh.serialize()\n    mesh2 = discretize.TreeMesh.deserialize(mesh_dict)\n    assert mesh is not mesh2\n    assert mesh.equals(mesh2)\n\n\ndef test_json_serialize(mesh, tmp_path):\n    json_file = tmp_path / \"tree.json\"\n\n    mesh.save(json_file)\n    with open(json_file, \"r\") as outfile:\n        jsondict = json.load(outfile)\n    mesh2 = discretize.TreeMesh.deserialize(jsondict)\n    assert mesh is not mesh2\n    assert mesh.equals(mesh2)\n"
  },
  {
    "path": "tests/tree/test_tree_operators.py",
    "content": "import numpy as np\nimport discretize\n\nMESHTYPES = [\"uniformTree\", \"randomTree\"]\n# MESHTYPES = ['randomTree']\ncall2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1])\ncall3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\ncart_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)]\ncart_row3 = lambda g, xfun, yfun, zfun: np.c_[\n    call3(xfun, g), call3(yfun, g), call3(zfun, g)\n]\ncartF2 = lambda M, fx, fy: np.vstack(\n    (cart_row2(M.gridFx, fx, fy), cart_row2(M.gridFy, fx, fy))\n)\ncartE2 = lambda M, ex, ey: np.vstack(\n    (cart_row2(M.gridEx, ex, ey), cart_row2(M.gridEy, ex, ey))\n)\ncartF3 = lambda M, fx, fy, fz: np.vstack(\n    (\n        cart_row3(M.gridFx, fx, fy, fz),\n        cart_row3(M.gridFy, fx, fy, fz),\n        cart_row3(M.gridFz, fx, fy, fz),\n    )\n)\ncartE3 = lambda M, ex, ey, ez: np.vstack(\n    (\n        cart_row3(M.gridEx, ex, ey, ez),\n        cart_row3(M.gridEy, ex, ey, ez),\n        cart_row3(M.gridEz, ex, ey, ez),\n    )\n)\n\n\nclass TestCellGrad2D(discretize.tests.OrderTest):\n    name = \"Cell Gradient 2D, using cellGradx and cellGrady\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    meshSizes = [8, 16]\n    # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2\n    expectedOrders = 1\n\n    def getError(self):\n        # Test function\n        sol = lambda x, y: np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y)\n        fx = lambda x, y: -2 * np.pi * np.sin(2 * np.pi * x) * np.cos(2 * np.pi * y)\n        fy = lambda x, y: -2 * np.pi * np.sin(2 * np.pi * y) * np.cos(2 * np.pi * x)\n\n        phi = call2(sol, self.M.gridCC)\n        gradF = self.M.cell_gradient * phi\n        Fc = cartF2(self.M, fx, fy)\n        gradF_ana = self.M.project_face_vector(Fc)\n\n        err = np.linalg.norm((gradF - gradF_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest(random_seed=421)\n\n\nclass TestCellGrad3D(discretize.tests.OrderTest):\n    name = \"Cell Gradient 3D, using cellGradx, cellGrady, and cellGradz\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n    meshSizes = [8, 16]\n    # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2\n    expectedOrders = 1\n\n    def getError(self):\n        # Test function\n        sol = (\n            lambda x, y, z: np.cos(2 * np.pi * x)\n            * np.cos(2 * np.pi * y)\n            * np.cos(2 * np.pi * z)\n        )\n        fx = (\n            lambda x, y, z: -2\n            * np.pi\n            * np.sin(2 * np.pi * x)\n            * np.cos(2 * np.pi * y)\n            * np.cos(2 * np.pi * z)\n        )\n        fy = (\n            lambda x, y, z: -2\n            * np.pi\n            * np.cos(2 * np.pi * x)\n            * np.sin(2 * np.pi * y)\n            * np.cos(2 * np.pi * z)\n        )\n        fz = (\n            lambda x, y, z: -2\n            * np.pi\n            * np.cos(2 * np.pi * x)\n            * np.cos(2 * np.pi * y)\n            * np.sin(2 * np.pi * z)\n        )\n        phi = call3(sol, self.M.gridCC)\n        gradF = self.M.cell_gradient * phi\n        Fc = cartF3(self.M, fx, fy, fz)\n        gradF_ana = self.M.project_face_vector(Fc)\n\n        err = np.linalg.norm((gradF - gradF_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest(5532)\n\n\nclass TestFaceDivxy2D(discretize.tests.OrderTest):\n    name = \"Face Divergence 2D, Testing faceDivx and faceDivy\"\n    meshTypes = MESHTYPES\n    meshDimension = 2\n    meshSizes = [16, 32]\n\n    def getError(self):\n        # Test function\n        fx = lambda x, y: np.sin(2 * np.pi * x)\n        fy = lambda x, y: np.sin(2 * np.pi * y)\n        sol = lambda x, y: 2 * np.pi * (np.cos(2 * np.pi * x) + np.cos(2 * np.pi * y))\n\n        Fx = call2(fx, self.M.gridFx)\n        Fy = call2(fy, self.M.gridFy)\n        divFx = self.M.face_x_divergence.dot(Fx)\n        divFy = self.M.face_y_divergence.dot(Fy)\n        divF = divFx + divFy\n\n        divF_ana = call2(sol, self.M.gridCC)\n\n        err = np.linalg.norm((divF - divF_ana), np.inf)\n\n        # self.M.plot_image(divF-divF_ana, show_it=True)\n\n        return err\n\n    def test_order(self):\n        self.orderTest(random_seed=19647823)\n\n\nclass TestFaceDiv3D(discretize.tests.OrderTest):\n    name = \"Face Divergence 3D\"\n    meshTypes = MESHTYPES\n    meshSizes = [8, 16, 32]\n\n    def getError(self):\n        fx = lambda x, y, z: np.sin(2 * np.pi * x)\n        fy = lambda x, y, z: np.sin(2 * np.pi * y)\n        fz = lambda x, y, z: np.sin(2 * np.pi * z)\n        sol = lambda x, y, z: (\n            2 * np.pi * np.cos(2 * np.pi * x)\n            + 2 * np.pi * np.cos(2 * np.pi * y)\n            + 2 * np.pi * np.cos(2 * np.pi * z)\n        )\n\n        Fc = cartF3(self.M, fx, fy, fz)\n        F = self.M.project_face_vector(Fc)\n\n        divF = self.M.face_divergence.dot(F)\n        divF_ana = call3(sol, self.M.gridCC)\n\n        return np.linalg.norm((divF - divF_ana), np.inf)\n\n    def test_order(self):\n        self.orderTest(random_seed=81725364)\n\n\nclass TestFaceDivxyz3D(discretize.tests.OrderTest):\n    name = \"Face Divergence 3D, Testing faceDivx, faceDivy, and faceDivz\"\n    meshTypes = MESHTYPES\n    meshDimension = 3\n    meshSizes = [8, 16, 32]\n\n    def getError(self):\n        # Test function\n        fx = lambda x, y, z: np.sin(2 * np.pi * x)\n        fy = lambda x, y, z: np.sin(2 * np.pi * y)\n        fz = lambda x, y, z: np.sin(2 * np.pi * z)\n        sol = lambda x, y, z: (\n            2 * np.pi * np.cos(2 * np.pi * x)\n            + 2 * np.pi * np.cos(2 * np.pi * y)\n            + 2 * np.pi * np.cos(2 * np.pi * z)\n        )\n\n        Fx = call3(fx, self.M.gridFx)\n        Fy = call3(fy, self.M.gridFy)\n        Fz = call3(fz, self.M.gridFz)\n        divFx = self.M.face_x_divergence.dot(Fx)\n        divFy = self.M.face_y_divergence.dot(Fy)\n        divFz = self.M.face_z_divergence.dot(Fz)\n        divF = divFx + divFy + divFz\n\n        divF_ana = call3(sol, self.M.gridCC)\n\n        err = np.linalg.norm((divF - divF_ana), np.inf)\n\n        # self.M.plot_image(divF-divF_ana, show_it=True)\n\n        return err\n\n    def test_order(self):\n        self.orderTest(random_seed=6172824)\n\n\nclass TestCurl(discretize.tests.OrderTest):\n    name = \"Curl\"\n    meshTypes = [\"notatreeTree\", \"uniformTree\"]\n    meshSizes = [8, 16]  # , 32]\n    expectedOrders = [2, 1]  # This is due to linear interpolation in the Re projection\n\n    def getError(self):\n        # fun: i (cos(y)) + j (cos(z)) + k (cos(x))\n        # sol: i (sin(z)) + j (sin(x)) + k (sin(y))\n\n        funX = lambda x, y, z: np.cos(2 * np.pi * y)\n        funY = lambda x, y, z: np.cos(2 * np.pi * z)\n        funZ = lambda x, y, z: np.cos(2 * np.pi * x)\n\n        solX = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * z)\n        solY = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * x)\n        solZ = lambda x, y, z: 2 * np.pi * np.sin(2 * np.pi * y)\n\n        Ec = cartE3(self.M, funX, funY, funZ)\n        E = self.M.project_edge_vector(Ec)\n\n        Fc = cartF3(self.M, solX, solY, solZ)\n        curlE_ana = self.M.project_face_vector(Fc)\n\n        curlE = self.M.edge_curl.dot(E)\n\n        err = np.linalg.norm((curlE - curlE_ana), np.inf)\n        # err = np.linalg.norm((curlE - curlE_ana)*self.M.face_areas, 2)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestNodalGrad(discretize.tests.OrderTest):\n    name = \"Nodal Gradient\"\n    meshTypes = [\"notatreeTree\", \"uniformTree\"]\n    meshSizes = [8, 16]  # , 32]\n    expectedOrders = [2, 1]\n\n    def getError(self):\n        # Test function\n        fun = lambda x, y, z: (np.cos(x) + np.cos(y) + np.cos(z))\n        # i (sin(x)) + j (sin(y)) + k (sin(z))\n        solX = lambda x, y, z: -np.sin(x)\n        solY = lambda x, y, z: -np.sin(y)\n        solZ = lambda x, y, z: -np.sin(z)\n\n        phi = call3(fun, self.M.gridN)\n        gradE = self.M.nodal_gradient.dot(phi)\n\n        Ec = cartE3(self.M, solX, solY, solZ)\n        gradE_ana = self.M.project_edge_vector(Ec)\n\n        err = np.linalg.norm((gradE - gradE_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestNodalGrad2D(discretize.tests.OrderTest):\n    name = \"Nodal Gradient 2D\"\n    meshTypes = [\"notatreeTree\", \"uniformTree\"]\n    meshSizes = [8, 16]  # , 32]\n    expectedOrders = [2, 1]\n    meshDimension = 2\n\n    def getError(self):\n        # Test function\n        fun = lambda x, y: (np.cos(x) + np.cos(y))\n        # i (sin(x)) + j (sin(y)) + k (sin(z))\n        solX = lambda x, y: -np.sin(x)\n        solY = lambda x, y: -np.sin(y)\n\n        phi = call2(fun, self.M.gridN)\n        gradE = self.M.nodal_gradient.dot(phi)\n\n        Ec = cartE2(self.M, solX, solY)\n        gradE_ana = self.M.project_edge_vector(Ec)\n\n        err = np.linalg.norm((gradE - gradE_ana), np.inf)\n\n        return err\n\n    def test_order(self):\n        self.orderTest()\n\n\nclass TestTreeInnerProducts(discretize.tests.OrderTest):\n    \"\"\"Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\n        \"uniformTree\",\n        \"notatreeTree\",\n    ]  # ['uniformTensorMesh', 'uniformCurv', 'rotateCurv']\n    meshDimension = 3\n    meshSizes = [4, 8]\n\n    def getError(self):\n        call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\n\n        ex = lambda x, y, z: x**2 + y * z\n        ey = lambda x, y, z: (z**2) * x + y * z\n        ez = lambda x, y, z: y**2 + x * z\n\n        sigma1 = lambda x, y, z: x * y + 1\n        sigma2 = lambda x, y, z: x * z + 2\n        sigma3 = lambda x, y, z: 3 + z * y\n        sigma4 = lambda x, y, z: 0.1 * x * y * z\n        sigma5 = lambda x, y, z: 0.2 * x * y\n        sigma6 = lambda x, y, z: 0.1 * z\n\n        Gc = self.M.gridCC\n        if self.sigmaTest == 1:\n            sigma = np.c_[call(sigma1, Gc)]\n            analytic = 647.0 / 360  # Found using sympy.\n        elif self.sigmaTest == 3:\n            sigma = np.r_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc)]\n            analytic = 37.0 / 12  # Found using sympy.\n        elif self.sigmaTest == 6:\n            sigma = np.c_[\n                call(sigma1, Gc),\n                call(sigma2, Gc),\n                call(sigma3, Gc),\n                call(sigma4, Gc),\n                call(sigma5, Gc),\n                call(sigma6, Gc),\n            ]\n            analytic = 69881.0 / 21600  # Found using sympy.\n\n        if self.location == \"edges\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n            Ec = np.vstack(\n                (cart(self.M.gridEx), cart(self.M.gridEy), cart(self.M.gridEz))\n            )\n            E = self.M.project_edge_vector(Ec)\n\n            if self.invert_model:\n                A = self.M.get_edge_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_edge_inner_product(sigma)\n            numeric = E.T.dot(A.dot(E))\n        elif self.location == \"faces\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n            Fc = np.vstack(\n                (cart(self.M.gridFx), cart(self.M.gridFy), cart(self.M.gridFz))\n            )\n            F = self.M.project_face_vector(Fc)\n\n            if self.invert_model:\n                A = self.M.get_face_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_face_inner_product(sigma)\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_edges(self):\n        self.name = \"Edge Inner Product - Anisotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Anisotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order6_edges(self):\n        self.name = \"Edge Inner Product - Full Tensor\"\n        self.location = \"edges\"\n        self.sigmaTest = 6\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order6_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Full Tensor - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 6\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_faces(self):\n        self.name = \"Face Inner Product - Anisotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_faces_invert_model(self):\n        self.name = \"Face Inner Product - Anisotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order6_faces(self):\n        self.name = \"Face Inner Product - Full Tensor\"\n        self.location = \"faces\"\n        self.sigmaTest = 6\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order6_faces_invert_model(self):\n        self.name = \"Face Inner Product - Full Tensor - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 6\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsFaceProperties3D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a surface within a unit cube domain\n    using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTree\", \"notatreeTree\"]\n    meshDimension = 3\n    meshSizes = [8, 16]\n\n    def getError(self):\n        call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\n\n        ex = lambda x, y, z: x**2 + y * z\n        ey = lambda x, y, z: (z**2) * x + y * z\n        ez = lambda x, y, z: y**2 + x * z\n\n        tau_x = lambda x, y, z: y * z + 1  # x-face properties\n        tau_y = lambda x, y, z: x * z + 2  # y-face properties\n        tau_z = lambda x, y, z: 3 + x * y  # z-face properties\n        tau_funcs = [tau_x, tau_y, tau_z]\n\n        tau = 3 * [None]\n        for ii, comp in enumerate([\"x\", \"y\", \"z\"]):\n            faces = getattr(self.M, f\"faces_{comp}\")\n            k = np.isclose(faces[:, ii], 0.5)  # x, y or z location for each plane\n            tau_ii = 1e-8 * np.ones(len(faces))  # effectively zeros but stable\n            tau_ii[k] = tau_funcs[ii](*faces[k].T)\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        # integrate components parallel to the plane of integration\n        if self.location == \"edges\":\n            analytic = 5.02760416666667  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n\n            Ec = np.vstack(\n                (cart(self.M.gridEx), cart(self.M.gridEy), cart(self.M.gridEz))\n            )\n            E = self.M.project_edge_vector(Ec)\n\n            if self.invert_model:\n                A = self.M.get_edge_inner_product_surface(1 / tau, invert_model=True)\n            else:\n                A = self.M.get_edge_inner_product_surface(tau)\n\n            numeric = E.T.dot(A.dot(E))\n\n        # integrate component normal to the plane of integration\n        elif self.location == \"faces\":\n            analytic = 2.66979166666667  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n\n            Fc = np.vstack(\n                (cart(self.M.gridFx), cart(self.M.gridFy), cart(self.M.gridFz))\n            )\n            F = self.M.project_face_vector(Fc)\n\n            if self.invert_model:\n                A = self.M.get_face_inner_product_surface(1 / tau, invert_model=True)\n            else:\n                A = self.M.get_face_inner_product_surface(tau)\n\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsEdgeProperties3D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a line within a unit cube domain\n    using edgeInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTree\", \"notatreeTree\"]\n    meshDimension = 3\n    meshSizes = [16, 32]\n\n    def getError(self):\n        call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2])\n\n        ex = lambda x, y, z: x**2 + y * z\n        ey = lambda x, y, z: (z**2) * x + y * z\n        ez = lambda x, y, z: y**2 + x * z\n\n        tau_x = lambda x, y, z: x + 1  # x-face properties  # NOQA F841\n        tau_y = lambda x, y, z: y + 2  # y-face properties  # NOQA F841\n        tau_z = lambda x, y, z: 3 * z + 1  # z-face properties  # NOQA F841\n\n        tau = 3 * [None]\n        for ii, comp in enumerate([\"x\", \"y\", \"z\"]):\n            k = np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 1], 0.5\n            ) & np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 2], 0.5\n            )  # x, y or z location for each line\n            tau_ii = 1e-8 * eval(\n                \"np.ones(self.M.nE{})\".format(comp)\n            )  # effectively zeros but stable\n            tau_ii[k] = eval(\"call(tau_{}, self.M.edges_{}[k, :])\".format(comp, comp))\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        analytic = 1.98906250000000  # Found using sympy.\n\n        cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)]\n\n        Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy), cart(self.M.gridEz)))\n        E = self.M.project_edge_vector(Ec)\n\n        if self.invert_model:\n            A = self.M.get_edge_inner_product_line(1 / tau, invert_model=True)\n        else:\n            A = self.M.get_edge_inner_product_line(tau)\n\n        numeric = E.T.dot(A.dot(E))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestTreeInnerProducts2D(discretize.tests.OrderTest):\n    \"\"\"Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTree\"]\n    meshDimension = 2\n    meshSizes = [4, 8]\n\n    def getError(self):\n        z = 5  # Because 5 is just such a great number.\n\n        call = lambda fun, xy: fun(xy[:, 0], xy[:, 1])\n\n        ex = lambda x, y: x**2 + y * z\n        ey = lambda x, y: (z**2) * x + y * z\n\n        sigma1 = lambda x, y: x * y + 1\n        sigma2 = lambda x, y: x * z + 2\n        sigma3 = lambda x, y: 3 + z * y\n\n        Gc = self.M.gridCC\n        if self.sigmaTest == 1:\n            sigma = np.c_[call(sigma1, Gc)]\n            analytic = 144877.0 / 360  # Found using sympy. z=5\n        elif self.sigmaTest == 2:\n            sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc)]\n            analytic = 189959.0 / 120  # Found using sympy. z=5\n        elif self.sigmaTest == 3:\n            sigma = np.r_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc)]\n            analytic = 781427.0 / 360  # Found using sympy. z=5\n\n        if self.location == \"edges\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n            Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy)))\n            E = self.M.project_edge_vector(Ec)\n            if self.invert_model:\n                A = self.M.get_edge_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_edge_inner_product(sigma)\n            numeric = E.T.dot(A.dot(E))\n        elif self.location == \"faces\":\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n            Fc = np.vstack((cart(self.M.gridFx), cart(self.M.gridFy)))\n            F = self.M.project_face_vector(Fc)\n\n            if self.invert_model:\n                A = self.M.get_face_inner_product(\n                    discretize.utils.inverse_property_tensor(self.M, sigma),\n                    invert_model=True,\n                )\n            else:\n                A = self.M.get_face_inner_product(sigma)\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"2D Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"2D Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_edges(self):\n        self.name = \"2D Edge Inner Product - Anisotropic\"\n        self.location = \"edges\"\n        self.sigmaTest = 2\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_edges_invert_model(self):\n        self.name = \"2D Edge Inner Product - Anisotropic - invert_model\"\n        self.location = \"edges\"\n        self.sigmaTest = 2\n        self.invert_model = True\n        self.orderTest()\n\n    # def test_order6_edges(self):\n    #     self.name = \"2D Edge Inner Product - Full Tensor\"\n    #     self.location = 'edges'\n    #     self.sigmaTest = 3\n    #     self.invert_model = False\n    #     self.orderTest()\n\n    # def test_order6_edges_invert_model(self):\n    #     self.name = \"2D Edge Inner Product - Full Tensor - invert_model\"\n    #     self.location = 'edges'\n    #     self.sigmaTest = 3\n    #     self.invert_model = True\n    #     self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"2D Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"2D Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 1\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order2_faces(self):\n        self.name = \"2D Face Inner Product - Anisotropic\"\n        self.location = \"faces\"\n        self.sigmaTest = 2\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order2_faces_invert_model(self):\n        self.name = \"2D Face Inner Product - Anisotropic - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 2\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order3_faces(self):\n        self.name = \"2D Face Inner Product - Full Tensor\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order3_faces_invert_model(self):\n        self.name = \"2D Face Inner Product - Full Tensor - invert_model\"\n        self.location = \"faces\"\n        self.sigmaTest = 3\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsFaceProperties2D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a surface within a unit cube domain\n    using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTree\", \"notatreeTree\"]\n    meshDimension = 2\n    meshSizes = [16, 32]\n\n    def getError(self):\n        call = lambda fun, xy: fun(xy[:, 0], xy[:, 1])\n\n        ex = lambda x, y: x**2 + y\n        ey = lambda x, y: (y**2) * x\n\n        tau_x = lambda x, y: 2 * y + 1  # x-face properties  # NOQA F841\n        tau_y = lambda x, y: x + 2  # y-face properties  # NOQA F841\n\n        tau = 2 * [None]\n        for ii, comp in enumerate([\"x\", \"y\"]):\n            k = np.isclose(\n                eval(\"self.M.faces_{}\".format(comp))[:, ii], 0.5\n            )  # x, or y location for each plane\n            tau_ii = 1e-8 * eval(\n                \"np.ones(self.M.nF{})\".format(comp)\n            )  # effectively zeros but stable\n            tau_ii[k] = eval(\"call(tau_{}, self.M.faces_{}[k, :])\".format(comp, comp))\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        # integrate components parallel to the plane of integration\n        if self.location == \"edges\":\n            analytic = 2.24166666666667  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n\n            Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy)))\n            E = self.M.project_edge_vector(Ec)\n\n            if self.invert_model:\n                A = self.M.get_edge_inner_product_surface(1 / tau, invert_model=True)\n            else:\n                A = self.M.get_edge_inner_product_surface(tau)\n\n            numeric = E.T.dot(A.dot(E))\n\n        # integrate component normal to the plane of integration\n        elif self.location == \"faces\":\n            analytic = 1.59895833333333  # Found using sympy.\n\n            cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n\n            Fc = np.vstack((cart(self.M.gridFx), cart(self.M.gridFy)))\n            F = self.M.project_face_vector(Fc)\n\n            if self.invert_model:\n                A = self.M.get_face_inner_product_surface(1 / tau, invert_model=True)\n            else:\n                A = self.M.get_face_inner_product_surface(tau)\n\n            numeric = F.T.dot(A.dot(F))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.orderTest()\n\n    def test_order1_faces(self):\n        self.name = \"Face Inner Product - Isotropic\"\n        self.location = \"faces\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_faces_invert_model(self):\n        self.name = \"Face Inner Product - Isotropic - invert_model\"\n        self.location = \"faces\"\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestInnerProductsEdgeProperties2D(discretize.tests.OrderTest):\n    \"\"\"Integrate a function over a line within a unit cube domain\n    using edgeInnerProducts.\"\"\"\n\n    meshTypes = [\"uniformTree\", \"notatreeTree\"]\n    meshDimension = 2\n    meshSizes = [16, 32]\n\n    def getError(self):\n        call = lambda fun, xy: fun(xy[:, 0], xy[:, 1])\n\n        ex = lambda x, y: x**2 + y\n        ey = lambda x, y: (x**2) * y\n\n        tau_x = lambda x, y: x + 1  # x-face properties  # NOQA F841\n        tau_y = lambda x, y: y + 2  # y-face properties  # NOQA F841\n\n        tau = 2 * [None]\n        for ii, comp in enumerate([\"x\", \"y\"]):\n            k = np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 1], 0.5\n            ) & np.isclose(\n                eval(\"self.M.edges_{}\".format(comp))[:, ii - 2], 0.5\n            )  # x, y or z location for each line\n            tau_ii = 1e-8 * eval(\n                \"np.ones(self.M.nE{})\".format(comp)\n            )  # effectively zeros but stable\n            tau_ii[k] = eval(\"call(tau_{}, self.M.edges_{}[k, :])\".format(comp, comp))\n            tau[ii] = tau_ii\n        tau = np.hstack(tau)\n\n        analytic = 1.38229166666667  # Found using sympy.\n\n        cart = lambda g: np.c_[call(ex, g), call(ey, g)]\n\n        Ec = np.vstack((cart(self.M.gridEx), cart(self.M.gridEy)))\n        E = self.M.project_edge_vector(Ec)\n\n        if self.invert_model:\n            A = self.M.get_edge_inner_product_line(1 / tau, invert_model=True)\n        else:\n            A = self.M.get_edge_inner_product_line(tau)\n\n        numeric = E.T.dot(A.dot(E))\n\n        err = np.abs(numeric - analytic)\n\n        return err\n\n    def test_order1_edges(self):\n        self.name = \"Edge Inner Product - Isotropic\"\n        self.location = \"edges\"\n        self.invert_model = False\n        self.orderTest()\n\n    def test_order1_edges_invert_model(self):\n        self.name = \"Edge Inner Product - Isotropic - invert_model\"\n        self.location = \"edges\"\n        self.invert_model = True\n        self.orderTest()\n\n\nclass TestTreeAveraging2D(discretize.tests.OrderTest):\n    \"\"\"Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.\"\"\"\n\n    meshTypes = [\"notatreeTree\", \"uniformTree\"]\n    meshDimension = 2\n    meshSizes = [4, 8, 16]\n    expectedOrders = [2, 1]\n\n    def getError(self):\n        num = self.getAve(self.M) * self.getHere(self.M)\n        err = np.linalg.norm((self.getThere(self.M) - num), np.inf)\n        return err\n\n    def test_orderN2CC(self):\n        self.name = \"Averaging 2D: N2CC\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridN)\n        self.getThere = lambda M: call2(fun, M.gridCC)\n        self.getAve = lambda M: M.aveN2CC\n        self.orderTest()\n\n    def test_orderN2Fx(self):\n        self.name = \"Averaging 2D: N2Fx\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridN)\n        self.getThere = lambda M: np.r_[call2(fun, M.gridFx), call2(fun, M.gridFy)]\n        self.getAve = lambda M: M.aveN2F\n        self.orderTest()\n\n    def test_orderN2E(self):\n        self.name = \"Averaging 2D: N2E\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridN)\n        self.getThere = lambda M: np.r_[call2(fun, M.gridEx), call2(fun, M.gridEy)]\n        self.getAve = lambda M: M.aveN2E\n        self.orderTest()\n\n    def test_orderF2CC(self):\n        self.name = \"Averaging 2D: F2CC\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: np.r_[call2(fun, np.r_[M.gridFx, M.gridFy])]\n        self.getThere = lambda M: call2(fun, M.gridCC)\n        self.getAve = lambda M: M.aveF2CC\n        self.orderTest()\n\n    def test_orderFx2CC(self):\n        self.name = \"Averaging 2D: Fx2CC\"\n        funX = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: np.r_[call2(funX, M.gridFx)]\n        self.getThere = lambda M: np.r_[call2(funX, M.gridCC)]\n        self.getAve = lambda M: M.aveFx2CC\n        self.orderTest()\n\n    def test_orderFy2CC(self):\n        self.name = \"Averaging 2D: Fy2CC\"\n        funY = lambda x, y: (np.cos(y) * np.sin(x))\n        self.getHere = lambda M: np.r_[call2(funY, M.gridFy)]\n        self.getThere = lambda M: np.r_[call2(funY, M.gridCC)]\n        self.getAve = lambda M: M.aveFy2CC\n        self.orderTest()\n\n    def test_orderF2CCV(self):\n        self.name = \"Averaging 2D: F2CCV\"\n        funX = lambda x, y: (np.cos(x) + np.sin(y))\n        funY = lambda x, y: (np.cos(y) * np.sin(x))\n        self.getHere = lambda M: np.r_[call2(funX, M.gridFx), call2(funY, M.gridFy)]\n        self.getThere = lambda M: np.r_[call2(funX, M.gridCC), call2(funY, M.gridCC)]\n        self.getAve = lambda M: M.aveF2CCV\n        self.orderTest()\n\n    def test_orderCC2F(self):\n        self.name = \"Averaging 2D: CC2F\"\n        fun = lambda x, y: (np.cos(x) + np.sin(y))\n        self.getHere = lambda M: call2(fun, M.gridCC)\n        self.getThere = lambda M: np.r_[call2(fun, M.gridFx), call2(fun, M.gridFy)]\n        self.getAve = lambda M: M.aveCC2F\n        self.expectedOrders = 1\n        self.orderTest()\n        self.expectedOrders = 2\n\n\nclass TestAveraging3D(discretize.tests.OrderTest):\n    name = \"Averaging 3D\"\n    meshTypes = [\"notatreeTree\", \"uniformTree\"]\n    meshDimension = 3\n    meshSizes = [8, 16]\n    expectedOrders = [2, 1]\n\n    def getError(self):\n        num = self.getAve(self.M) * self.getHere(self.M)\n        err = np.linalg.norm((self.getThere(self.M) - num), np.inf)\n        return err\n\n    def test_orderN2CC(self):\n        self.name = \"Averaging 3D: N2CC\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridN)\n        self.getThere = lambda M: call3(fun, M.gridCC)\n        self.getAve = lambda M: M.aveN2CC\n        self.orderTest()\n\n    def test_orderN2F(self):\n        self.name = \"Averaging 3D: N2F\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridN)\n        self.getThere = lambda M: np.r_[\n            call3(fun, M.gridFx), call3(fun, M.gridFy), call3(fun, M.gridFz)\n        ]\n        self.getAve = lambda M: M.aveN2F\n        self.orderTest()\n\n    def test_orderN2E(self):\n        self.name = \"Averaging 3D: N2E\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridN)\n        self.getThere = lambda M: np.r_[\n            call3(fun, M.gridEx), call3(fun, M.gridEy), call3(fun, M.gridEz)\n        ]\n        self.getAve = lambda M: M.aveN2E\n        self.orderTest()\n\n    def test_orderF2CC(self):\n        self.name = \"Averaging 3D: F2CC\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(fun, M.gridFx), call3(fun, M.gridFy), call3(fun, M.gridFz)\n        ]\n        self.getThere = lambda M: call3(fun, M.gridCC)\n        self.getAve = lambda M: M.aveF2CC\n        self.orderTest()\n\n    def test_orderFx2CC(self):\n        self.name = \"Averaging 3D: Fx2CC\"\n        funX = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[call3(funX, M.gridFx)]\n        self.getThere = lambda M: np.r_[call3(funX, M.gridCC)]\n        self.getAve = lambda M: M.aveFx2CC\n        self.orderTest()\n\n    def test_orderFy2CC(self):\n        self.name = \"Averaging 3D: Fy2CC\"\n        funY = lambda x, y, z: (np.cos(x) + np.sin(y) * np.exp(z))\n        self.getHere = lambda M: np.r_[call3(funY, M.gridFy)]\n        self.getThere = lambda M: np.r_[call3(funY, M.gridCC)]\n        self.getAve = lambda M: M.aveFy2CC\n        self.orderTest()\n\n    def test_orderFz2CC(self):\n        self.name = \"Averaging 3D: Fz2CC\"\n        funZ = lambda x, y, z: (np.cos(x) + np.sin(y) * np.exp(z))\n        self.getHere = lambda M: np.r_[call3(funZ, M.gridFz)]\n        self.getThere = lambda M: np.r_[call3(funZ, M.gridCC)]\n        self.getAve = lambda M: M.aveFz2CC\n        self.orderTest()\n\n    def test_orderF2CCV(self):\n        self.name = \"Averaging 3D: F2CCV\"\n        funX = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        funY = lambda x, y, z: (np.cos(x) + np.sin(y) * np.exp(z))\n        funZ = lambda x, y, z: (np.cos(x) * np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(funX, M.gridFx), call3(funY, M.gridFy), call3(funZ, M.gridFz)\n        ]\n        self.getThere = lambda M: np.r_[\n            call3(funX, M.gridCC), call3(funY, M.gridCC), call3(funZ, M.gridCC)\n        ]\n        self.getAve = lambda M: M.aveF2CCV\n        self.orderTest()\n\n    def test_orderEx2CC(self):\n        self.name = \"Averaging 3D: Ex2CC\"\n        funX = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[call3(funX, M.gridEx)]\n        self.getThere = lambda M: np.r_[call3(funX, M.gridCC)]\n        self.getAve = lambda M: M.aveEx2CC\n        self.orderTest()\n\n    def test_orderEy2CC(self):\n        self.name = \"Averaging 3D: Ey2CC\"\n        funY = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[call3(funY, M.gridEy)]\n        self.getThere = lambda M: np.r_[call3(funY, M.gridCC)]\n        self.getAve = lambda M: M.aveEy2CC\n        self.orderTest()\n\n    def test_orderEz2CC(self):\n        self.name = \"Averaging 3D: Ez2CC\"\n        funZ = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[call3(funZ, M.gridEz)]\n        self.getThere = lambda M: np.r_[call3(funZ, M.gridCC)]\n        self.getAve = lambda M: M.aveEz2CC\n        self.orderTest()\n\n    def test_orderE2CC(self):\n        self.name = \"Averaging 3D: E2CC\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(fun, M.gridEx), call3(fun, M.gridEy), call3(fun, M.gridEz)\n        ]\n        self.getThere = lambda M: call3(fun, M.gridCC)\n        self.getAve = lambda M: M.aveE2CC\n        self.orderTest()\n\n    def test_orderE2CCV(self):\n        self.name = \"Averaging 3D: E2CCV\"\n        funX = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        funY = lambda x, y, z: (np.cos(x) + np.sin(y) * np.exp(z))\n        funZ = lambda x, y, z: (np.cos(x) * np.sin(y) + np.exp(z))\n        self.getHere = lambda M: np.r_[\n            call3(funX, M.gridEx), call3(funY, M.gridEy), call3(funZ, M.gridEz)\n        ]\n        self.getThere = lambda M: np.r_[\n            call3(funX, M.gridCC), call3(funY, M.gridCC), call3(funZ, M.gridCC)\n        ]\n        self.getAve = lambda M: M.aveE2CCV\n        self.orderTest()\n\n    def test_orderCC2F(self):\n        self.name = \"Averaging 3D: CC2F\"\n        fun = lambda x, y, z: (np.cos(x) + np.sin(y) + np.exp(z))\n        self.getHere = lambda M: call3(fun, M.gridCC)\n        self.getThere = lambda M: np.r_[\n            call3(fun, M.gridFx), call3(fun, M.gridFy), call3(fun, M.gridFz)\n        ]\n        self.getAve = lambda M: M.aveCC2F\n        self.expectedOrders = 1\n        self.orderTest()\n        self.expectedOrders = 2\n"
  },
  {
    "path": "tests/tree/test_tree_plotting.py",
    "content": "import pytest\n\ntry:\n    import matplotlib\n    import matplotlib.pyplot as plt\n\n    matplotlib.use(\"Agg\")\nexcept ImportError:\n    pytest.skip(\n        \"Skipping TreeMesh plotting tests due to no matplotlib\", allow_module_level=True\n    )\nimport numpy as np\nimport unittest\nfrom discretize import TreeMesh\n\nrng = np.random.default_rng(4213678)\n\n\nclass TestOcTreePlotting(unittest.TestCase):\n    def setUp(self):\n        mesh = TreeMesh([32, 32, 32])\n        mesh.refine_box([0.2, 0.2, 0.2], [0.5, 0.7, 0.8], 5)\n        self.mesh = mesh\n\n    def test_plot_slice(self):\n        mesh = self.mesh\n\n        plt.figure()\n        ax = plt.subplot(111)\n\n        mesh.plot_grid(faces=True, edges=True, nodes=True)\n\n        # CC plot\n        mod_cc = rng.random(len(mesh)) + 1j * rng.random(len(mesh))\n        mod_cc[rng.random(len(mesh)) < 0.2] = np.nan\n\n        mesh.plot_slice(mod_cc, normal=\"X\", grid=True)\n        mesh.plot_slice(mod_cc, normal=\"Y\", ax=ax)\n        mesh.plot_slice(mod_cc, normal=\"Z\", ax=ax)\n        mesh.plot_slice(mod_cc, view=\"imag\", ax=ax)\n        mesh.plot_slice(mod_cc, view=\"abs\", ax=ax)\n\n        mod_ccv = rng.random((len(mesh), 3))\n        mesh.plot_slice(mod_ccv, v_type=\"CCv\", view=\"vec\", ax=ax)\n\n        # F plot tests\n        mod_f = rng.random(mesh.n_faces)\n        mesh.plot_slice(mod_f, v_type=\"Fx\", ax=ax)\n        mesh.plot_slice(mod_f, v_type=\"Fy\", ax=ax)\n        mesh.plot_slice(mod_f, v_type=\"Fz\", ax=ax)\n        mesh.plot_slice(mod_f, v_type=\"F\", ax=ax)\n        mesh.plot_slice(mod_f, v_type=\"F\", view=\"vec\", ax=ax)\n\n        # E plot tests\n        mod_e = rng.random(mesh.n_edges)\n        mesh.plot_slice(mod_e, v_type=\"Ex\", ax=ax)\n        mesh.plot_slice(mod_e, v_type=\"Ey\", ax=ax)\n        mesh.plot_slice(mod_e, v_type=\"Ez\", ax=ax)\n        mesh.plot_slice(mod_e, v_type=\"E\", ax=ax)\n        mesh.plot_slice(mod_e, v_type=\"E\", view=\"vec\", ax=ax)\n\n        # Nodes\n        mod_n = rng.random(mesh.n_nodes)\n        mesh.plot_slice(mod_n, v_type=\"N\")\n        plt.close(\"all\")\n\n\nclass TestQuadTreePlotting(unittest.TestCase):\n    def setUp(self):\n        mesh = TreeMesh([32, 32])\n        mesh.refine_box([0.2, 0.2], [0.5, 0.8], 5)\n        self.mesh = mesh\n\n    def test_plot_slice(self):\n        mesh = self.mesh\n        plt.figure()\n        ax = plt.subplot(111)\n\n        mesh.plot_grid(faces=True, edges=True, nodes=True)\n\n        # CC plot\n        mod_cc = rng.random(len(mesh)) + 1j * rng.random(len(mesh))\n        mod_cc[rng.random(len(mesh)) < 0.2] = np.nan\n\n        mesh.plot_image(mod_cc)\n        mesh.plot_image(mod_cc, ax=ax)\n        mesh.plot_image(mod_cc, view=\"imag\", ax=ax)\n        mesh.plot_image(mod_cc, view=\"abs\", ax=ax)\n\n        mod_ccv = rng.random((len(mesh), 2))\n        mesh.plot_image(mod_ccv, v_type=\"CCv\", view=\"vec\", ax=ax)\n\n        # F plot tests\n        mod_f = rng.random(mesh.n_faces)\n        mesh.plot_image(mod_f, v_type=\"Fx\", ax=ax)\n        mesh.plot_image(mod_f, v_type=\"Fy\", ax=ax)\n        mesh.plot_image(mod_f, v_type=\"F\", ax=ax)\n        mesh.plot_image(mod_f, v_type=\"F\", view=\"vec\", ax=ax)\n\n        # E plot tests\n        mod_e = rng.random(mesh.n_edges)\n        mesh.plot_image(mod_e, v_type=\"Ex\", ax=ax)\n        mesh.plot_image(mod_e, v_type=\"Ey\", ax=ax)\n        mesh.plot_image(mod_e, v_type=\"E\", ax=ax)\n        mesh.plot_image(mod_e, v_type=\"E\", view=\"vec\", ax=ax)\n\n        # Nodes\n        mod_n = rng.random(mesh.n_nodes)\n        mesh.plot_image(mod_n, v_type=\"N\", ax=ax)\n        plt.close(\"all\")\n"
  },
  {
    "path": "tests/tree/test_tree_utils.py",
    "content": "import numpy as np\nimport unittest\nfrom discretize.utils import mesh_builder_xyz, refine_tree_xyz\n\nTOL = 1e-8\n\n\nclass TestRefineOcTree(unittest.TestCase):\n    def test_radial(self):\n        dx = 0.25\n        rad = 10\n        mesh = mesh_builder_xyz(\n            np.c_[0.01, 0.01, 0.01],\n            [dx, dx, dx],\n            depth_core=0,\n            padding_distance=[[0, 20], [0, 20], [0, 20]],\n            mesh_type=\"TREE\",\n        )\n\n        radCell = int(np.ceil(rad / dx))\n        mesh = refine_tree_xyz(\n            mesh,\n            np.c_[0, 0, 0],\n            octree_levels=[radCell],\n            method=\"radial\",\n            finalize=True,\n        )\n        cell_levels = mesh.cell_levels_by_index(np.arange(mesh.n_cells))\n\n        vol = 4.0 * np.pi / 3.0 * (rad + dx) ** 3.0\n\n        vol_mesh = mesh.cell_volumes[cell_levels == mesh.max_level].sum()\n\n        self.assertLess(np.abs(vol - vol_mesh) / vol, 0.05)\n\n        levels, cells_per_level = np.unique(cell_levels, return_counts=True)\n\n        self.assertEqual(mesh.n_cells, 311858)\n        np.testing.assert_array_equal(levels, [3, 4, 5, 6, 7])\n        np.testing.assert_array_equal(cells_per_level, [232, 1176, 2671, 9435, 298344])\n\n    def test_box(self):\n        dx = 0.25\n        dl = 10\n\n        # Create a box 2*dl in width\n        X, Y, Z = np.meshgrid(np.c_[-dl, dl], np.c_[-dl, dl], np.c_[-dl, dl])\n        xyz = np.c_[np.ravel(X), np.ravel(Y), np.ravel(Z)]\n        mesh = mesh_builder_xyz(\n            np.c_[0.01, 0.01, 0.01],\n            [dx, dx, dx],\n            depth_core=0,\n            padding_distance=[[0, 20], [0, 20], [0, 20]],\n            mesh_type=\"TREE\",\n        )\n\n        mesh = refine_tree_xyz(\n            mesh, xyz, octree_levels=[0, 1], method=\"box\", finalize=True\n        )\n        cell_levels = mesh.cell_levels_by_index(np.arange(mesh.n_cells))\n\n        vol = (2 * (dl + 2 * dx)) ** 3  # 2*dx is cell size at second to highest level\n        vol_mesh = np.sum(mesh.cell_volumes[cell_levels == mesh.max_level - 1])\n        self.assertLess((vol - vol_mesh) / vol, 0.05)\n\n        levels, cells_per_level = np.unique(cell_levels, return_counts=True)\n\n        self.assertEqual(mesh.n_cells, 80221)\n        np.testing.assert_array_equal(levels, [3, 4, 5, 6])\n        np.testing.assert_array_equal(cells_per_level, [80, 1762, 4291, 74088])\n\n    def test_surface(self):\n        dx = 0.1\n        dl = 20\n\n        # Define triangle\n        xyz = np.r_[\n            np.c_[-dl / 2, -dl / 2, 0],\n            np.c_[dl / 2, -dl / 2, 0],\n            np.c_[dl / 2, dl / 2, 0],\n        ]\n        mesh = mesh_builder_xyz(\n            np.c_[0.01, 0.01, 0.01],\n            [dx, dx, dx],\n            depth_core=0,\n            padding_distance=[[0, 20], [0, 20], [0, 20]],\n            mesh_type=\"TREE\",\n        )\n\n        mesh = refine_tree_xyz(\n            mesh, xyz, octree_levels=[1], method=\"surface\", finalize=True\n        )\n\n        # Volume of triangle\n        vol = dl * dl * dx / 2\n\n        residual = (\n            np.abs(\n                vol\n                - mesh.cell_volumes[\n                    mesh._cell_levels_by_indexes(range(mesh.nC)) == mesh.max_level\n                ].sum()\n                / 2\n            )\n            / vol\n            * 100\n        )\n\n        self.assertLess(residual, 5)\n\n    def test_errors(self):\n        dx = 0.25\n        rad = 10\n        self.assertRaises(\n            ValueError,\n            mesh_builder_xyz,\n            np.c_[0.01, 0.01, 0.01],\n            [dx, dx, dx],\n            depth_core=0,\n            padding_distance=[[0, 20], [0, 20], [0, 20]],\n            mesh_type=\"cyl\",\n        )\n\n        mesh = mesh_builder_xyz(\n            np.c_[0.01, 0.01, 0.01],\n            [dx, dx, dx],\n            depth_core=0,\n            padding_distance=[[0, 20], [0, 20], [0, 20]],\n            mesh_type=\"tree\",\n        )\n\n        radCell = int(np.ceil(rad / dx))\n        self.assertRaises(\n            NotImplementedError,\n            refine_tree_xyz,\n            mesh,\n            np.c_[0, 0, 0],\n            octree_levels=[radCell],\n            method=\"other\",\n            finalize=True,\n        )\n\n        self.assertRaises(\n            ValueError,\n            refine_tree_xyz,\n            mesh,\n            np.c_[0, 0, 0],\n            octree_levels=[radCell],\n            octree_levels_padding=[],\n            method=\"surface\",\n            finalize=True,\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/tree/test_tree_vtk.py",
    "content": "import numpy as np\nimport unittest\nimport discretize\n\ntry:\n    import vtk.util.numpy_support as nps\nexcept ImportError:\n    has_vtk = False\nelse:\n    has_vtk = True\n\n\nif has_vtk:\n\n    class TestTreeMeshVTK(unittest.TestCase):\n        def setUp(self):\n            h = np.ones(16)\n            mesh = discretize.TreeMesh([h, 2 * h, 3 * h])\n            cell_points = np.array([[0.5, 0.5, 0.5], [0.5, 2.5, 0.5]])\n            cell_levels = np.array([4, 4])\n            mesh.insert_cells(cell_points, cell_levels)\n            self.mesh = mesh\n\n        def test_VTK_object_conversion(self):\n            mesh = self.mesh\n            vec = np.arange(mesh.nC)\n            models = {\"arange\": vec}\n\n            vtkObj = mesh.to_vtk(models)\n\n            self.assertEqual(mesh.nC, vtkObj.GetNumberOfCells())\n            # TODO: this is actually different?: self.assertEqual(mesh.nN, vtkObj.GetNumberOfPoints())\n            # Remember that the tree vtk conversion adds two arrays\n            self.assertEqual(\n                len(models.keys()) + 2, vtkObj.GetCellData().GetNumberOfArrays()\n            )\n            bnds = vtkObj.GetBounds()\n            self.assertEqual(mesh.x0[0], bnds[0])\n            self.assertEqual(mesh.x0[1], bnds[2])\n            self.assertEqual(mesh.x0[2], bnds[4])\n\n            names = list(models.keys())\n            for name in names:\n                arr = nps.vtk_to_numpy(vtkObj.GetCellData().GetArray(name))\n                arr = arr.flatten(order=\"F\")\n                self.assertTrue(np.allclose(models[name], arr))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tutorials/inner_products/1_basic.py",
    "content": "r\"\"\"\nBasic Inner Products\n====================\n\nInner products between two scalar or vector quantities represents the most\nbasic class of inner products. For this class of inner products, we demonstrate:\n\n    - How to construct the inner product matrix\n    - How to use inner product matricies to approximate the inner product\n    - How to construct the inverse of the inner product matrix.\n\nFor scalar quantities :math:`\\psi` and :math:`\\phi`, the\ninner product is given by:\n\n.. math::\n    (\\psi , \\phi ) = \\int_\\Omega \\psi \\, \\phi \\, dv\n\n\nAnd for vector quantities :math:`\\vec{u}` and :math:`\\vec{v}`, the\ninner product is given by:\n\n.. math::\n    (\\vec{u}, \\vec{v}) = \\int_\\Omega \\vec{u} \\cdot \\vec{v} \\, dv\n\n\nIn discretized form, we can approximate the aforementioned inner-products as:\n\n.. math::\n    (\\psi , \\phi) \\approx \\mathbf{\\psi^T \\, M \\, \\phi}\n\n\nand\n\n.. math::\n    (\\vec{u}, \\vec{v}) \\approx \\mathbf{u^T \\, M \\, v}\n\n\nwhere :math:`\\mathbf{M}` in either equation represents an\n*inner-product matrix*. :math:`\\mathbf{\\psi}`, :math:`\\mathbf{\\phi}`,\n:math:`\\mathbf{u}` and :math:`\\mathbf{v}` are discrete variables that live\non the mesh. It is important to note a few things about the\ninner-product matrix in this case:\n\n    1. It depends on the dimensions and discretization of the mesh\n    2. It depends on where the discrete variables live; e.g. edges, faces, nodes, centers\n\nFor this simple class of inner products, the inner product matricies for\ndiscrete quantities living on various parts of the mesh have the form:\n\n.. math::\n    \\textrm{Centers:} \\; \\mathbf{M_c} &= \\textrm{diag} (\\mathbf{v} ) \\\\\n    \\textrm{Nodes:} \\; \\mathbf{M_n} &= \\frac{1}{2^{2k}} \\mathbf{P_n^T } \\textrm{diag} (\\mathbf{v} ) \\mathbf{P_n} \\\\\n    \\textrm{Faces:} \\; \\mathbf{M_f} &= \\frac{1}{4} \\mathbf{P_f^T } \\textrm{diag} (\\mathbf{I_k \\otimes v} ) \\mathbf{P_f} \\\\\n    \\textrm{Edges:} \\; \\mathbf{M_e} &= \\frac{1}{4^{k-1}} \\mathbf{P_e^T } \\textrm{diag} (\\mathbf{I_k \\otimes v}) \\mathbf{P_e}\n\nwhere :math:`k = 1,2,3`, :math:`\\mathbf{I_k}` is the identity matrix and\n:math:`\\otimes` is the kronecker product. :math:`\\mathbf{P}` are projection\nmatricies that map quantities from one part of the cell (nodes, faces, edges)\nto cell centers.\n\n\n\"\"\"\n\n####################################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial\n#\n\nfrom discretize.utils import sdiag\nfrom discretize import TensorMesh\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nrng = np.random.default_rng(8572)\n\n# sphinx_gallery_thumbnail_number = 2\n\n\n#####################################################\n# Scalars\n# -------\n#\n# It is natural for scalar quantities to live at cell centers or nodes. Here\n# we will define a scalar function (a Gaussian distribution in this case):\n#\n# .. math::\n#     \\phi(x) = \\frac{1}{\\sqrt{2 \\pi \\sigma^2}} \\, e^{- \\frac{(x- \\mu )^2}{2 \\sigma^2}}\n#\n#\n# We will then evaluate the following inner product:\n#\n# .. math::\n#     (\\phi , \\phi) = \\int_\\Omega \\phi^2 \\, dx = \\frac{1}{2\\sigma \\sqrt{\\pi}}\n#\n#\n# according to the mid-point rule using inner-product matricies. Next we\n# compare the numerical approximation of the inner product with the analytic\n# solution. *Note that the method for evaluating inner products here can be\n# extended to variables in 2D and 3D*.\n#\n\n\n# Define the Gaussian function\ndef fcn_gaussian(x, mu, sig):\n    return (1 / np.sqrt(2 * np.pi * sig**2)) * np.exp(-0.5 * (x - mu) ** 2 / sig**2)\n\n\n# Create a tensor mesh that is sufficiently large\nh = 0.1 * np.ones(100)\nmesh = TensorMesh([h], \"C\")\n\n# Define center point and standard deviation\nmu = 0\nsig = 1.5\n\n# Evaluate at cell centers and nodes\nphi_c = fcn_gaussian(mesh.gridCC, mu, sig)\nphi_n = fcn_gaussian(mesh.gridN, mu, sig)\n\n# Define inner-product matricies\nMc = sdiag(mesh.cell_volumes)  # cell-centered\n# Mn = mesh.getNodalInnerProduct()  # on nodes (*functionality pending*)\n\n# Compute the inner product\nipt = 1 / (2 * sig * np.sqrt(np.pi))  # true value of (f, f)\nipc = np.dot(phi_c, (Mc * phi_c))\n# ipn = np.dot(phi_n, (Mn*phi_n)) (*functionality pending*)\n\nfig = plt.figure(figsize=(5, 5))\nax = fig.add_subplot(111)\nax.plot(mesh.gridCC, phi_c)\nax.set_title(\"phi at cell centers\")\n\n# Verify accuracy\nprint(\"ACCURACY\")\nprint(\"Analytic solution:    \", ipt)\nprint(\"Cell-centered approx.:\", ipc)\n# print('Nodal approx.:        ', ipn)\n\n\n#####################################################\n# Vectors\n# -------\n#\n# To preserve the natural boundary conditions for each cell, it is standard\n# practice to define fields on cell edges and fluxes on cell faces. Here we\n# will define a 2D vector quantity:\n#\n# .. math::\n#     \\vec{v}(x,y) = \\Bigg [ \\frac{-y}{r} \\hat{x} + \\frac{x}{r} \\hat{y} \\Bigg ]\n#     \\, e^{-\\frac{x^2+y^2}{2\\sigma^2}}\n#\n# We will then evaluate the following inner product:\n#\n# .. math::\n#     (\\vec{v}, \\vec{v}) = \\int_\\Omega \\vec{v} \\cdot \\vec{v} \\, da\n#     = 2 \\pi \\sigma^2\n#\n# using inner-product matricies. Next we compare the numerical evaluation\n# of the inner products with the analytic solution. *Note that the method for\n# evaluating inner products here can be extended to variables in 3D*.\n#\n\n\n# Define components of the function\ndef fcn_x(xy, sig):\n    return (-xy[:, 1] / np.sqrt(np.sum(xy**2, axis=1))) * np.exp(\n        -0.5 * np.sum(xy**2, axis=1) / sig**2\n    )\n\n\ndef fcn_y(xy, sig):\n    return (xy[:, 0] / np.sqrt(np.sum(xy**2, axis=1))) * np.exp(\n        -0.5 * np.sum(xy**2, axis=1) / sig**2\n    )\n\n\n# Create a tensor mesh that is sufficiently large\nh = 0.1 * np.ones(100)\nmesh = TensorMesh([h, h], \"CC\")\n\n# Define center point and standard deviation\nsig = 1.5\n\n# Evaluate inner-product using edge-defined discrete variables\nvx = fcn_x(mesh.gridEx, sig)\nvy = fcn_y(mesh.gridEy, sig)\nv = np.r_[vx, vy]\n\nMe = mesh.get_edge_inner_product()  # Edge inner product matrix\n\nipe = np.dot(v, Me * v)\n\n# Evaluate inner-product using face-defined discrete variables\nvx = fcn_x(mesh.gridFx, sig)\nvy = fcn_y(mesh.gridFy, sig)\nv = np.r_[vx, vy]\n\nMf = mesh.get_face_inner_product()  # Edge inner product matrix\n\nipf = np.dot(v, Mf * v)\n\n# The analytic solution of (v, v)\nipt = np.pi * sig**2\n\n# Plot the vector function\nfig = plt.figure(figsize=(5, 5))\nax = fig.add_subplot(111)\nmesh.plot_image(\n    v, ax=ax, v_type=\"F\", view=\"vec\", stream_opts={\"color\": \"w\", \"density\": 1.0}\n)\nax.set_title(\"v at cell faces\")\n\nfig.show()\n\n# Verify accuracy\nprint(\"ACCURACY\")\nprint(\"Analytic solution:    \", ipt)\nprint(\"Edge variable approx.:\", ipe)\nprint(\"Face variable approx.:\", ipf)\n\n##############################################\n# Inverse of Inner Product Matricies\n# ----------------------------------\n#\n# The final discretized system using the finite volume method may contain\n# the inverse of an inner-product matrix. Here we show how the inverse of\n# the inner product matrix can be explicitly constructed. We validate its\n# accuracy for cell-centers, nodes, edges and faces by computing the folling\n# L2-norm for each:\n#\n# .. math::\n#     \\| \\mathbf{v - M^{-1} M v} \\|^2\n#\n#\n\n\n# Create a tensor mesh\nh = 0.1 * np.ones(100)\nmesh = TensorMesh([h, h], \"CC\")\n\n# Cell centered for scalar quantities\nMc = sdiag(mesh.cell_volumes)\nMc_inv = sdiag(1 / mesh.cell_volumes)\n\n# Nodes for scalar quantities  (*functionality pending*)\n# Mn = mesh.getNodalInnerProduct()\n# Mn_inv = mesh.getNodalInnerProduct(invert_matrix=True)\n\n# Edges for vector quantities\nMe = mesh.get_edge_inner_product()\nMe_inv = mesh.get_edge_inner_product(invert_matrix=True)\n\n# Faces for vector quantities\nMf = mesh.get_face_inner_product()\nMf_inv = mesh.get_face_inner_product(invert_matrix=True)\n\n# Generate some random vectors\nphi_c = rng.random(mesh.nC)\n# phi_n = rng.random(mesh.nN)\nvec_e = rng.random(mesh.nE)\nvec_f = rng.random(mesh.nF)\n\n# Generate some random vectors\nnorm_c = np.linalg.norm(phi_c - Mc_inv.dot(Mc.dot(phi_c)))\n# norm_n = np.linalg.norm(phi_n - Mn_inv*Mn*phi_n)\nnorm_e = np.linalg.norm(vec_e - Me_inv * Me * vec_e)\nnorm_f = np.linalg.norm(vec_f - Mf_inv * Mf * vec_f)\n\n# Verify accuracy\nprint(\"ACCURACY\")\nprint(\"Norm for centers:\", norm_c)\n# print('Norm for nodes:  ', norm_n)\nprint(\"Norm for edges:  \", norm_e)\nprint(\"Norm for faces:  \", norm_f)\n"
  },
  {
    "path": "tutorials/inner_products/2_physical_properties.py",
    "content": "r\"\"\"\nConstitutive Relations\n======================\n\nWhen solving PDEs using the finite volume approach, inner products may\ncontain constitutive relations; examples include Ohm's law and Hooke's law.\nFor this class of inner products, you will learn how to:\n\n    - Construct the inner-product matrix in the case of isotropic and anisotropic constitutive relations\n    - Construct the inverse of the inner-product matrix\n    - Work with constitutive relations defined by the reciprocal of a parameter\n\nLet :math:`\\vec{J}` and :math:`\\vec{E}` be two physically related\nquantities. If their relationship is isotropic (defined by a constant\n:math:`\\sigma`), then the constitutive relation is given by:\n\n.. math::\n    \\vec{J} = \\sigma \\vec{E}\n\nThe inner product between a vector :math:`\\vec{v}` and the right-hand side\nof this expression is given by:\n\n.. math::\n    (\\vec{v}, \\sigma \\vec{E} ) = \\int_\\Omega \\vec{v} \\cdot \\sigma \\vec{E} \\, dv\n\nJust like in the previous tutorial, we would like to approximate the inner\nproduct numerically using an *inner-product matrix* such that:\n\n.. math::\n    (\\vec{v}, \\sigma \\vec{E} ) \\approx \\mathbf{v^T M_\\sigma e}\n\nwhere the inner product matrix :math:`\\mathbf{M_\\sigma}` now depends on:\n\n    1. the dimensions and discretization of the mesh\n    2. where :math:`\\mathbf{v}` and :math:`\\mathbf{e}` live\n    3. the spatial distribution of the property :math:`\\sigma`\n\nIn the case of anisotropy, the constitutive relations are defined by a tensor\n(:math:`\\Sigma`). Here, the constitutive relation is of the form:\n\n.. math::\n    \\vec{J} = \\Sigma \\vec{E}\n\nwhere\n\n.. math::\n    \\Sigma = \\begin{bmatrix} \\sigma_{1} & \\sigma_{4} & \\sigma_{5} \\\\\n    \\sigma_{4} & \\sigma_{2} & \\sigma_{6} \\\\\n    \\sigma_{5} & \\sigma_{6} & \\sigma_{3} \\end{bmatrix}\n\nIs symmetric and defined by 6 independent parameters. The inner product between\na vector :math:`\\vec{v}` and the right-hand side of this expression is given\nby:\n\n.. math::\n    (\\vec{v}, \\Sigma \\vec{E} ) = \\int_\\Omega \\vec{v} \\cdot \\Sigma \\vec{E}  \\, dv\n\nOnce again we would like to approximate the inner product numerically using an\n*inner-product matrix* :math:`\\mathbf{M_\\Sigma}` such that:\n\n.. math::\n    (\\vec{v}, \\Sigma \\vec{E} ) \\approx \\mathbf{v^T M_\\Sigma e}\n\n\"\"\"\n\n####################################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial\n#\n\nfrom discretize import TensorMesh\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nrng = np.random.default_rng(87236)\n\n# sphinx_gallery_thumbnail_number = 1\n\n#####################################################\n# Inner Product for a Single Cell\n# -------------------------------\n#\n# Here we compare the inner product matricies for a single cell when the\n# constitutive relationship is:\n#\n#     - **isotropic:** :math:`\\sigma_1 = \\sigma_2 = \\sigma_3 = \\sigma` and :math:`\\sigma_4 = \\sigma_5 = \\sigma_6 = 0`; e.g. :math:`\\vec{J} = \\sigma \\vec{E}`\n#     - **diagonal anisotropic:** independent parameters :math:`\\sigma_1, \\sigma_2, \\sigma_3` and :math:`\\sigma_4 = \\sigma_5 = \\sigma_6 = 0`\n#     - **fully anisotropic:** independent parameters :math:`\\sigma_1, \\sigma_2, \\sigma_3, \\sigma_4, \\sigma_5, \\sigma_6`\n#\n# When approximating the inner product according to the finite volume approach,\n# the constitutive parameters are defined at cell centers; even if the\n# fields/fluxes live at cell edges/faces. As we will see, inner-product\n# matricies are generally diagonal; except for in the fully anisotropic case\n# where the inner product matrix contains a significant number of non-diagonal\n# entries.\n#\n\n# Create a single 3D cell\nh = np.ones(1)\nmesh = TensorMesh([h, h, h])\n\n# Define 6 constitutive parameters for the cell\nsig1, sig2, sig3, sig4, sig5, sig6 = 6, 5, 4, 3, 2, 1\n\n# Isotropic case\nsig = sig1 * np.ones((1, 1))\nsig_tensor_1 = np.diag(sig1 * np.ones(3))\nMe1 = mesh.get_edge_inner_product(sig)  # Edges inner product matrix\nMf1 = mesh.get_face_inner_product(sig)  # Faces inner product matrix\n\n# Diagonal anisotropic\nsig = np.c_[sig1, sig2, sig3]\nsig_tensor_2 = np.diag(np.array([sig1, sig2, sig3]))\nMe2 = mesh.get_edge_inner_product(sig)\nMf2 = mesh.get_face_inner_product(sig)\n\n# Full anisotropic\nsig = np.c_[sig1, sig2, sig3, sig4, sig5, sig6]\nsig_tensor_3 = np.diag(np.array([sig1, sig2, sig3]))\nsig_tensor_3[(0, 1), (1, 0)] = sig4\nsig_tensor_3[(0, 2), (2, 0)] = sig5\nsig_tensor_3[(1, 2), (2, 1)] = sig6\nMe3 = mesh.get_edge_inner_product(sig)\nMf3 = mesh.get_face_inner_product(sig)\n\n# Plotting matrix entries\nfig = plt.figure(figsize=(12, 12))\n\nax1 = fig.add_subplot(331)\nax1.imshow(sig_tensor_1)\nax1.set_title(\"Property Tensor (isotropic)\")\n\nax2 = fig.add_subplot(332)\nax2.imshow(sig_tensor_2)\nax2.set_title(\"Property Tensor (diagonal anisotropic)\")\n\nax3 = fig.add_subplot(333)\nax3.imshow(sig_tensor_3)\nax3.set_title(\"Property Tensor (full anisotropic)\")\n\nax4 = fig.add_subplot(334)\nax4.imshow(Mf1.todense())\nax4.set_title(\"M-faces Matrix (isotropic)\")\n\nax5 = fig.add_subplot(335)\nax5.imshow(Mf2.todense())\nax5.set_title(\"M-faces Matrix (diagonal anisotropic)\")\n\nax6 = fig.add_subplot(336)\nax6.imshow(Mf3.todense())\nax6.set_title(\"M-faces Matrix (full anisotropic)\")\n\nax7 = fig.add_subplot(337)\nax7.imshow(Me1.todense())\nax7.set_title(\"M-edges Matrix (isotropic)\")\n\nax8 = fig.add_subplot(338)\nax8.imshow(Me2.todense())\nax8.set_title(\"M-edges Matrix (diagonal anisotropic)\")\n\nax9 = fig.add_subplot(339)\nax9.imshow(Me3.todense())\nax9.set_title(\"M-edges Matrix (full anisotropic)\")\n\n\n#############################################################\n# Spatially Variant Parameters\n# ----------------------------\n#\n# In practice, the parameter :math:`\\sigma` or tensor :math:`\\Sigma` will\n# vary spatially. In this case, we define the parameter\n# :math:`\\sigma` (or parameters :math:`\\Sigma`) for each cell. When\n# creating the inner product matrix, we enter these parameters as\n# a numpy array. This is demonstrated below. Properties of the resulting\n# inner product matricies are discussed.\n#\n\n# Create a small 3D mesh\nh = np.ones(5)\nmesh = TensorMesh([h, h, h])\n\n# Isotropic case: (nC, ) numpy array\nsig = rng.random(mesh.nC)  # sig for each cell\nMe1 = mesh.get_edge_inner_product(sig)  # Edges inner product matrix\nMf1 = mesh.get_face_inner_product(sig)  # Faces inner product matrix\n\n# Linear case: (nC, dim) numpy array\nsig = rng.random((mesh.nC, mesh.dim))\nMe2 = mesh.get_edge_inner_product(sig)\nMf2 = mesh.get_face_inner_product(sig)\n\n# Anisotropic case: (nC, 3) for 2D and (nC, 6) for 3D\nsig = rng.random((mesh.nC, 6))\nMe3 = mesh.get_edge_inner_product(sig)\nMf3 = mesh.get_face_inner_product(sig)\n\n# Properties of inner product matricies\nprint(\"\\n FACE INNER PRODUCT MATRIX\")\nprint(\"- Number of faces              :\", mesh.nF)\nprint(\"- Dimensions of operator       :\", str(mesh.nF), \"x\", str(mesh.nF))\nprint(\"- Number non-zero (isotropic)  :\", str(Mf1.nnz))\nprint(\"- Number non-zero (linear)     :\", str(Mf2.nnz))\nprint(\"- Number non-zero (anisotropic):\", str(Mf3.nnz), \"\\n\")\n\nprint(\"\\n EDGE INNER PRODUCT MATRIX\")\nprint(\"- Number of faces              :\", mesh.nE)\nprint(\"- Dimensions of operator       :\", str(mesh.nE), \"x\", str(mesh.nE))\nprint(\"- Number non-zero (isotropic)  :\", str(Me1.nnz))\nprint(\"- Number non-zero (linear)     :\", str(Me2.nnz))\nprint(\"- Number non-zero (anisotropic):\", str(Me3.nnz), \"\\n\")\n\n\n#############################################################\n# Inverse\n# -------\n#\n# The final discretized system using the finite volume method may contain\n# the inverse of the inner-product matrix. Here we show how to call this\n# using the *invert_matrix* keyword argument.\n#\n# For the isotropic and diagonally anisotropic cases, the inner product matrix\n# is diagonal. As a result, its inverse can be easily formed. For the full\n# anisotropic case however, we cannot expicitly form the inverse because the\n# inner product matrix contains a significant number of off-diagonal elements.\n#\n# For the isotropic and diagonal anisotropic cases we can form\n# :math:`\\mathbf{M}^{-1}` then apply it to a vector using the :math:`*`\n# operator. For the full anisotropic case, we must form the inner product\n# matrix and do a numerical solve.\n#\n\n# Create a small 3D mesh\nh = np.ones(5)\nmesh = TensorMesh([h, h, h])\n\n# Isotropic case: (nC, ) numpy array\nsig = rng.random(mesh.nC)\nMe1_inv = mesh.get_edge_inner_product(sig, invert_matrix=True)\nMf1_inv = mesh.get_face_inner_product(sig, invert_matrix=True)\n\n# Diagonal anisotropic: (nC, dim) numpy array\nsig = rng.random((mesh.nC, mesh.dim))\nMe2_inv = mesh.get_edge_inner_product(sig, invert_matrix=True)\nMf2_inv = mesh.get_face_inner_product(sig, invert_matrix=True)\n\n# Full anisotropic: (nC, 3) for 2D and (nC, 6) for 3D\nsig = rng.random((mesh.nC, 6))\nMe3 = mesh.get_edge_inner_product(sig)\nMf3 = mesh.get_face_inner_product(sig)\n\n\n###########################################################################\n# Reciprocal Properties\n# ---------------------\n#\n# At times, the constitutive relation may be defined by the reciprocal of\n# a parameter (:math:`\\rho`). Here we demonstrate how inner product matricies\n# can be formed using the keyword argument *invert_model*. We will do this for a\n# single cell and plot the matrix elements. We can easily extend this to\n# a mesh comprised of many cells.\n#\n# In this case, the constitutive relation is given by:\n#\n# .. math::\n#     \\vec{J} = \\frac{1}{\\rho} \\vec{E}\n#\n# The inner product between a vector :math:`\\vec{v}` and the right-hand side\n# of the expression is given by:\n#\n# .. math::\n#     (\\vec{v}, \\rho^{-1} \\vec{E} ) = \\int_\\Omega \\vec{v} \\cdot \\rho^{-1} \\vec{E} \\, dv\n#\n# where the inner product is approximated using an inner product matrix\n# :math:`\\mathbf{M_{\\rho^{-1}}}` as follows:\n#\n# .. math::\n#     (\\vec{v}, \\rho^{-1} \\vec{E} ) \\approx \\mathbf{v^T M_{\\rho^{-1}} e}\n#\n# In the case that the constitutive relation is defined by a\n# tensor :math:`P`, e.g.:\n#\n# .. math::\n#     \\vec{J} = P \\vec{E}\n#\n# where\n#\n# .. math::\n#     P = \\begin{bmatrix} \\rho_{1}^{-1} & \\rho_{4}^{-1} & \\rho_{5}^{-1} \\\\\n#     \\rho_{4}^{-1} & \\rho_{2}^{-1} & \\rho_{6}^{-1} \\\\\n#     \\rho_{5}^{-1} & \\rho_{6}^{-1} & \\rho_{3}^{-1} \\end{bmatrix}\n#\n# The inner product between a vector :math:`\\vec{v}` and the right-hand side of\n# this expression is given by:\n#\n# .. math::\n#     (\\vec{v}, P \\vec{E} ) = \\int_\\Omega \\vec{v} \\cdot P \\vec{E}  \\, dv\n#\n# Once again we would like to approximate the inner product numerically using an\n# *inner-product matrix* :math:`\\mathbf{M_P}` such that:\n#\n# .. math::\n#     (\\vec{v}, P \\vec{E} ) \\approx \\mathbf{v^T M_P e}\n#\n# Here we demonstrate how to form the inner-product matricies\n# :math:`\\mathbf{M_{\\rho^{-1}}}` and :math:`\\mathbf{M_P}`.\n#\n\n# Create a small 3D mesh\nh = np.ones(1)\nmesh = TensorMesh([h, h, h])\n\n# Define 6 constitutive parameters for the cell\nrho1, rho2, rho3, rho4, rho5, rho6 = (\n    1.0 / 6.0,\n    1.0 / 5.0,\n    1.0 / 4.0,\n    1.0 / 3.0,\n    1.0 / 2.0,\n    1,\n)\n\n# Isotropic case\nrho = rho1 * np.ones((1, 1))\nMe1 = mesh.get_edge_inner_product(rho, invert_model=True)  # Edges inner product matrix\nMf1 = mesh.get_face_inner_product(rho, invert_model=True)  # Faces inner product matrix\n\n# Diagonal anisotropic case\nrho = np.c_[rho1, rho2, rho3]\nMe2 = mesh.get_edge_inner_product(rho, invert_model=True)\nMf2 = mesh.get_face_inner_product(rho, invert_model=True)\n\n# Full anisotropic case\nrho = np.c_[rho1, rho2, rho3, rho4, rho5, rho6]\nMe3 = mesh.get_edge_inner_product(rho, invert_model=True)\nMf3 = mesh.get_face_inner_product(rho, invert_model=True)\n\n# Plotting matrix entries\nfig = plt.figure(figsize=(14, 9))\n\nax1 = fig.add_subplot(231)\nax1.imshow(Mf1.todense())\nax1.set_title(\"Isotropic (Faces)\")\n\nax2 = fig.add_subplot(232)\nax2.imshow(Mf2.todense())\nax2.set_title(\"Diagonal Anisotropic (Faces)\")\n\nax3 = fig.add_subplot(233)\nax3.imshow(Mf3.todense())\nax3.set_title(\"Full Anisotropic (Faces)\")\n\nax4 = fig.add_subplot(234)\nax4.imshow(Me1.todense())\nax4.set_title(\"Isotropic (Edges)\")\n\nax5 = fig.add_subplot(235)\nax5.imshow(Me2.todense())\nax5.set_title(\"Diagonal Anisotropic (Edges)\")\n\nax6 = fig.add_subplot(236)\nax6.imshow(Me3.todense())\nax6.set_title(\"Full Anisotropic (Edges)\")\n"
  },
  {
    "path": "tutorials/inner_products/3_calculus.py",
    "content": "r\"\"\"\nDifferential Operators\n======================\n\nWhen solving PDEs using the finite volume approach, inner products may\ncontain differential operators. Where :math:`\\psi` and :math:`\\phi` are\nscalar quantities, and :math:`\\vec{u}` and :math:`\\vec{v}` are vector\nquantities, we may need to derive a discrete approximation for the following\ninner products:\n\n    1. :math:`(\\vec{u} , \\nabla \\phi)`\n    2. :math:`(\\psi , \\nabla \\cdot \\vec{v})`\n    3. :math:`(\\vec{u} , \\nabla \\times \\vec{v})`\n    4. :math:`(\\psi, \\Delta^2 \\phi)`\n\nIn this section, we demonstrate how to go from the inner product to the\ndiscrete approximation for each case. In doing so, we must construct\ndiscrete differential operators, inner product matricies and consider\nboundary conditions.\n\"\"\"\n\n####################################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial\n#\n\nfrom discretize.utils import sdiag\nfrom discretize import TensorMesh\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n\n#####################################################\n# Gradient\n# --------\n#\n# Where :math:`\\phi` is a scalar quantity and :math:`\\vec{u}` is a vector\n# quantity, we would like to evaluate the following inner product:\n#\n# .. math::\n#     (\\vec{u} , \\nabla \\phi) = \\int_\\Omega \\vec{u} \\cdot \\nabla \\phi \\, dv\n#\n# **Inner Product at edges:**\n#\n# In the case that :math:`\\vec{u}` represents a field, it is natural for it to\n# be discretized to live on cell edges. By defining :math:`\\phi` to live at\n# the nodes, we can use the nodal gradient operator (:math:`\\mathbf{G_n}`) to\n# map from nodes to edges. The inner product is therefore computed using an\n# inner product matrix (:math:`\\mathbf{M_e}`) for\n# quantities living on cell edges, e.g.:\n#\n# .. math::\n#     (\\vec{u} , \\nabla \\phi) \\approx \\mathbf{u^T M_e G_n \\phi}\n#\n# **Inner Product at faces:**\n#\n# In the case that :math:`\\vec{u}` represents a flux, it is natural for it to\n# be discretized to live on cell faces. By defining :math:`\\phi` to live at\n# cell centers, we can use the cell gradient operator (:math:`\\mathbf{G_c}`) to\n# map from centers to faces. In this case, we must impose boundary conditions\n# on the discrete gradient operator because it cannot use locations outside\n# the mesh to evaluate the gradient on the boundary. If done correctly, the\n# inner product is computed using an inner product matrix (:math:`\\mathbf{M_f}`)\n# for quantities living on cell faces, e.g.:\n#\n# .. math::\n#    (\\vec{u} , \\nabla \\phi) \\approx \\mathbf{u^T M_f G_c \\phi}\n#\n\n# Make basic mesh\nh = np.ones(10)\nmesh = TensorMesh([h, h, h])\n\n# Items required to perform u.T*(Me*Gn*phi)\nMe = mesh.get_edge_inner_product()  # Basic inner product matrix (edges)\nGn = mesh.nodal_gradient  # Nodes to edges gradient\n\n# Items required to perform u.T*(Mf*Gc*phi)\nMf = mesh.get_face_inner_product()  # Basic inner product matrix (faces)\nmesh.set_cell_gradient_BC(\n    [\"neumann\", \"dirichlet\", \"neumann\"]\n)  # Set boundary conditions\nGc = mesh.cell_gradient  # Cells to faces gradient\n\n# Plot Sparse Representation\nfig = plt.figure(figsize=(5, 6))\n\nax1 = fig.add_subplot(121)\nax1.spy(Me * Gn, markersize=0.5)\nax1.set_title(\"Me*Gn\")\n\nax2 = fig.add_subplot(122)\nax2.spy(Mf * Gc, markersize=0.5)\nax2.set_title(\"Mf*Gc\")\n\n#####################################################\n# Divergence\n# ----------\n#\n# Where :math:`\\psi` is a scalar quantity and :math:`\\vec{v}` is a vector\n# quantity, we would like to evaluate the following inner product:\n#\n# .. math::\n#     (\\psi , \\nabla \\cdot \\vec{v}) = \\int_\\Omega \\psi \\nabla \\cdot \\vec{v} \\, dv\n#\n# The divergence defines a measure of the flux leaving/entering a volume. As a\n# result, it is natural for :math:`\\vec{v}` to be a flux defined on cell faces.\n# The face divergence operator (:math:`\\mathbf{D}`) maps from cell faces to\n# cell centers, therefore # we should define :math:`\\psi` at cell centers. The\n# inner product is ultimately computed using an inner product matrix\n# (:math:`\\mathbf{M_f}`) for quantities living on cell faces, e.g.:\n#\n# .. math::\n#    (\\psi , \\nabla \\cdot \\vec{v}) \\approx \\mathbf{\\psi^T} \\textrm{diag} (\\mathbf{vol} ) \\mathbf{D v}\n#\n\n# Make basic mesh\nh = np.ones(10)\nmesh = TensorMesh([h, h, h])\n\n# Items required to perform psi.T*(Mc*D*v)\nMc = sdiag(mesh.cell_volumes)  # Basic inner product matrix (centers)\nD = mesh.face_divergence  # Faces to centers divergence\n\n# Plot sparse representation\nfig = plt.figure(figsize=(8, 5))\n\nax1 = fig.add_subplot(111)\nax1.spy(Mc * D, markersize=0.5)\nax1.set_title(\"Mc*D\", pad=20)\n\n#####################################################\n# Curl\n# ----\n#\n# Where :math:`\\vec{u}` and :math:`\\vec{v}` are vector quantities, we would\n# like to evaluate the following inner product:\n#\n# .. math::\n#     (\\vec{u} , \\nabla \\times \\vec{v}) = \\int_\\Omega \\vec{u} \\nabla \\times \\vec{v} \\, dv\n#\n# **Inner Product at Faces:**\n#\n# Let :math:`\\vec{u}` denote a flux and let :math:`\\vec{v}` denote a field.\n# In this case, it is natural for the flux :math:`\\vec{u}` to live on cell\n# faces and for the field :math:`\\vec{v}` to live on cell edges. The discrete\n# curl operator (:math:`\\mathbf{C_e}`) in this case naturally maps from cell\n# edges to cell faces without the need to define boundary conditions. The\n# inner product can be approxiated using an inner product matrix\n# (:math:`\\mathbf{M_f}`) for quantities living on cell faces, e.g.:\n#\n# .. math::\n#     (\\vec{u} , \\nabla \\times \\vec{v}) \\approx \\mathbf{u^T M_f C_e v}\n#\n# **Inner Product at Edges:**\n#\n# Now let :math:`\\vec{u}` denote a field and let :math:`\\vec{v}` denote a flux.\n# Now it is natural for the :math:`\\vec{u}` to live on cell edges\n# and for :math:`\\vec{v}` to live on cell faces. We would like to compute the\n# inner product using an inner product matrix (:math:`\\mathbf{M_e}`) for\n# quantities living on cell edges. However, this requires a discrete curl\n# operator (:math:`\\mathbf{C_f}`) that maps from cell faces\n# to cell edges; which requires to impose boundary conditions on the operator.\n# If done successfully:\n#\n# .. math::\n#     (\\vec{u} , \\nabla \\times \\vec{v}) \\approx \\mathbf{u^T M_e C_f v}\n#\n\n# Make basic mesh\nh = np.ones(10)\nmesh = TensorMesh([h, h, h])\n\n# Items required to perform u.T*(Mf*Ce*v)\nMf = mesh.get_face_inner_product()  # Basic inner product matrix (faces)\nCe = mesh.edge_curl  # Edges to faces curl\n\n# Items required to perform u.T*(Me*Cf*v)\nMe = mesh.get_edge_inner_product()  # Basic inner product matrix (edges)\nCf = mesh.edge_curl.T  # Faces to edges curl (assumes Dirichlet)\n\n# Plot Sparse Representation\nfig = plt.figure(figsize=(9, 5))\n\nax1 = fig.add_subplot(121)\nax1.spy(Mf * Ce, markersize=0.5)\nax1.set_title(\"Mf*Ce\", pad=10)\n\nax2 = fig.add_subplot(122)\nax2.spy(Me * Cf, markersize=0.5)\nax2.set_title(\"Me*Cf\", pad=10)\n\n\n###########################################################\n# Scalar Laplacian\n# ----------------\n#\n# Where :math:`\\psi` and :math:`\\phi` are scalar quantities, and the scalar\n# Laplacian :math:`\\Delta^2 = \\nabla \\cdot \\nabla`, we would like to\n# approximate the following inner product:\n#\n# .. math::\n#     (\\psi , \\nabla \\cdot \\nabla \\phi) = \\int_\\Omega \\psi (\\nabla \\cdot \\nabla \\phi) \\, dv\n#\n# Using :math:`p \\nabla \\cdot \\mathbf{q} = \\nabla \\cdot (p \\mathbf{q}) - \\mathbf{q} \\cdot (\\nabla p )`\n# and the Divergence theorem we obtain:\n#\n# .. math::\n#     \\int_{\\partial \\Omega} \\mathbf{n} \\cdot ( \\psi \\nabla \\phi ) \\, da\n#     - \\int_\\Omega (\\nabla \\psi ) \\cdot (\\nabla \\phi ) \\, dv\n#\n# In this case, the surface integral can be eliminated if we can assume a\n# Neumann condition of :math:`\\partial \\phi/\\partial n = 0` on the boundary.\n#\n# **Inner Prodcut at Edges:**\n#\n# Let :math:`\\psi` and :math:`\\phi` be discretized to the nodes. In this case,\n# the discrete gradient operator (:math:`\\mathbf{G_n}`) must map from nodes\n# to edges. Ultimately we evaluate the inner product using an inner product\n# matrix (:math:`\\mathbf{M_e}` for quantities living on cell edges, e.g.:\n#\n# .. math::\n#     (\\psi , \\nabla \\cdot \\nabla \\phi) \\approx \\mathbf{\\psi G_n^T M_e G_n \\phi}\n#\n# **Inner Product at Faces:**\n#\n# Let :math:`\\psi` and :math:`\\phi` be discretized to cell centers. In this\n# case, the discrete gradient operator (:math:`\\mathbf{G_c}`) must map from\n# centers to faces; and requires the user to set Neumann conditions in the\n# operator. Ultimately we evaluate the inner product using an inner product\n# matrix (:math:`\\mathbf{M_f}`) for quantities living on cell faces, e.g.:\n#\n# .. math::\n#     (\\psi , \\nabla \\cdot \\nabla \\phi) \\approx \\mathbf{\\psi G_c^T M_f G_c \\phi}\n#\n#\n\n# Make basic mesh\nh = np.ones(10)\nmesh = TensorMesh([h, h, h])\n\n# Items required to perform psi.T*(Gn.T*Me*Gn*phi)\nMe = mesh.get_edge_inner_product()  # Basic inner product matrix (edges)\nGn = mesh.nodal_gradient  # Nodes to edges gradient\n\n# Items required to perform psi.T*(Gc.T*Mf*Gc*phi)\nMf = mesh.get_face_inner_product()  # Basic inner product matrix (faces)\nmesh.set_cell_gradient_BC([\"dirichlet\", \"dirichlet\", \"dirichlet\"])\nGc = mesh.cell_gradient  # Centers to faces gradient\n\n# Plot Sparse Representation\nfig = plt.figure(figsize=(9, 4))\n\nax1 = fig.add_subplot(121)\nax1.spy(Gn.T * Me * Gn, markersize=0.5)\nax1.set_title(\"Gn.T*Me*Gn\", pad=5)\n\nax2 = fig.add_subplot(122)\nax2.spy(Gc.T * Mf * Gc, markersize=0.5)\nax2.set_title(\"Gc.T*Mf*Gc\", pad=5)\n"
  },
  {
    "path": "tutorials/inner_products/4_advanced.py",
    "content": "\"\"\"\nAdvanced Examples\n=================\n\nIn this section, we demonstrate how to go from the inner product to the\ndiscrete approximation for some special cases. We also show how all\nnecessary operators are constructed for each case.\n\n\"\"\"\n\n####################################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial\n#\n\nfrom discretize.utils import sdiag\nfrom discretize import TensorMesh\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nrng = np.random.default_rng(4321)\n\n\n#####################################################\n# Constitive Relations and Differential Operators\n# -----------------------------------------------\n#\n# Where :math:`\\psi` and :math:`\\phi` are scalar quantities,\n# :math:`\\vec{u}` and :math:`\\vec{v}` are vector quantities, and\n# :math:`\\sigma` defines a constitutive relationship, we may need to derive\n# discrete approximations for the following inner products:\n#\n#     1. :math:`(\\vec{u} , \\sigma \\nabla \\phi)`\n#     2. :math:`(\\psi , \\sigma \\nabla \\cdot \\vec{v})`\n#     3. :math:`(\\vec{u} , \\sigma \\nabla \\times \\vec{v})`\n#\n# These cases effectively combine what was learned in the previous two\n# tutorials. For each case, we must:\n#\n#     - Define discretized quantities at the appropriate mesh locations\n#     - Define an inner product matrix that depends on a single constitutive parameter (:math:`\\sigma`) or a tensor (:math:`\\Sigma`)\n#     - Construct differential operators that may require you to define boundary conditions\n#\n# Where :math:`\\mathbf{M_e}(\\sigma)` is the property dependent inner-product\n# matrix for quantities on cell edges, :math:`\\mathbf{M_f}(\\sigma)` is the\n# property dependent inner-product matrix for quantities on cell faces,\n# :math:`\\mathbf{G_{ne}}` is the nodes to edges gradient operator and\n# :math:`\\mathbf{G_{cf}}` is the centers to faces gradient operator:\n#\n# .. math::\n#     (\\vec{u} , \\sigma \\nabla \\phi) &= \\mathbf{u_f^T M_f}(\\sigma) \\mathbf{ G_{cf} \\, \\phi_c} \\;\\;\\;\\;\\; (\\vec{u} \\;\\textrm{on faces and} \\; \\phi \\; \\textrm{at centers}) \\\\\n#     &= \\mathbf{u_e^T M_e}(\\sigma) \\mathbf{ G_{ne} \\, \\phi_n} \\;\\;\\;\\; (\\vec{u} \\;\\textrm{on edges and} \\; \\phi \\; \\textrm{on nodes})\n#\n# Where :math:`\\mathbf{M_c}(\\sigma)` is the property dependent inner-product\n# matrix for quantities at cell centers and :math:`\\mathbf{D}` is the faces\n# to centers divergence operator:\n#\n# .. math::\n#     (\\psi , \\sigma \\nabla \\cdot \\vec{v}) = \\mathbf{\\psi_c^T M_c} (\\sigma)\\mathbf{ D v_f} \\;\\;\\;\\; (\\psi \\;\\textrm{at centers and} \\; \\vec{v} \\; \\textrm{on faces} )\n#\n# Where :math:`\\mathbf{C_{ef}}` is the edges to faces curl operator and\n# :math:`\\mathbf{C_{fe}}` is the faces to edges curl operator:\n#\n# .. math::\n#     (\\vec{u} , \\sigma \\nabla \\times \\vec{v}) &= \\mathbf{u_f^T M_f} (\\sigma) \\mathbf{ C_{ef} \\, v_e} \\;\\;\\;\\; (\\vec{u} \\;\\textrm{on edges and} \\; \\vec{v} \\; \\textrm{on faces} )\\\\\n#     &= \\mathbf{u_e^T M_e} (\\sigma) \\mathbf{ C_{fe} \\, v_f} \\;\\;\\;\\; (\\vec{u} \\;\\textrm{on faces and} \\; \\vec{v} \\; \\textrm{on edges} )\n#\n# **With the operators constructed below, you can compute all of the\n# aforementioned inner products.**\n#\n\n\n# Make basic mesh\nh = np.ones(10)\nmesh = TensorMesh([h, h, h])\nsig = rng.random(mesh.nC)  # isotropic\nSig = rng.random((mesh.nC, 6))  # anisotropic\n\n# Inner product matricies\nMc = sdiag(mesh.cell_volumes * sig)  # Inner product matrix (centers)\n# Mn = mesh.getNodalInnerProduct(sig)  # Inner product matrix (nodes)  (*functionality pending*)\nMe = mesh.get_edge_inner_product(sig)  # Inner product matrix (edges)\nMf = mesh.get_face_inner_product(sig)  # Inner product matrix for tensor (faces)\n\n# Differential operators\nGne = mesh.nodal_gradient  # Nodes to edges gradient\nmesh.set_cell_gradient_BC(\n    [\"neumann\", \"dirichlet\", \"neumann\"]\n)  # Set boundary conditions\nGcf = mesh.cell_gradient  # Cells to faces gradient\nD = mesh.face_divergence  # Faces to centers divergence\nCef = mesh.edge_curl  # Edges to faces curl\nCfe = mesh.edge_curl.T  # Faces to edges curl\n\n# EXAMPLE: (u, sig*Curl*v)\nfig = plt.figure(figsize=(9, 5))\n\nax1 = fig.add_subplot(121)\nax1.spy(Mf * Cef, markersize=0.5)\nax1.set_title(\"Me(sig)*Cef (Isotropic)\", pad=10)\n\nMf_tensor = mesh.get_face_inner_product(Sig)  # inner product matrix for tensor\nax2 = fig.add_subplot(122)\nax2.spy(Mf_tensor * Cef, markersize=0.5)\nax2.set_title(\"Me(sig)*Cef (Anisotropic)\", pad=10)\n\n#####################################################\n# Divergence of a Scalar and a Vector Field\n# -----------------------------------------\n#\n# Where :math:`\\psi` and :math:`\\phi` are scalar quantities, and\n# :math:`\\vec{u}` is a known vector field, we may need to derive\n# a discrete approximation for the following inner product:\n#\n# .. math::\n#     (\\psi , \\nabla \\cdot \\phi \\vec{u})\n#\n# Scalar and vector quantities are generally discretized to lie on\n# different locations on the mesh. As result, it is better to use the\n# identity :math:`\\nabla \\cdot \\phi \\vec{u} = \\phi \\nabla \\cdot \\vec{u} + \\vec{u} \\cdot \\nabla \\phi`\n# and separate the inner product into two parts:\n#\n# .. math::\n#    (\\psi , \\phi \\nabla \\cdot \\vec{u} ) + (\\psi , \\vec{u} \\cdot \\nabla \\phi)\n#\n# **Term 1:**\n#\n# If the vector field :math:`\\vec{u}` is divergence free, there is no need\n# to evaluate the first inner product term. This is the case for advection when\n# the fluid is incompressible.\n#\n# Where :math:`\\mathbf{D_{fc}}` is the faces to centers divergence operator, and\n# :math:`\\mathbf{M_c}` is the basic inner product matrix for cell centered\n# quantities, we can approximate this inner product as:\n#\n# .. math::\n#     (\\psi , \\phi \\nabla \\cdot \\vec{u} ) = \\mathbf{\\psi_c^T M_c} \\textrm{diag} (\\mathbf{D_{fc} u_f} ) \\, \\mathbf{\\phi_c}\n#\n# **Term 2:**\n#\n# Let :math:`\\mathbf{G_{cf}}` be the cell centers to faces gradient operator,\n# :math:`\\mathbf{M_c}` be the basic inner product matrix for cell centered\n# quantities, and :math:`\\mathbf{\\tilde{A}_{fc}}` and averages *and* sums the\n# cartesian contributions of :math:`\\vec{u} \\cdot \\nabla \\phi`, we can\n# approximate the inner product as:\n#\n# .. math::\n#     (\\psi , \\vec{u} \\cdot \\nabla \\phi) = \\mathbf{\\psi_c^T M_c \\tilde A_{fc}} \\text{diag} (\\mathbf{u_f} ) \\mathbf{G_{cf} \\, \\phi_c}\n#\n# **With the operators constructed below, you can compute all of the\n# inner products.**\n\n# Make basic mesh\nh = np.ones(10)\nmesh = TensorMesh([h, h, h])\n\n# Inner product matricies\nMc = sdiag(mesh.cell_volumes * sig)  # Inner product matrix (centers)\n\n# Differential operators\nmesh.set_cell_gradient_BC(\n    [\"neumann\", \"dirichlet\", \"neumann\"]\n)  # Set boundary conditions\nGcf = mesh.cell_gradient  # Cells to faces gradient\nDfc = mesh.face_divergence  # Faces to centers divergence\n\n# Averaging and summing matrix\nAfc = mesh.dim * mesh.aveF2CC\n"
  },
  {
    "path": "tutorials/inner_products/README.txt",
    "content": "Inner Products\n==============\n\nNumerical solutions to differential equations frequently make use\nof the **weak formulation**. That is, we take the inner product\nof each PDE with some test function. There are many different ways to\nevaluate inner products numerically; i.e. trapezoidal rule, midpoint\nrule, or higher-order approximations. A simple method for evaluating\ninner products on a numerical grid is to apply the midpoint rule; \nwhich is used by the *discretize* package.\n\nHere, we demonstrate how to approximate various classes of inner\nproducts numerically. If this is known, the user will be capable\nof properly discretizing any term in a problem specific PDE."
  },
  {
    "path": "tutorials/mesh_generation/1_mesh_overview.py",
    "content": "\"\"\"\nOverview of Mesh Types\n======================\n\nHere we provide an overview of mesh types and define some terminology.\nSeparate tutorials have been provided for each mesh type.\n\n\"\"\"\n\nimport numpy as np\nimport discretize\nimport matplotlib.pyplot as plt\n\n###############################################################################\n# General Categories of Meshes\n# ----------------------------\n#\n# The three main types of meshes in discretize are\n#\n#    - **Tensor meshes** (:class:`discretize.TensorMesh`); which includes **cylindrical meshes** (:class:`discretize.CylindricalMesh`)\n#\n#    - **Tree meshes** (:class:`discretize.TreeMesh`): also referred to as QuadTree or OcTree meshes\n#\n#    - **Curvilinear meshes** (:class:`discretize.CurvilinearMesh`): also referred to as logically rectangular non-orthogonal\n#\n# Examples for each mesh type are shown below.\n#\n\nncx = 16  # number of cells in the x-direction\nncy = 16  # number of cells in the y-direction\n\n# create a tensor mesh\ntensor_mesh = discretize.TensorMesh([ncx, ncy])\n\n# create a tree mesh and refine some of the cells\ntree_mesh = discretize.TreeMesh([ncx, ncy])\n\n\ndef refine(cell):\n    if np.sqrt(((np.r_[cell.center] - 0.5) ** 2).sum()) < 0.2:\n        return 4\n    return 2\n\n\ntree_mesh.refine(refine)\n\n# create a curvilinear mesh\ncurvi_mesh = discretize.CurvilinearMesh(\n    discretize.utils.example_curvilinear_grid([ncx, ncy], \"rotate\")\n)\n\n# Plot\nfig, axes = plt.subplots(1, 3, figsize=(14.5, 4))\ntensor_mesh.plot_grid(ax=axes[0])\naxes[0].set_title(\"TensorMesh\")\n\ntree_mesh.plot_grid(ax=axes[1])\naxes[1].set_title(\"TreeMesh\")\n\ncurvi_mesh.plot_grid(ax=axes[2])\naxes[2].set_title(\"CurvilinearMesh\")\n\n###############################################################################\n# Variable Locations and Terminology\n# ----------------------------------\n#\n# When solving differential equations on a numerical grid, variables can be\n# defined on:\n#\n#    - nodes\n#    - cell centers\n#    - cell faces\n#    - cell edges\n#\n# Below we show an example for a 2D tensor mesh.\n#\n\nhx = np.r_[3, 1, 1, 3]\nhy = np.r_[3, 2, 1, 1, 1, 1, 2, 3]\ntensor_mesh2 = discretize.TensorMesh([hx, hy])\n\n# Plot\nfig, axes2 = plt.subplots(1, 3, figsize=(14.5, 5))\ntensor_mesh2.plot_grid(ax=axes2[0], nodes=True, centers=True)\naxes2[0].legend((\"Nodes\", \"Centers\"))\naxes2[0].set_title(\"Nodes and cell centers\")\n\ntensor_mesh2.plot_grid(ax=axes2[1], edges=True)\naxes2[1].legend((\"X-edges\", \"Y-edges\"))\naxes2[1].set_title(\"Cell edges\")\n\ntensor_mesh2.plot_grid(ax=axes2[2], faces=True)\naxes2[2].legend((\"X-faces\", \"Y-faces\"))\naxes2[2].set_title(\"Cell faces\")\n\n###############################################################################\n# Note that we define X-edges as being edges that lie parallel to the x-axis.\n# And we define X-faces as being faces whose normal lies parallel to the\n# axis. In 3D, the difference between edges and faces is more obvious.\n#\n"
  },
  {
    "path": "tutorials/mesh_generation/2_tensor_mesh.py",
    "content": "\"\"\"\nTensor meshes\n=============\n\nTensor meshes are the most basic class of meshes that can be created with\ndiscretize. They belong to the class (:class:`~discretize.TensorMesh`).\nTensor meshes can be defined in 1, 2 or 3 dimensions. Here we demonstrate:\n\n    - How to create basic tensor meshes\n    - How to include padding cells\n    - How to plot tensor meshes\n    - How to extract properties from meshes\n\n\"\"\"\n\n###############################################\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial.\n#\n\nfrom discretize import TensorMesh\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# sphinx_gallery_thumbnail_number = 3\n\n###############################################\n# Basic Example\n# -------------\n#\n# The easiest way to define a tensor mesh is to define the cell widths in\n# x, y and z as 1D numpy arrays. And to provide the position of the bottom\n# southwest corner of the mesh. We demonstrate this here for a 2D mesh (thus\n# we do not need to consider the z-dimension).\n#\n\nncx = 10  # number of core mesh cells in x\nncy = 15  # number of core mesh cells in y\ndx = 15  # base cell width x\ndy = 10  # base cell width y\nhx = dx * np.ones(ncx)\nhy = dy * np.ones(ncy)\n\nx0 = 0\ny0 = -150\n\nmesh = TensorMesh([hx, hy], x0=[x0, y0])\n\nmesh.plot_grid()\n\n\n###############################################\n# Padding Cells and Plotting\n# --------------------------\n#\n# For practical purposes, the user may want to define a region where the cell\n# widths are increasing/decreasing in size. For example, padding is often used\n# to define a large domain while reducing the total number of mesh cells.\n# Here we demonstrate how to create tensor meshes that have padding cells.\n#\n\nncx = 10  # number of core mesh cells in x\nncy = 15  # number of core mesh cells in y\ndx = 15  # base cell width x\ndy = 10  # base cell width y\nnpad_x = 4  # number of padding cells in x\nnpad_y = 4  # number of padding cells in y\nexp_x = 1.25  # expansion rate of padding cells in x\nexp_y = 1.25  # expansion rate of padding cells in y\n\n# Use a list of tuples to define cell widths in each direction. Each tuple\n# contains the cell width, number of cells and the expansion factor. A\n# negative sign is used to indicate an interval where cells widths go\n# from largest to smallest.\nhx = [(dx, npad_x, -exp_x), (dx, ncx), (dx, npad_x, exp_x)]\nhy = [(dy, npad_y, -exp_y), (dy, ncy), (dy, npad_y, exp_y)]\n\n# We can use flags 'C', '0' and 'N' to shift the xyz position of the mesh\n# relative to the origin\nmesh = TensorMesh([hx, hy], x0=\"CN\")\n\n# We can apply the plot_grid method and output to a specified axes object\nfig = plt.figure(figsize=(6, 6))\nax = fig.add_subplot(111)\nmesh.plot_grid(ax=ax)\nax.set_xbound(mesh.x0[0], mesh.x0[0] + np.sum(mesh.h[0]))\nax.set_ybound(mesh.x0[1], mesh.x0[1] + np.sum(mesh.h[1]))\nax.set_title(\"Tensor Mesh\")\n\n###############################################\n# Extracting Mesh Properties\n# --------------------------\n#\n# Once the mesh is created, you may want to extract certain properties. Here,\n# we show some properties that can be extracted from 2D meshes.\n#\n\nncx = 10  # number of core mesh cells in x\nncy = 15  # number of core mesh cells in y\ndx = 15  # base cell width x\ndy = 10  # base cell width y\nnpad_x = 4  # number of padding cells in x\nnpad_y = 4  # number of padding cells in y\nexp_x = 1.25  # expansion rate of padding cells in x\nexp_y = 1.25  # expansion rate of padding cells in y\n\nhx = [(dx, npad_x, -exp_x), (dx, ncx), (dx, npad_x, exp_x)]\nhy = [(dy, npad_y, -exp_y), (dy, ncy), (dy, npad_y, exp_y)]\n\nmesh = TensorMesh([hx, hy], x0=\"C0\")\n\n# The bottom west corner\nx0 = mesh.x0\n\n# The total number of cells\nnC = mesh.nC\n\n# An (nC, 2) array containing the cell-center locations\ncc = mesh.gridCC\n\n# A boolean array specifying which cells lie on the boundary\nbInd = mesh.cell_boundary_indices\n\n# Plot the cell areas (2D \"volume\")\ns = mesh.cell_volumes\n\nfig = plt.figure(figsize=(6, 6))\nax = fig.add_subplot(111)\nmesh.plot_image(s, grid=True, ax=ax)\nax.set_xbound(mesh.x0[0], mesh.x0[0] + np.sum(mesh.h[0]))\nax.set_ybound(mesh.x0[1], mesh.x0[1] + np.sum(mesh.h[1]))\nax.set_title(\"Cell Areas\")\n\n\n###############################################\n# 3D Example\n# ----------\n#\n# Here we show how the same approach can be used to create and extract\n# properties from a 3D tensor mesh.\n#\n\nnc = 10  # number of core mesh cells in x, y and z\ndh = 10  # base cell width in x, y and z\nnpad = 5  # number of padding cells\nexp = 1.25  # expansion rate of padding cells\n\nh = [(dh, npad, -exp), (dh, nc), (dh, npad, exp)]\nmesh = TensorMesh([h, h, h], x0=\"C00\")\n\n# The bottom southwest corner\nx0 = mesh.x0\n\n# The total number of cells\nnC = mesh.nC\n\n# An (nC, 3) array containing the cell-center locations\ncc = mesh.gridCC\n\n# A boolean array specifying which cells lie on the boundary\nbInd = mesh.cell_boundary_indices\n\n# The cell volumes\nv = mesh.cell_volumes\n\n# Plot all cells volumes or plot cell volumes for a particular horizontal slice\nfig = plt.figure(figsize=(9, 4))\nax1 = fig.add_subplot(121)\nax2 = fig.add_subplot(122)\n\nmesh.plot_image(np.log10(v), grid=True, ax=ax1)\nax1.set_title(\"All Cell Log-Volumes\")\n\ncplot = mesh.plot_slice(np.log10(v), grid=True, ax=ax2, normal=\"Z\", ind=2)\ncplot[0].set_clim(np.min(np.log10(v)), np.max(np.log10(v)))\nax2.set_title(\"Cell Log-Volumes #2\")\n"
  },
  {
    "path": "tutorials/mesh_generation/3_cylindrical_mesh.py",
    "content": "\"\"\"\nCylindrical meshes\n==================\n\nCylindrical meshes (:class:`~discretize.CylindricalMesh`) are defined in terms of *r*\n(radial position), *z* (vertical position) and *phi* (azimuthal position).\nThey are a child class of the tensor mesh class. Cylindrical meshes are useful\nin solving differential equations that possess rotational symmetry. Here we\ndemonstrate:\n\n    - How to create basic cylindrical meshes\n    - How to include padding cells\n    - How to plot cylindrical meshes\n    - How to extract properties from meshes\n    - How to create cylindrical meshes to solve PDEs with rotational symmetry\n\n\"\"\"\n\n###############################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial.\n#\n\nfrom discretize import CylindricalMesh\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n###############################################\n# Basic Example\n# -------------\n#\n# The easiest way to define a cylindrical mesh is to define the cell widths in\n# *r*, *phi* and *z* as 1D numpy arrays. And to provide a Cartesian position\n# for the bottom of the vertical axis of symmetry of the mesh. Note that\n#\n#    1. *phi* is in radians\n#    2. The sum of values in the numpy array for *phi* cannot exceed :math:`2\\pi`\n#\n#\n\nncr = 10  # number of mesh cells in r\nncp = 8  # number of mesh cells in phi\nncz = 15  # number of mesh cells in z\ndr = 15  # cell width r\ndz = 10  # cell width z\n\nhr = dr * np.ones(ncr)\nhp = (2 * np.pi / ncp) * np.ones(ncp)\nhz = dz * np.ones(ncz)\n\nx0 = 0.0\ny0 = 0.0\nz0 = -150.0\n\nmesh = CylindricalMesh([hr, hp, hz], x0=[x0, y0, z0])\n\nmesh.plot_grid()\n\n\n###############################################\n# Padding Cells and Extracting Properties\n# ---------------------------------------\n#\n# For practical purposes, the user may want to define a region where the cell\n# widths are increasing/decreasing in size. For example, padding is often used\n# to define a large domain while reducing the total number of mesh cells.\n# Here we demonstrate how to create cylindrical meshes that have padding cells.\n# We then show some properties that can be extracted from cylindrical meshes.\n#\n\nncr = 10  # number of mesh cells in r\nncp = 8  # number of mesh cells in phi\nncz = 15  # number of mesh cells in z\ndr = 15  # cell width r\ndp = 2 * np.pi / ncp  # cell width phi\ndz = 10  # cell width z\nnpad_r = 4  # number of padding cells in r\nnpad_z = 4  # number of padding cells in z\nexp_r = 1.25  # expansion rate of padding cells in r\nexp_z = 1.25  # expansion rate of padding cells in z\n\n# Use a list of tuples to define cell widths in each direction. Each tuple\n# contains the cell with, number of cells and the expansion factor (+ve/-ve).\nhr = [(dr, ncr), (dr, npad_r, exp_r)]\nhp = [(dp, ncp)]\nhz = [(dz, npad_z, -exp_z), (dz, ncz), (dz, npad_z, exp_z)]\n\n# We can use flags 'C', '0' and 'N' to define the xyz position of the mesh.\nmesh = CylindricalMesh([hr, hp, hz], x0=\"00C\")\n\n# We can apply the plot_grid method and change the axis properties\nax = mesh.plot_grid()\nax[0].set_title(\"Discretization in phi\")\n\nax[1].set_title(\"Discretization in r and z\")\nax[1].set_xlabel(\"r\")\nax[1].set_xbound(mesh.x0[0], mesh.x0[0] + np.sum(mesh.h[0]))\nax[1].set_ybound(mesh.x0[2], mesh.x0[2] + np.sum(mesh.h[2]))\n\n# The bottom end of the vertical axis of rotational symmetry\nx0 = mesh.x0\n\n# The total number of cells\nnC = mesh.nC\n\n# An (nC, 3) array containing the cell-center locations\ncc = mesh.gridCC\n\n# The cell volumes\nv = mesh.cell_volumes\n\n###############################################\n# Cylindrical Mesh for Rotational Symmetry\n# ----------------------------------------\n#\n# Cylindrical mesh are most useful when solving problems with perfect\n# rotational symmetry. More precisely when:\n#\n#    - field components in the *phi* direction are 0\n#    - fluxes in *r* and *z* are 0\n#\n# In this case, the size of the forward problem can be significantly reduced.\n# Here we demonstrate how to create a mesh for solving differential equations\n# with perfect rotational symmetry. Since the fields and fluxes are independent\n# of the phi position, there will be no need to discretize along the phi\n# direction.\n#\n\nncr = 10  # number of mesh cells in r\nncz = 15  # number of mesh cells in z\ndr = 15  # cell width r\ndz = 10  # cell width z\nnpad_r = 4  # number of padding cells in r\nnpad_z = 4  # number of padding cells in z\nexp_r = 1.25  # expansion rate of padding cells in r\nexp_z = 1.25  # expansion rate of padding cells in z\n\nhr = [(dr, ncr), (dr, npad_r, exp_r)]\nhz = [(dz, npad_z, -exp_z), (dz, ncz), (dz, npad_z, exp_z)]\n\n# A value of 1 is used to define the discretization in phi for this case.\nmesh = CylindricalMesh([hr, 1, hz], x0=\"00C\")\n\n# The bottom end of the vertical axis of rotational symmetry\nx0 = mesh.x0\n\n# The total number of cells\nnC = mesh.nC\n\n# An (nC, 3) array containing the cell-center locations\ncc = mesh.gridCC\n\n# Plot the cell volumes.\nv = mesh.cell_volumes\n\nfig = plt.figure(figsize=(6, 4))\nax = fig.add_subplot(111)\nmesh.plot_image(np.log10(v), grid=True, ax=ax)\nax.set_xlabel(\"r\")\nax.set_xbound(mesh.x0[0], mesh.x0[0] + np.sum(mesh.h[0]))\nax.set_ybound(mesh.x0[2], mesh.x0[2] + np.sum(mesh.h[2]))\nax.set_title(\"Cell Log-Volumes\")\n\n##############################################################################\n# Notice that we do not plot the discretization in phi as it is irrelevant.\n#\n"
  },
  {
    "path": "tutorials/mesh_generation/4_tree_mesh.py",
    "content": "\"\"\"\nTree Meshes\n===========\n\nCompared to tensor meshes, tree meshes are able to provide higher levels\nof discretization in certain regions while reducing the total number of\ncells. Tree meshes belong to the class (:class:`~discretize.TreeMesh`).\nTree meshes can be defined in 2 or 3 dimensions. Here we demonstrate:\n\n    - How to create basic tree meshes in 2D and 3D\n    - Strategies for local mesh refinement\n    - How to plot tree meshes\n    - How to extract properties from tree meshes\n\nTo create a tree mesh, we first define the base tensor mesh (a mesh\ncomprised entirely of the smallest cells). Next we choose the level of\ndiscretization around certain points or within certain regions. When\ncreating tree meshes, we must remember certain rules:\n\n    - The number of base mesh cells in x, y and z must all be powers of 2\n    - We cannot refine the mesh to create cells smaller than those defining the base mesh\n    - The range of cell sizes in the tree mesh depends on the number of base mesh cells in x, y and z\n\n\n\"\"\"\n\n###############################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial.\n#\n\nfrom discretize import TreeMesh\nfrom discretize.utils import mkvc\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# sphinx_gallery_thumbnail_number = 4\n\n###############################################\n# Basic Example\n# -------------\n#\n# Here we demonstrate the basic two step process for creating a 2D tree mesh\n# (QuadTree mesh). The region of highest discretization if defined within a\n# rectangular box. We use the keyword argument *octree_levels* to define the\n# rate of cell width increase outside the box.\n#\n\ndh = 5  # minimum cell width (base mesh cell width)\nnbc = 64  # number of base mesh cells in x\n\n# Define base mesh (domain and finest discretization)\nh = dh * np.ones(nbc)\nmesh = TreeMesh([h, h])\n\n# Define corner points for rectangular box\nxp, yp = np.meshgrid([120.0, 240.0], [80.0, 160.0])\nxy = np.c_[mkvc(xp), mkvc(yp)]  # mkvc creates vectors\n\n# Discretize to finest cell size within rectangular box, with padding in the z direction\n# at the finest and second finest levels.\npadding = [[0, 2], [0, 2]]\nmesh.refine_bounding_box(xy, level=-1, padding_cells_by_level=padding)\n\nmesh.plot_grid(show_it=True)\n\n\n###############################################\n# Intermediate Example and Plotting\n# ---------------------------------\n#\n# The widths of the base mesh cells do not need to be the same in x and y.\n# However the number of base mesh cells in x and y each needs to be a power of 2.\n#\n# Here we show topography-based mesh refinement and refinement about a\n# set of points. We also show some aspect of customizing plots. We use the\n# keyword argument *octree_levels* to define the rate of cell width increase\n# relative to our surface and the set of discrete points about which we are\n# refining.\n#\n\ndx = 5  # minimum cell width (base mesh cell width) in x\ndy = 5  # minimum cell width (base mesh cell width) in y\n\nx_length = 300.0  # domain width in x\ny_length = 300.0  # domain width in y\n\n# Compute number of base mesh cells required in x and y\nnbcx = 2 ** int(np.round(np.log(x_length / dx) / np.log(2.0)))\nnbcy = 2 ** int(np.round(np.log(y_length / dy) / np.log(2.0)))\n\n# Define the base mesh\nhx = [(dx, nbcx)]\nhy = [(dy, nbcy)]\nmesh = TreeMesh([hx, hy], x0=\"CC\")\n\n# Refine surface topography\nxx = mesh.nodes_x\nyy = -3 * np.exp((xx**2) / 100**2) + 50.0\npts = np.c_[mkvc(xx), mkvc(yy)]\npadding = [[0, 2], [0, 2]]\nmesh.refine_surface(pts, padding_cells_by_level=padding, finalize=False)\n\n# Refine mesh near points\nxx = np.array([0.0, 10.0, 0.0, -10.0])\nyy = np.array([-20.0, -10.0, 0.0, -10])\npts = np.c_[mkvc(xx), mkvc(yy)]\nmesh.refine_points(pts, padding_cells_by_level=[2, 2], finalize=False)\n\nmesh.finalize()\n\n# We can apply the plot_grid method and output to a specified axes object\nfig = plt.figure(figsize=(6, 6))\nax = fig.add_subplot(111)\nmesh.plot_grid(ax=ax)\nax.set_xbound(mesh.x0[0], mesh.x0[0] + np.sum(mesh.h[0]))\nax.set_ybound(mesh.x0[1], mesh.x0[1] + np.sum(mesh.h[1]))\nax.set_title(\"QuadTree Mesh\")\n\n####################################################\n# Extracting Mesh Properties\n# --------------------------\n#\n# Once the mesh is created, you may want to extract certain properties. Here,\n# we show some properties that can be extracted from a QuadTree mesh.\n#\n\ndx = 5  # minimum cell width (base mesh cell width) in x\ndy = 5  # minimum cell width (base mesh cell width) in y\n\nx_length = 300.0  # domain width in x\ny_length = 300.0  # domain width in y\n\n# Compute number of base mesh cells required in x and y\nnbcx = 2 ** int(np.round(np.log(x_length / dx) / np.log(2.0)))\nnbcy = 2 ** int(np.round(np.log(y_length / dy) / np.log(2.0)))\n\n# Define the base mesh\nhx = [(dx, nbcx)]\nhy = [(dy, nbcy)]\nmesh = TreeMesh([hx, hy], x0=\"CC\")\n\n# Refine surface topography\nxx = mesh.nodes_x\nyy = -3 * np.exp((xx**2) / 100**2) + 50.0\npts = np.c_[mkvc(xx), mkvc(yy)]\npadding = [[0, 2], [0, 2]]\nmesh.refine_surface(pts, padding_cells_by_level=padding, finalize=False)\n\n# Refine near points\nxx = np.array([0.0, 10.0, 0.0, -10.0])\nyy = np.array([-20.0, -10.0, 0.0, -10])\npts = np.c_[mkvc(xx), mkvc(yy)]\nmesh.refine_points(pts, padding_cells_by_level=[2, 2], finalize=False)\n\nmesh.finalize()\n\n# The bottom west corner\nx0 = mesh.x0\n\n# The total number of cells\nnC = mesh.nC\n\n# An (nC, 2) array containing the cell-center locations\ncc = mesh.gridCC\n\n# A boolean array specifying which cells lie on the boundary\nbInd = mesh.cell_boundary_indices\n\n# The cell areas (2D \"volume\")\ns = mesh.cell_volumes\n\nfig = plt.figure(figsize=(6, 6))\nax = fig.add_subplot(111)\nmesh.plot_image(np.log10(s), grid=True, ax=ax)\nax.set_xbound(mesh.x0[0], mesh.x0[0] + np.sum(mesh.h[0]))\nax.set_ybound(mesh.x0[1], mesh.x0[1] + np.sum(mesh.h[1]))\nax.set_title(\"Log of Cell Areas\")\n\n###############################################\n# 3D Example\n# ----------\n#\n# Here we show how the same approach can be used to create and extract\n# properties from a 3D tree mesh.\n#\n\ndx = 5  # minimum cell width (base mesh cell width) in x\ndy = 5  # minimum cell width (base mesh cell width) in y\ndz = 5  # minimum cell width (base mesh cell width) in z\n\nx_length = 300.0  # domain width in x\ny_length = 300.0  # domain width in y\nz_length = 300.0  # domain width in y\n\n# Compute number of base mesh cells required in x and y\nnbcx = 2 ** int(np.round(np.log(x_length / dx) / np.log(2.0)))\nnbcy = 2 ** int(np.round(np.log(y_length / dy) / np.log(2.0)))\nnbcz = 2 ** int(np.round(np.log(z_length / dz) / np.log(2.0)))\n\n# Define the base mesh\nhx = [(dx, nbcx)]\nhy = [(dy, nbcy)]\nhz = [(dz, nbcz)]\nmesh = TreeMesh([hx, hy, hz], x0=\"CCC\")\n\n# Refine surface topography\n[xx, yy] = np.meshgrid(mesh.nodes_x, mesh.nodes_y)\nzz = -3 * np.exp((xx**2 + yy**2) / 100**2) + 50.0\npts = np.c_[mkvc(xx), mkvc(yy), mkvc(zz)]\npadding = [[0, 0, 2], [0, 0, 2]]\nmesh.refine_surface(pts, padding_cells_by_level=padding, finalize=False)\n\n# Refine box\nxp, yp, zp = np.meshgrid([-40.0, 40.0], [-40.0, 40.0], [-60.0, 0.0])\nxyz = np.c_[mkvc(xp), mkvc(yp), mkvc(zp)]\nmesh.refine_bounding_box(xyz, padding_cells_by_level=padding, finalize=False)\nmesh.finalize()\n\n# The bottom west corner\nx0 = mesh.x0\n\n# The total number of cells\nnC = mesh.nC\n\n# An (nC, 3) array containing the cell-center locations\ncc = mesh.gridCC\n\n# A boolean array specifying which cells lie on the boundary\nbInd = mesh.cell_boundary_indices\n\n# Cell volumes\nv = mesh.cell_volumes\n\nfig = plt.figure(figsize=(6, 6))\nax = fig.add_subplot(111)\nmesh.plot_slice(np.log10(v), normal=\"Y\", ax=ax, ind=int(mesh.h[1].size / 2), grid=True)\nax.set_title(\"Cell Log-Volumes at Y = 0 m\")\n"
  },
  {
    "path": "tutorials/mesh_generation/README.txt",
    "content": "Mesh Generation\n===============\n\n`discretize` provides a numerical grid (or \"mesh\") on which to solve differential\nequations. Each mesh type has a similar API to make working with\ndifferent meshes relatively simple. Within `discretize`, all meshes are\nclasses that have properties like the number of cells `nC`, and methods,\nlike `plotGrid`.\n\nTo learn how to create meshes with `discretize`, we have provided a set\nof tutorials. These tutorials aim to teach the user:\n\n\t- where discrete variables can live on meshes\n\t- how to construct various types of meshes\n\t- how to extract useful properties from mesh objects\n\t- some aspects of plotting and meshes\n\n\n\n"
  },
  {
    "path": "tutorials/operators/1_averaging.py",
    "content": "\"\"\"\nAveraging Matricies\n===================\n\nAveraging matricies are used when a discrete variable living on some part of\nthe mesh (e.g. nodes, centers, edges or faces) must be approximated at other\nlocations. Averaging matricies are sparse and exist for 1D, 2D and\n3D meshes. For each mesh class (*Tensor mesh*, *Tree mesh*,\n*Curvilinear mesh*), the set of averaging matricies are properties that are\nonly constructed when called.\n\nHere we discuss:\n\n    - How to construct and apply averaging matricies\n    - Averaging matricies in 1D, 2D and 3D\n    - Averaging discontinuous functions\n    - The transpose of an averaging matrix\n\n\"\"\"\n\n###############################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial.\n#\n\nfrom discretize import TensorMesh\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# sphinx_gallery_thumbnail_number = 3\n\n\n#############################################\n# 1D Example\n# ----------\n#\n# Here we compute a scalar function on cell nodes and average to cell centers.\n# We then compute the scalar function at cell centers to validate the\n# averaging operator.\n#\n\n# Create a uniform grid\nh = 10 * np.ones(20)\nmesh = TensorMesh([h], \"C\")\n\n# Get node and cell center locations\nx_nodes = mesh.nodes_x\nx_centers = mesh.cell_centers_x\n\n\n# Define a continuous function\ndef fun(x):\n    return np.exp(-(x**2) / 50**2)\n\n\n# Compute function on nodes and cell centers\nv_nodes = fun(x_nodes)\nv_centers = fun(x_centers)\n\n# Create operator and average from nodes to cell centers\nA = mesh.aveN2CC\nv_approx = A * v_nodes\n\n# Compare\nfig = plt.figure(figsize=(12, 4))\nax1 = fig.add_axes([0.03, 0.01, 0.3, 0.91])\nax1.spy(A, markersize=5)\nax1.set_title(\"Sparse representation of A\", pad=10)\n\nax2 = fig.add_axes([0.4, 0.06, 0.55, 0.85])\nax2.plot(\n    x_centers,\n    v_centers,\n    \"b-\",\n    x_centers,\n    v_approx,\n    \"ko\",\n    x_centers,\n    np.c_[v_centers - v_approx],\n    \"r-\",\n)\nax2.set_title(\"Comparison plot\")\nax2.legend((\"evaluated at centers\", \"averaged from nodes\", \"absolute error\"))\n\nfig.show()\n\n#############################################\n# 1D, 2D and 3D Averaging\n# -----------------------\n#\n# Here we discuss averaging operators in 1D, 2D and 3D. In 1D we can\n# average between nodes and cell centers. In higher dimensions, we may need to\n# average between nodes, cell centers, faces and edges. For this example we\n# describe the averaging operator from faces to cell centers in 1D, 2D and 3D.\n#\n\n# Construct uniform meshes in 1D, 2D and 3D\nh = 10 * np.ones(10)\nmesh1D = TensorMesh([h], x0=\"C\")\nmesh2D = TensorMesh([h, h], x0=\"CC\")\nmesh3D = TensorMesh([h, h, h], x0=\"CCC\")\n\n# Create averaging operators\nA1 = mesh1D.aveF2CC  # Averages faces (nodes in 1D) to centers\nA2 = mesh2D.aveF2CC  # Averages from x and y faces to centers\nA3 = mesh3D.aveF2CC  # Averages from x, y and z faces to centers\n\n# Plot sparse representation\nfig = plt.figure(figsize=(7, 8))\nax1 = fig.add_axes([0.37, 0.72, 0.2, 0.2])\nax1.spy(A1, markersize=2.5)\nax1.set_title(\"Faces to centers in 1D\", pad=17)\n\nax2 = fig.add_axes([0.17, 0.42, 0.6, 0.22])\nax2.spy(A2, markersize=1)\nax2.set_title(\"Faces to centers in 2D\", pad=17)\n\nax3 = fig.add_axes([0.05, 0, 0.93, 0.4])\nax3.spy(A3, markersize=0.5)\nax3.set_title(\"Faces to centers in 3D\", pad=17)\n\nfig.show()\n\n# Print some properties\nprint(\"\\n For 1D mesh:\")\nprint(\"- Number of cells:\", str(mesh1D.nC))\nprint(\"- Number of faces:\", str(mesh1D.nF))\nprint(\"- Dimensions of operator:\", str(mesh1D.nC), \"x\", str(mesh1D.nF))\nprint(\"- Number of non-zero elements:\", str(A1.nnz), \"\\n\")\n\nprint(\"For 2D mesh:\")\nprint(\"- Number of cells:\", str(mesh2D.nC))\nprint(\"- Number of faces:\", str(mesh2D.nF))\nprint(\"- Dimensions of operator:\", str(mesh2D.nC), \"x\", str(mesh2D.nF))\nprint(\"- Number of non-zero elements:\", str(A2.nnz), \"\\n\")\n\nprint(\"For 3D mesh:\")\nprint(\"- Number of cells:\", str(mesh3D.nC))\nprint(\"- Number of faces:\", str(mesh3D.nF))\nprint(\"- Dimensions of operator:\", str(mesh3D.nC), \"x\", str(mesh3D.nF))\nprint(\"- Number of non-zero elements:\", str(A3.nnz))\n\n\n######################################################\n# Discontinuous Functions and the Transpose\n# -----------------------------------------\n#\n# Here we show the effects of applying averaging operators to discontinuous\n# functions. We will see that averaging smears the function at\n# discontinuities.\n#\n# The transpose of an averaging operator is also an\n# averaging operator. For example, we can average from cell centers to faces\n# by taking the transpose of operator that averages from faces to cell centers.\n# Note that values on the boundaries are not accurate when applying the\n# transpose as an averaging operator. This is also true for staggered grids.\n#\n\n# Create mesh and obtain averaging operators\nh = 2 * np.ones(50)\nmesh = TensorMesh([h, h], x0=\"CC\")\n\nA2 = mesh.aveCC2F  # cell centers to faces\nA3 = mesh.aveN2CC  # nodes to cell centers\nA4 = mesh.aveF2CC  # faces to cell centers\n\n# Create a variable on cell centers\nv = 100.0 * np.ones(mesh.nC)\nxy = mesh.gridCC\nv[(xy[:, 1] > 0)] = 0.0\nv[(xy[:, 1] < -10.0) & (xy[:, 0] > -10.0) & (xy[:, 0] < 10.0)] = 50.0\n\nfig = plt.figure(figsize=(10, 10))\nax1 = fig.add_subplot(221)\nmesh.plot_image(v, ax=ax1)\nax1.set_title(\"Variable at cell centers\")\n\n# Apply cell centers to faces averaging\nax2 = fig.add_subplot(222)\nmesh.plot_image(A2 * v, ax=ax2, v_type=\"F\")\nax2.set_title(\"Cell centers to faces\")\n\n# Use the transpose to go from cell centers to nodes\nax3 = fig.add_subplot(223)\nmesh.plot_image(A3.T * v, ax=ax3, v_type=\"N\")\nax3.set_title(\"Cell centers to nodes using transpose\")\n\n# Use the transpose to go from cell centers to faces\nax4 = fig.add_subplot(224)\nmesh.plot_image(A4.T * v, ax=ax4, v_type=\"F\")\nax4.set_title(\"Cell centers to faces using transpose\")\n\nfig.show()\n"
  },
  {
    "path": "tutorials/operators/2_differential.py",
    "content": "r\"\"\"\nDifferential Operators\n======================\n\nFor discretized quantities living on a mesh, sparse matricies can be used to\napproximate the following differential operators:\n\n    - gradient: :math:`\\nabla \\phi`\n    - divergence: :math:`\\nabla \\cdot \\mathbf{v}`\n    - curl: :math:`\\nabla \\times \\mathbf{v}`\n    - scalar Laplacian: :math:`\\Delta \\mathbf{v}`\n\nNumerical differential operators exist for 1D, 2D and 3D meshes. For each mesh\nclass (*Tensor mesh*, *Tree mesh*, *Curvilinear mesh*), the set of numerical\ndifferential operators are properties that are only constructed when called.\n\nHere we demonstrate:\n\n    - How to construct and apply numerical differential operators\n    - Mapping and dimensions\n    - Applications for the transpose\n\n\n\"\"\"\n\n###############################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial.\n#\n\nfrom discretize import TensorMesh, TreeMesh\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# sphinx_gallery_thumbnail_number = 2\n\n\n#############################################\n# 1D Example\n# ----------\n#\n# Here we compute a scalar function on cell nodes and differentiate with\n# respect to x. We then compute the analytic derivative of function to validate\n# the numerical differentiation.\n#\n\n# Create a uniform grid\nh = np.ones(20)\nmesh = TensorMesh([h], \"C\")\n\n# Get node and cell center locations\nx_nodes = mesh.nodes_x\nx_centers = mesh.cell_centers_x\n\n# Compute function on nodes and derivative at cell centers\nv = np.exp(-(x_nodes**2) / 4**2)\ndvdx = -(2 * x_centers / 4**2) * np.exp(-(x_centers**2) / 4**2)\n\n# Derivative in x (gradient in 1D) from nodes to cell centers\nG = mesh.nodal_gradient\ndvdx_approx = G * v\n\n# Compare\nfig = plt.figure(figsize=(12, 4))\nax1 = fig.add_axes([0.03, 0.01, 0.3, 0.89])\nax1.spy(G, markersize=5)\nax1.set_title(\"Sparse representation of G\", pad=10)\n\nax2 = fig.add_axes([0.4, 0.06, 0.55, 0.85])\nax2.plot(x_nodes, v, \"b-\", x_centers, dvdx, \"r-\", x_centers, dvdx_approx, \"ko\")\nax2.set_title(\"Comparison plot\")\nax2.legend((\"function\", \"analytic derivative\", \"numeric derivative\"))\n\nfig.show()\n\n\n#############################################\n# Mapping and Dimensions\n# ----------------------\n#\n# When discretizing and solving differential equations, it is\n# natural for certain quantities to be defined at particular locations on the\n# mesh; e.g.:\n#\n#    - Scalar quantities on nodes or at cell centers\n#    - Vector quantities on cell edges or on cell faces\n#\n# As such, numerical differential operators frequently map from one part of\n# the mesh to another. For example, the gradient acts on a scalar quantity\n# an results in a vector quantity. As a result, the numerical gradient\n# operator may map from nodes to edges or from cell centers to faces.\n#\n# Here we explore the dimensions of the gradient, divergence and curl\n# operators for a 3D tensor mesh. This can be extended to other mesh types.\n#\n\n# Create a uniform grid\nh = np.ones(20)\nmesh = TensorMesh([h, h, h], \"CCC\")\n\n# Get differential operators\nGRAD = mesh.nodal_gradient  # Gradient from nodes to edges\nDIV = mesh.face_divergence  # Divergence from faces to cell centers\nCURL = mesh.edge_curl  # Curl edges to cell centers\n\n\nfig = plt.figure(figsize=(9, 8))\n\nax1 = fig.add_axes([0.07, 0, 0.20, 0.7])\nax1.spy(GRAD, markersize=0.5)\nax1.set_title(\"Gradient (nodes to edges)\")\n\nax2 = fig.add_axes([0.345, 0.73, 0.59, 0.185])\nax2.spy(DIV, markersize=0.5)\nax2.set_title(\"Divergence (faces to centers)\", pad=20)\n\nax3 = fig.add_axes([0.31, 0.05, 0.67, 0.60])\nax3.spy(CURL, markersize=0.5)\nax3.set_title(\"Curl (edges to faces)\")\n\nfig.show()\n\n# Print some properties\nprint(\"\\n Gradient:\")\nprint(\"- Number of nodes:\", str(mesh.nN))\nprint(\"- Number of edges:\", str(mesh.nE))\nprint(\"- Dimensions of operator:\", str(mesh.nE), \"x\", str(mesh.nN))\nprint(\"- Number of non-zero elements:\", str(GRAD.nnz), \"\\n\")\n\nprint(\"Divergence:\")\nprint(\"- Number of faces:\", str(mesh.nF))\nprint(\"- Number of cells:\", str(mesh.nC))\nprint(\"- Dimensions of operator:\", str(mesh.nC), \"x\", str(mesh.nF))\nprint(\"- Number of non-zero elements:\", str(DIV.nnz), \"\\n\")\n\nprint(\"Curl:\")\nprint(\"- Number of faces:\", str(mesh.nF))\nprint(\"- Number of edges:\", str(mesh.nE))\nprint(\"- Dimensions of operator:\", str(mesh.nE), \"x\", str(mesh.nF))\nprint(\"- Number of non-zero elements:\", str(CURL.nnz))\n\n\n#############################################\n# 2D Example\n# ----------\n#\n# Here we apply the gradient, divergence and curl operators to a set of\n# functions defined on a 2D tensor mesh. We then plot the results.\n#\n\n# Create a uniform grid\nh = np.ones(20)\nmesh = TensorMesh([h, h], \"CC\")\n\n# Get differential operators\nGRAD = mesh.nodal_gradient  # Gradient from nodes to edges\nDIV = mesh.face_divergence  # Divergence from faces to cell centers\nCURL = mesh.edge_curl  # Curl edges to cell centers (goes to faces in 3D)\n\n# Evaluate gradient of a scalar function\nnodes = mesh.gridN\nu = np.exp(-(nodes[:, 0] ** 2 + nodes[:, 1] ** 2) / 4**2)\ngrad_u = GRAD * u\n\n# Evaluate divergence of a vector function in x and y\nfaces_x = mesh.gridFx\nfaces_y = mesh.gridFy\n\nvx = (faces_x[:, 0] / np.sqrt(np.sum(faces_x**2, axis=1))) * np.exp(\n    -(faces_x[:, 0] ** 2 + faces_x[:, 1] ** 2) / 6**2\n)\n\nvy = (faces_y[:, 1] / np.sqrt(np.sum(faces_y**2, axis=1))) * np.exp(\n    -(faces_y[:, 0] ** 2 + faces_y[:, 1] ** 2) / 6**2\n)\n\nv = np.r_[vx, vy]\ndiv_v = DIV * v\n\n# Evaluate curl of a vector function in x and y\nedges_x = mesh.gridEx\nedges_y = mesh.gridEy\n\nwx = (-edges_x[:, 1] / np.sqrt(np.sum(edges_x**2, axis=1))) * np.exp(\n    -(edges_x[:, 0] ** 2 + edges_x[:, 1] ** 2) / 6**2\n)\n\nwy = (edges_y[:, 0] / np.sqrt(np.sum(edges_y**2, axis=1))) * np.exp(\n    -(edges_y[:, 0] ** 2 + edges_y[:, 1] ** 2) / 6**2\n)\n\nw = np.r_[wx, wy]\ncurl_w = CURL * w\n\n# Plot Gradient of u\nfig = plt.figure(figsize=(10, 5))\n\nax1 = fig.add_subplot(121)\nmesh.plot_image(u, ax=ax1, v_type=\"N\")\nax1.set_title(\"u at cell centers\")\n\nax2 = fig.add_subplot(122)\nmesh.plot_image(\n    grad_u, ax=ax2, v_type=\"E\", view=\"vec\", stream_opts={\"color\": \"w\", \"density\": 1.0}\n)\nax2.set_title(\"gradient of u on edges\")\n\nfig.show()\n\n# Plot divergence of v\nfig = plt.figure(figsize=(10, 5))\n\nax1 = fig.add_subplot(121)\nmesh.plot_image(\n    v, ax=ax1, v_type=\"F\", view=\"vec\", stream_opts={\"color\": \"w\", \"density\": 1.0}\n)\nax1.set_title(\"v at cell faces\")\n\nax2 = fig.add_subplot(122)\nmesh.plot_image(div_v, ax=ax2)\nax2.set_title(\"divergence of v at cell centers\")\n\nfig.show()\n\n# Plot curl of w\nfig = plt.figure(figsize=(10, 5))\n\nax1 = fig.add_subplot(121)\nmesh.plot_image(\n    w, ax=ax1, v_type=\"E\", view=\"vec\", stream_opts={\"color\": \"w\", \"density\": 1.0}\n)\nax1.set_title(\"w at cell edges\")\n\nax2 = fig.add_subplot(122)\nmesh.plot_image(curl_w, ax=ax2)\nax2.set_title(\"curl of w at cell centers\")\n\nfig.show()\n\n#########################################################\n# Tree Mesh Divergence\n# --------------------\n#\n# For a tree mesh, there needs to be special attention taken for the hanging\n# faces to achieve second order convergence for the divergence operator.\n# Although the divergence cannot be constructed through Kronecker product\n# operations, the initial steps are exactly the same for calculating the\n# stencil, volumes, and areas. This yields a divergence defined for every\n# cell in the mesh using all faces. There is, however, redundant information\n# when hanging faces are included.\n#\n\nmesh = TreeMesh([[(1, 16)], [(1, 16)]], levels=4)\nmesh.insert_cells(np.array([5.0, 5.0]), np.array([3]))\nmesh.number()\n\nfig = plt.figure(figsize=(10, 10))\n\nax1 = fig.add_subplot(211)\n\nmesh.plot_grid(centers=True, nodes=False, ax=ax1)\nax1.axis(\"off\")\nax1.set_title(\"Simple QuadTree Mesh\")\nax1.set_xlim([-1, 17])\nax1.set_ylim([-1, 17])\n\nfor ii, loc in zip(range(mesh.nC), mesh.gridCC):\n    ax1.text(loc[0] + 0.2, loc[1], \"{0:d}\".format(ii), color=\"r\")\n\nax1.plot(mesh.gridFx[:, 0], mesh.gridFx[:, 1], \"g>\")\nfor ii, loc in zip(range(mesh.nFx), mesh.gridFx):\n    ax1.text(loc[0] + 0.2, loc[1], \"{0:d}\".format(ii), color=\"g\")\n\nax1.plot(mesh.gridFy[:, 0], mesh.gridFy[:, 1], \"m^\")\nfor ii, loc in zip(range(mesh.nFy), mesh.gridFy):\n    ax1.text(loc[0] + 0.2, loc[1] + 0.2, \"{0:d}\".format((ii + mesh.nFx)), color=\"m\")\n\nax2 = fig.add_subplot(212)\nax2.spy(mesh.face_divergence)\nax2.set_title(\"Face Divergence\")\nax2.set_ylabel(\"Cell Number\")\nax2.set_xlabel(\"Face Number\")\n\n\n#########################################################\n# Vector Calculus Identities\n# --------------------------\n#\n# Here we show that vector calculus identities hold for the discrete\n# differential operators. Namely that for a scalar quantity :math:`\\phi` and\n# a vector quantity :math:`\\mathbf{v}`:\n#\n# .. math::\n#     \\begin{align}\n#     &\\nabla \\times (\\nabla \\phi ) = 0 \\\\\n#     &\\nabla \\cdot (\\nabla \\times \\mathbf{v}) = 0\n#     \\end{align}\n#\n#\n# We do this by computing the CURL*GRAD and DIV*CURL matricies. We then\n# plot the sparse representations and show neither contain any non-zero\n# entries; **e.g. each is just a matrix of zeros**.\n#\n\n# Create a mesh\nh = 5 * np.ones(20)\nmesh = TensorMesh([h, h, h], \"CCC\")\n\n# Get operators\nGRAD = mesh.nodal_gradient  # nodes to edges\nDIV = mesh.face_divergence  # faces to centers\nCURL = mesh.edge_curl  # edges to faces\n\n# Plot\nfig = plt.figure(figsize=(11, 7))\n\nax1 = fig.add_axes([0.12, 0.1, 0.2, 0.8])\nax1.spy(CURL * GRAD, markersize=0.5)\nax1.set_title(\"CURL*GRAD\")\n\nax2 = fig.add_axes([0.35, 0.64, 0.6, 0.25])\nax2.spy(DIV * CURL, markersize=0.5)\nax2.set_title(\"DIV*CURL\", pad=20)\n"
  },
  {
    "path": "tutorials/operators/README.txt",
    "content": "Operators\n=========\n\nNumerical solutions to differential equations using the finite volume\nmethod require discrete operators. These include averaging operators\nand differential operators. Averaging operators are used when a\nvariable living on some part of the mesh (e.g. nodes, centers, edges or\nfaces) must be approximated at other locations. Differential operators\ninclude the gradient, divergence, curl and scalar Laplacian.\n\nThe discrete operators are properties of each mesh class (*tensor mesh*,\n*tree mesh*, *curvilinear mesh*). An operator is only constructed when\ncalled. Since each mesh type has a similar API, the operators can be\ncalled using the same syntax.\n\nTo learn about discrete operators, we have provided a set\nof tutorials. These tutorials aim to teach the user:\n\n\t- how to construct averaging and differential operators from a mesh\n\t- how to apply the discrete operators to discrete variables\n\t- how to impose boundary conditions using differential operators\n\t- how discrete differential operators preserve vector calculus identities\n\n\n\n"
  },
  {
    "path": "tutorials/pde/1_poisson.py",
    "content": "r\"\"\"\nGauss' Law of Electrostatics\n============================\n\nHere we use the discretize package to solve for the electric potential\n(:math:`\\phi`) and electric fields (:math:`\\mathbf{e}`) in 2D that result from\na static charge distribution. Starting with Gauss' law and Faraday's law:\n\n.. math::\n    &\\nabla \\cdot \\mathbf{E} = \\frac{\\rho}{\\epsilon_0} \\\\\n    &\\nabla \\times \\mathbf{E} = \\mathbf{0} \\;\\;\\; \\Rightarrow \\;\\;\\; \\mathbf{E} = -\\nabla \\phi \\\\\n    &\\textrm{s.t.} \\;\\;\\; \\phi \\Big |_{\\partial \\Omega} = 0\n\nwhere :math:`\\sigma` is the charge density and :math:`\\epsilon_0` is the\npermittivity of free space. We will consider the case where there is both a\npositive and a negative charge of equal magnitude within our domain. Thus:\n\n.. math::\n    \\rho = \\rho_0 \\big [ \\delta ( \\mathbf{r_+}) - \\delta (\\mathbf{r_-} ) \\big ]\n\nTo solve this problem numerically, we use the weak formulation; that is, we\ntake the inner product of each equation with an appropriate test function.\nWhere :math:`\\psi` is a scalar test function and :math:`\\mathbf{f}` is a\nvector test function:\n\n.. math::\n    \\int_\\Omega \\psi (\\nabla \\cdot \\mathbf{E}) dV = \\frac{1}{\\epsilon_0} \\int_\\Omega \\psi \\rho dV \\\\\n    \\int_\\Omega \\mathbf{f \\cdot E} \\, dV = - \\int_\\Omega \\mathbf{f} \\cdot (\\nabla \\phi ) dV\n\n\nIn the case of Gauss' law, we have a volume integral containing the Dirac delta\nfunction, thus:\n\n.. math::\n    \\int_\\Omega \\psi (\\nabla \\cdot \\mathbf{E}) dV = \\frac{1}{\\epsilon_0} \\psi \\, q\n\nwhere :math:`q` represents an integrated charge density. By applying the finite\nvolume approach to this expression we obtain:\n\n.. math::\n    \\mathbf{\\psi^T M_c D e} = \\frac{1}{\\epsilon_0} \\mathbf{\\psi^T q}\n\nwhere :math:`\\mathbf{q}` denotes the total enclosed charge for each cell. Thus\n:math:`\\mathbf{q_i}=\\rho_0` for the cell containing the positive charge and\n:math:`\\mathbf{q_i}=-\\rho_0` for the cell containing the negative charge. It\nis zero for every other cell.\n\n:math:`\\mathbf{\\psi}` and :math:`\\mathbf{q}` live at cell centers and\n:math:`\\mathbf{e}` lives on cell faces. :math:`\\mathbf{D}` is the discrete\ndivergence operator. :math:`\\mathbf{M_c}` is an inner product matrix for cell\ncentered quantities.\n\nFor the second weak form equation, we make use of the divergence theorem as\nfollows:\n\n.. math::\n    \\int_\\Omega \\mathbf{f \\cdot E} \\, dV &= - \\int_\\Omega \\mathbf{f} \\cdot (\\nabla \\phi ) dV \\\\\n    & = - \\frac{1}{\\epsilon_0} \\int_\\Omega \\nabla \\cdot (\\mathbf{f} \\phi ) dV\n    + \\frac{1}{\\epsilon_0} \\int_\\Omega ( \\nabla \\cdot \\mathbf{f} ) \\phi \\, dV \\\\\n    & = - \\frac{1}{\\epsilon_0} \\int_{\\partial \\Omega} \\mathbf{n} \\cdot (\\mathbf{f} \\phi ) da\n    + \\frac{1}{\\epsilon_0} \\int_\\Omega ( \\nabla \\cdot \\mathbf{f} ) \\phi \\, dV \\\\\n    & = 0 + \\frac{1}{\\epsilon_0} \\int_\\Omega ( \\nabla \\cdot \\mathbf{f} ) \\phi \\, dV\n\nwhere the surface integral is zero due to the boundary conditions we imposed.\nEvaluating this expression according to the finite volume approach we obtain:\n\n.. math::\n    \\mathbf{f^T M_f e} = \\mathbf{f^T D^T M_c \\phi}\n\nwhere :math:`\\mathbf{f}` lives on cell faces and :math:`\\mathbf{M_f}` is the\ninner product matrix for quantities that live on cell faces. By canceling terms\nand combining the set of discrete equations we obtain:\n\n.. math::\n    \\big [ \\mathbf{M_c D M_f^{-1} D^T M_c} \\big ] \\mathbf{\\phi} = \\frac{1}{\\epsilon_0} \\mathbf{q}\n\nfrom which we can solve for :math:`\\mathbf{\\phi}`. The electric field can be\nobtained by computing:\n\n.. math::\n    \\mathbf{e} = \\mathbf{M_f^{-1} D^T M_c \\phi}\n\n\"\"\"\n\n###############################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial.\n#\n\n\nfrom discretize import TensorMesh\nfrom scipy.sparse.linalg import spsolve\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom discretize.utils import sdiag\n\n\n###############################################\n#\n# Solving the Problem\n# -------------------\n#\n\n# Create a tensor mesh\nh = np.ones(75)\nmesh = TensorMesh([h, h], \"CC\")\n\n# Create system\nDIV = mesh.face_divergence  # Faces to cell centers divergence\nMf_inv = mesh.get_face_inner_product(invert_matrix=True)\nMc = sdiag(mesh.cell_volumes)\nA = Mc * DIV * Mf_inv * DIV.T * Mc\n\n# Define RHS (charge distributions at cell centers)\nxycc = mesh.gridCC\nkneg = (xycc[:, 0] == -10) & (xycc[:, 1] == 0)  # -ve charge distr. at (-10, 0)\nkpos = (xycc[:, 0] == 10) & (xycc[:, 1] == 0)  # +ve charge distr. at (10, 0)\n\nrho = np.zeros(mesh.nC)\nrho[kneg] = -1\nrho[kpos] = 1\n\n# LU factorization and solve\nphi = spsolve(A, rho)\n\n# Compute electric fields\nE = Mf_inv * DIV.T * Mc * phi\n\n# Plotting\nfig = plt.figure(figsize=(14, 4))\n\nax1 = fig.add_subplot(131)\nmesh.plot_image(rho, v_type=\"CC\", ax=ax1)\nax1.set_title(\"Charge Density\")\n\nax2 = fig.add_subplot(132)\nmesh.plot_image(phi, v_type=\"CC\", ax=ax2)\nax2.set_title(\"Electric Potential\")\n\nax3 = fig.add_subplot(133)\nmesh.plot_image(\n    E, ax=ax3, v_type=\"F\", view=\"vec\", stream_opts={\"color\": \"w\", \"density\": 1.0}\n)\nax3.set_title(\"Electric Fields\")\n"
  },
  {
    "path": "tutorials/pde/2_advection_diffusion.py",
    "content": "r\"\"\"\nAdvection-Diffusion Equation\n============================\n\nHere we use the discretize package to model the advection-diffusion\nequation. The goal of this tutorial is to demonstrate:\n\n    - How to solve time-dependent PDEs\n    - How to apply Neumann boundary conditions\n    - Strategies for applying finite volume to 2nd order PDEs\n\n\nDerivation\n----------\n\nIf we assume the fluid is incompressible (:math:`\\nabla \\cdot \\mathbf{u} = 0`),\nthe advection-diffusion equation with Neumann boundary conditions is given by:\n\n.. math::\n    p_t = \\nabla \\cdot \\alpha \\nabla p\n    - \\mathbf{u} \\cdot \\nabla p + s \\\\\n    \\textrm{s.t.} \\;\\;\\; \\frac{\\partial p}{\\partial n} \\Bigg |_{\\partial \\Omega} = 0\n\nwhere :math:`p` is the unknown variable, :math:`\\alpha` defines the\ndiffusivity within the domain, :math:`\\mathbf{u}` is the velocity field, and\n:math:`s` is the source term. We will consider the case where there is a single\npoint source within our domain. Thus:\n\n.. math::\n    s = s_0 \\delta ( \\mathbf{r} )\n\nwhere :math:`s_0` is a constant. To solve this problem numerically, we\nre-express the advection-diffusion equation as a set of first order PDEs:\n\n.. math::\n    \\; \\; p_t = \\nabla \\cdot \\mathbf{j} - \\mathbf{u} \\cdot \\mathbf{w} + s \\;\\;\\; (1)\\\\\n    \\; \\; \\mathbf{w} = \\nabla p \\;\\;\\; (2) \\\\\n    \\; \\; \\alpha^{-1} \\mathbf{j} = \\mathbf{w} \\;\\;\\; (3)\n\n\nWe then apply the weak formulation; that is, we\ntake the inner product of each equation with an appropriate test function.\n\n**Expression 1:**\n\nLet :math:`\\psi` be a scalar test function. By taking the inner product with\nexpression (1) we obtain:\n\n.. math::\n    \\int_\\Omega \\psi \\, p_t \\, dv =\n    \\int_\\Omega \\psi \\, (\\nabla \\cdot \\mathbf{j}) \\, dv\n    - \\int_\\Omega \\psi \\, \\big ( \\mathbf{u} \\cdot \\mathbf{w} \\big ) \\, dv\n    + s_0 \\int_\\Omega \\psi \\, \\delta (\\mathbf{r}) \\, dv\n\nThe source term is a volume integral containing the Dirac delta function, thus:\n\n.. math::\n    s_0 \\int_\\Omega \\psi \\, \\delta (\\mathbf{r}) \\, dv \\approx \\mathbf{\\psi^T \\, q}\n\nwhere :math:`q=s_0` for the cell containing the point source and zero everywhere\nelse. By evaluating the inner products according to the finite volume approach\nwe obtain:\n\n.. math::\n    \\mathbf{\\psi^T M_c p_t} = \\mathbf{\\psi^T M_c D \\, j}\n    - \\mathbf{\\psi^T M_c A_{fc}} \\, \\textrm{diag} ( \\mathbf{u} ) \\mathbf{w}\n    + \\mathbf{\\psi^T q}\n\nwhere :math:`\\mathbf{\\psi}`, :math:`\\mathbf{p}` and :math:`\\mathbf{p_t}`\nlive at cell centers and :math:`\\mathbf{j}`, :math:`\\mathbf{u}` and\n:math:`\\mathbf{w}` live on cell faces. :math:`\\mathbf{D}`\nis a discrete divergence operator. :math:`\\mathbf{M_c}` is the cell center\ninner product matrix. :math:`\\mathbf{A_{fc}}` takes the dot product of\n:math:`\\mathbf{u}` and :math:`\\mathbf{w}`, projects it to cell centers and sums\nthe contributions by each Cartesian component.\n\n**Expression 2:**\n\nLet :math:`\\mathbf{f}` be a vector test function. By taking the inner product\nwith expression (2) we obtain:\n\n.. math::\n    \\int_\\Omega \\mathbf{f \\cdot w} \\, dv =\n    \\int_\\Omega \\mathbf{f \\cdot \\nabla}p \\, dv\n\nIf we use the identity\n:math:`\\phi \\nabla \\cdot \\mathbf{a} = \\nabla \\cdot (\\phi \\mathbf{a}) - \\mathbf{a} \\cdot (\\nabla \\phi )`\nand apply the divergence theorem we obtain:\n\n.. math::\n    \\int_\\Omega \\mathbf{f \\cdot w} \\, dv =\n    \\int_{\\partial \\Omega} \\mathbf{n \\, \\cdot} \\, p \\mathbf{f} \\, da\n    - \\int_{\\Omega} p \\cdot \\nabla \\mathbf{f} \\, dv\n\nIf we assume that :math:`f=0` on the boundary, we can eliminate the surface\nintegral. By evaluating the inner products in the weak formulation according\nto the finite volume approach we obtain:\n\n.. math::\n    \\mathbf{f^T M_f w} = - \\mathbf{f^T D^T M_c}p\n\nwhere :math:`\\mathbf{f}` lives at cell faces and :math:`\\mathbf{M_f}` is\nthe face inner product matrix.\n\n**Expression 3:**\n\nLet :math:`\\mathbf{f}` be a vector test function. By taking the inner product\nwith expression (3) we obtain:\n\n.. math::\n    \\int_\\Omega \\mathbf{f} \\cdot \\alpha^{-1} \\mathbf{j} \\, dv =\n    \\int_\\Omega \\mathbf{f} \\cdot \\mathbf{w} \\, dv\n\nBy evaluating the inner products according to the finite volume approach\nwe obtain:\n\n.. math::\n    \\mathbf{f^T M_\\alpha \\, j} = \\mathbf{f^T M_f \\, w}\n\nwhere :math:`\\mathbf{M_\\alpha}` is a face inner product matrix that\ndepends on the inverse of the diffusivity.\n\n**Final Numerical System:**\n\nBy combining the set of discrete expressions and letting\n:math:`\\mathbf{s} = \\mathbf{M_c^{-1} q}`, we obtain:\n\n.. math::\n    \\mathbf{p_t} =\n    - \\mathbf{D M_\\alpha^{-1} \\, D^T \\, M_c} \\, p\n    + \\mathbf{A_{fc}} \\, \\textrm{diag} ( \\mathbf{u} ) \\mathbf{M_f^{-1} D^T \\, M_c} p\n    + \\mathbf{s}\n\nSince the Neumann boundary condition is being used for the variable :math:`p`,\nthe transpose of the divergence operator is the negative of the gradient\noperator with Neumann boundary conditions; e.g. :math:`\\mathbf{D^T = -G}`. Thus:\n\n.. math::\n    \\mathbf{p_t} = - \\mathbf{M} \\mathbf{p} + \\mathbf{s}\n\nwhere\n\n.. math::\n    \\mathbf{M} = - \\mathbf{D M_\\alpha^{-1} \\, G \\, M_c} \\, p\n    + \\mathbf{A_{fc}} \\, \\textrm{diag} ( \\mathbf{u} ) \\mathbf{M_f^{-1} G \\, M_c} p\n\nFor the example, we will discretize in time using backward Euler. This results\nin the following system which must be solve at every time step :math:`k`.\nWhere :math:`\\Delta t` is the step size:\n\n.. math::\n    \\big [ \\mathbf{I} + \\Delta t \\, \\mathbf{M} \\big ] \\mathbf{p}^{k+1} =\n    \\mathbf{p}^k + \\Delta t \\, \\mathbf{s}\n\n\n\"\"\"\n\n###################################################\n#\n# Import Packages\n# ---------------\n#\n# Here we import the packages required for this tutorial.\n#\n\nfrom discretize import TensorMesh\nfrom scipy.sparse.linalg import splu\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport numpy as np\nfrom discretize.utils import sdiag, mkvc\n\n###############################################\n#\n# Solving the Problem\n# -------------------\n#\n\n# Create a tensor mesh\nh = np.ones(75)\nmesh = TensorMesh([h, h], \"CC\")\n\n# Define a divergence free vector field on faces\nfaces_x = mesh.gridFx\nfaces_y = mesh.gridFy\n\nr_x = np.sqrt(np.sum(faces_x**2, axis=1))\nr_y = np.sqrt(np.sum(faces_y**2, axis=1))\n\nux = 0.5 * (-faces_x[:, 1] / r_x) * (1 + np.tanh(0.15 * (28.0 - r_x)))\nuy = 0.5 * (faces_y[:, 0] / r_y) * (1 + np.tanh(0.15 * (28.0 - r_y)))\n\nu = 10.0 * np.r_[ux, uy]  # Maximum velocity is 10 m/s\n\n# Define vector q where s0 = 1 in our analytic source term\nxycc = mesh.gridCC\nk = (xycc[:, 0] == 0) & (xycc[:, 1] == -15)  # source at (0, -15)\n\nq = np.zeros(mesh.nC)\nq[k] = 1\n\n# Define diffusivity within each cell\na = mkvc(8.0 * np.ones(mesh.nC))\n\n# Define the matrix M\nAfc = mesh.dim * mesh.aveF2CC  # modified averaging operator to sum dot product\nMf_inv = mesh.get_face_inner_product(invert_matrix=True)\nMc = sdiag(mesh.cell_volumes)\nMc_inv = sdiag(1 / mesh.cell_volumes)\nMf_alpha_inv = mesh.get_face_inner_product(a, invert_model=True, invert_matrix=True)\n\nmesh.set_cell_gradient_BC([\"neumann\", \"neumann\"])  # Set Neumann BC\nG = mesh.cell_gradient\nD = mesh.face_divergence\n\nM = -D * Mf_alpha_inv * G * Mc + Afc * sdiag(u) * Mf_inv * G * Mc\n\n\n# Set time stepping, initial conditions and final matricies\ndt = 0.02  # Step width\np = np.zeros(mesh.nC)  # Initial conditions p(t=0)=0\n\nI = sdiag(np.ones(mesh.nC))  # Identity matrix\nB = I + dt * M\ns = Mc_inv * q\n\nBinv = splu(B)\n\n\n# Plot the vector field\nfig = plt.figure(figsize=(15, 15))\nax = 9 * [None]\n\nax[0] = fig.add_subplot(332)\nmesh.plot_image(\n    u,\n    ax=ax[0],\n    v_type=\"F\",\n    view=\"vec\",\n    stream_opts={\"color\": \"w\", \"density\": 1.0},\n    clim=[0.0, 10.0],\n)\nax[0].set_title(\"Divergence free vector field\")\n\nax[1] = fig.add_subplot(333)\nax[1].set_aspect(10, anchor=\"W\")\ncbar = mpl.colorbar.ColorbarBase(ax[1], orientation=\"vertical\")\ncbar.set_label(\"Velocity (m/s)\", rotation=270, labelpad=5)\n\n# Perform backward Euler and plot\n\nn = 3\n\nfor ii in range(300):\n    p = Binv.solve(p + s)\n\n    if ii + 1 in (1, 25, 50, 100, 200, 300):\n        ax[n] = fig.add_subplot(3, 3, n + 1)\n        mesh.plot_image(p, v_type=\"CC\", ax=ax[n], pcolor_opts={\"cmap\": \"gist_heat_r\"})\n        title_str = \"p at t = \" + str((ii + 1) * dt) + \" s\"\n        ax[n].set_title(title_str)\n        n = n + 1\n"
  },
  {
    "path": "tutorials/pde/3_nodal_dirichlet_poisson.py",
    "content": "r\"\"\"\nNodal Dirichlet Poisson solution\n================================\nIn this example, we demonstrate how to solve a Poisson equation where the\nsolution lives on nodes, and we want to impose Dirichlet conditions on the\nboundary.\n\nSolve a nodal Poisson's equation with Dirichlet boundary conditions\n-------------------------------------------------------------------\nThe PDE we want to solve is Poisson's equation with a variable\nconductivity, where we know the value of the solution on the boundary.\n\n.. math::\n    - \\nabla \\cdot \\sigma \\nabla u = f \\\\\n    u = q \\textrm{ on } \\partial\\Omega\n\nWe express this in a weak form, where we multiply by an arbitrary test function\nand integrate over the volume:\n\n.. math::\n    \\left<w,\\nabla \\cdot -\\sigma \\nabla u \\right> = \\left<w,f \\right>\n\nThe left hand side can be changed to avoid taking the divergence of\n:math:`-\\sigma \\nabla u` using a higher dimension integration by parts, instead\ntransfering the differentiability to :math:`w`.\n\n.. math::\n    \\left<\\nabla w,\\sigma \\nabla u \\right>\n    - \\int_{\\partial\\Omega}w \\left(\\sigma \\nabla u \\right) \\cdot \\hat{n} dA\n    = \\left<w,f\\right>\n\nWe are interested in solving this where :math:`u`, :math:`f`, and :math:`w` are\ndefined on cell nodes, :math:`\\sigma` is defined on cell centers. Also of note\nis that in the finite volume formulation we approximate the solution as\nintegrals of averages over each cell and the test functions and basis functions\nare piecewise constant. Thus our discrete form goes to:\n\n.. math::\n    w^T G^T M_{e,\\sigma} G u - w_b^T b_c = w^T M_n f\n\nwith :math:`b_c` representing the boundary condition on\n:math:`\\left(\\sigma \\nabla u \\right) \\cdot \\hat{n}`. Where :math:`G` is a nodal\ngradient operator that estimates the gradient at the edge centers,\n:math:`M_{e, \\sigma}` represents the inner product of vectors that live on mesh\nedges taken with respect to :math:`\\sigma`, and :math:`M_{n}` is\ncorrespondingly the inner product operator for scalars that live on mesh nodes.\n\nTaking care of the boundaries\n-----------------------------\n\nSet :math:`u_b`, and :math:`w_b` as the values of :math:`u` and :math:`w` on\nthe boundary, and :math:`u_f` and :math:`w_f` the values of :math:`u` and\n:math:`w` on the interior. We have that\n\n.. math::\n    w = P_f w_f + P_b w_b \\\\\n    u = P_f u_f + P_b u_b\n\nWhere :math:`P_b` and :math:`P_f` are the matrices which project the values\non the boundary and free interior nodes, respectively, to all of the nodes.\n\nThere is much freedom in what we choose to be our test functions, and to avoid\nthe integral on the boundary without assuming a value for\n:math:`\\left(\\sigma \\nabla u \\right) \\cdot \\hat{n}`, we set that our test\nfunctions are zero on the boundary, :math:`w_b = 0`\n\nThus the full system is\n\n.. math::\n    w_f^T P_f^T G^T M_{e,\\sigma} G (P_f u_f + P_b u_b) = w_f^T P_f^T M_n f\n\nOur final system of equations\n-----------------------------\n\nThe above scalar equality must be true for all vectors :math:`w`, thus the\nvectors are also equal:\n\n.. math::\n    P_f^T G^T M_{e,\\sigma} G (P_f u_f + P_b u_b) = P_f^T M_n f\n\nWe then collect the unknowns :math:`u_f` on the left hand side, and move\neverything else to the right.\n\n.. math::\n    P_f^T G^T M_{e,\\sigma} G P_f u_f = P_f^T M_n f\n    - P_f^T G^T M_{e,\\sigma} G P_b u_b\n\nNow lets actually code this solution up using these matrices!\n\"\"\"\n\nimport numpy as np\nimport discretize\nimport matplotlib.pyplot as plt\nimport scipy.sparse as sp\nfrom discretize import tests\n\nimport sympy\nimport sympy.vector as spv\nfrom sympy.utilities.lambdify import lambdify\n\n# %%\n# Our PDE\n# -------\n# We are going to setup a simple two-dimensional problem which we will solve on\n# a `TensorMesh`. We want to test the accuracy of our discretization scheme for\n# the problem (it should be second order accurate). Thus we define a known\n# solution :math:`u` and property model :math:`\\sigma`. Then we use sympy to do\n# all the derivatives for us to determine the correct forcing function.\n#\n# .. math::\n#     u = \\sin(x) \\cos(y) e^{x + y} \\\\\n#     \\sigma = \\cos(x) \\sin(y)\n#\n\nC = spv.CoordSys3D(\"u\")\n\nu = sympy.sin(C.x) * sympy.cos(C.y) * sympy.exp(C.x + C.y)\nsig = sympy.cos(C.x) * sympy.sin(C.y)\n\nf = -spv.divergence(sig * spv.gradient(u))\n\n# lambdify the functions so we can evalute them at any requested location\nu_func = lambdify([C.x, C.y], u)\nsig_func = lambdify([C.x, C.y], sig)\nf_func = lambdify([C.x, C.y], f)\nprint(f)\n\n# %%\n# Next we are going to solve our PDE! We encapsulate the code into a\n# function that returns the error and discretization size, so we can use\n# `discretize`'s testing utilities to perform an order test for us.\n\n\ndef get_error(n_cells, plot_it=False):\n    # Create a mesh with a certain number of cells on the [0, 1] square\n    mesh = discretize.TensorMesh([n_cells, n_cells])\n    h = mesh.nodes_x[1] - mesh.nodes_x[0]\n\n    # evaluate our boundary conditions, the sigma model, and\n    # the forcing function.\n    u_b = u_func(*mesh.boundary_nodes.T)\n    sig_cc = sig_func(*mesh.cell_centers.T)\n    f_n = f_func(*mesh.nodes.T)\n\n    # Get the finite volume operators for the mesh\n    G = mesh.nodal_gradient\n    M_e_sigma = mesh.get_edge_inner_product(sig_cc)\n    M_n = sp.diags(mesh.average_node_to_cell.T @ mesh.cell_volumes)\n\n    # Determin which mesh nodes are on the boundary, and which are interior\n    is_boundary = np.zeros(mesh.n_nodes, dtype=bool)\n    is_boundary[mesh.project_node_to_boundary_node.indices] = True\n\n    # construct the projection matrices\n    P_b = sp.eye(mesh.n_nodes, format=\"csc\")[:, is_boundary]\n    P_f = sp.eye(mesh.n_nodes, format=\"csc\")[:, ~is_boundary]\n\n    # Assemble the solution matrix and rhs.\n    A = P_f.T @ G.T @ M_e_sigma @ G @ P_f\n    rhs = P_f.T @ M_n @ f_n - P_f.T @ G.T @ M_e_sigma @ G @ P_b @ u_b\n\n    # Solve for the value of u on the free nodes, then project\n    # with the boundary conditions to obtain the solution on\n    # all nodes.\n    u_f = sp.linalg.spsolve(A, rhs)\n    u_sol = P_f @ u_f + P_b @ u_b\n\n    # Since we know the true solution we can check the error\n    # which we expect to be second order accurate.\n    u_true = u_func(*mesh.nodes.T)\n    err = np.linalg.norm(u_sol - u_true, ord=np.inf)\n\n    # Add some codes for us to use this function to plot the solution.\n    if plot_it:\n        ax = plt.subplot(121)\n        (im,) = mesh.plot_image(u_sol, v_type=\"N\", ax=ax)\n        ax.set_title(\"Numerical Solution\")\n        ax.set_aspect(\"equal\")\n        plt.colorbar(im, shrink=0.3)\n\n        ax = plt.subplot(122)\n        (im,) = mesh.plot_image(u_sol - u_true, v_type=\"N\", ax=ax)\n        ax.set_title(\"Error\")\n        ax.set_aspect(\"equal\")\n        plt.colorbar(im, shrink=0.3)\n        plt.tight_layout()\n        plt.show()\n    else:\n        return err, h\n\n\n# %%\n# Lets look at the solution itself!\nget_error(20, plot_it=True)\n\n# %%\n# Order Testing\n# -------------\n# To verify our discretization strategy we will use the above function to perform a\n# convergence test. We make use of the `assert_expected_order` function to print out\n# a convergence table. This will raise an error if our convergence is not what we\n# expect.\n\ntests.assert_expected_order(get_error, [10, 20, 30, 40, 50])\n"
  },
  {
    "path": "tutorials/pde/README.txt",
    "content": "Solving PDEs\n============\n\nHere we show how the *discretize* package can be used to solve partial differential\nequations (PDE) numerically by employing the finite volume method. To solve a PDE\nnumerically we must complete the following steps:\n\n\t1. Formulate the problem; e.g. the PDE and its boundary conditions\n\t2. Apply the weak formulation by taking the inner product of each PDE with a test function\n\t3. Formulate a discrete set of equations for the inner products according to the finite volume method\n\t4. Use the discrete set of equations to solve for the unknown variable numerically\n"
  }
]