├── .gitattributes ├── .github └── workflows │ ├── black.yml │ ├── docs.yml │ ├── flake8.yml │ ├── test-notebooks.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── AUTHORS.rst ├── CHANGELOG.rst ├── CITATION.cff ├── CONTRIBUTING.rst ├── LICENSE.rst ├── MANIFEST.in ├── Makefile ├── README.rst ├── cmt └── __init__.py ├── conftest.py ├── deprecated ├── component_info.py ├── grids │ └── vtk_mixin.py ├── nsdict.py ├── ordered_task_status.py ├── plugin.py ├── portprinter │ └── tests │ │ ├── test_bov_port_printer.py │ │ └── test_vtk_port_printer.py ├── printers │ ├── bov │ │ ├── __init__.py │ │ ├── bov_io.py │ │ ├── database.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ ├── test_database.py │ │ │ └── test_write.py │ └── vtk │ │ ├── __init__.py │ │ ├── encoders.py │ │ ├── rect.vti │ │ ├── rect.vts │ │ ├── tests │ │ ├── __init__.py │ │ └── test_vtk.py │ │ ├── vti.py │ │ ├── vtk.py │ │ ├── vtktypes.py │ │ ├── vtkxml.py │ │ ├── vtr.py │ │ ├── vts.py │ │ └── vtu.py ├── status.py ├── task_status.py ├── test_component_info.py ├── testing │ └── assertions.py ├── tests │ ├── test_nsdict.py │ ├── test_ordered_tasks.py │ └── test_task_status.py └── utils │ ├── decorators.py │ ├── run_dir.py │ ├── tests │ ├── test_run_dir.py │ └── test_which.py │ ├── verbose.py │ └── which.py ├── docs ├── Makefile ├── _static │ ├── apple.svg │ ├── hydrotrend-discharge.png │ ├── linux.svg │ ├── powered-by-logo-header.png │ ├── pymt-logo-header-text.png │ ├── pymt-logo-header.png │ └── windows.svg ├── _templates │ ├── links.html │ └── sidebarintro.html ├── api │ └── index.rst ├── authors.rst ├── conda-environments.rst ├── conf.py ├── contributing.rst ├── environment.yml ├── examples.rst ├── glossary.rst ├── history.rst ├── index.rst ├── install.rst ├── installation-check.rst ├── installation-environment.rst ├── license.rst ├── make.bat ├── models.rst ├── quickstart.rst ├── readme.rst ├── scripts │ └── make_table.py └── usage.rst ├── environment-docs.yml ├── environment.yml ├── news └── .gitignore ├── notebooks ├── Ganges │ ├── HYDRO.CLIMATE │ ├── HYDRO.IN │ ├── HYDRO0.HYPS │ └── hydro_config.txt ├── README.md ├── RhineBedload.csv ├── cem.ipynb ├── cem_and_waves.ipynb ├── child.ipynb ├── conftest.py ├── ecsimplesnow.ipynb ├── exclude.yml ├── frost_number.ipynb ├── gipl.ipynb ├── gipl_and_ecsimplesnow.ipynb ├── hydrotrend.ipynb ├── hydrotrend_Ganges.ipynb ├── ku.ipynb ├── sedflux3d.ipynb ├── sedflux3d_and_child.ipynb ├── subside.ipynb ├── test_notebooks.py └── welcome.ipynb ├── pymt ├── __init__.py ├── _version.py ├── bmi │ ├── __init__.py │ └── bmi.py ├── cmd │ ├── __init__.py │ └── cmt_config.py ├── component │ ├── __init__.py │ ├── component.py │ ├── grid.py │ └── model.py ├── errors.py ├── events │ ├── __init__.py │ ├── chain.py │ ├── empty.py │ ├── manager.py │ ├── port.py │ └── printer.py ├── framework │ ├── __init__.py │ ├── bmi_bridge.py │ ├── bmi_docstring.py │ ├── bmi_mapper.py │ ├── bmi_plot.py │ ├── bmi_setup.py │ ├── bmi_timeinterp.py │ ├── bmi_ugrid.py │ ├── services.py │ └── timeinterp.py ├── grids │ ├── __init__.py │ ├── assertions.py │ ├── connectivity.py │ ├── esmp.py │ ├── field.py │ ├── grid_type.py │ ├── igrid.py │ ├── map.py │ ├── meshgrid.py │ ├── raster.py │ ├── rectilinear.py │ ├── structured.py │ ├── unstructured.py │ └── utils.py ├── mappers │ ├── __init__.py │ ├── celltopoint.py │ ├── esmp.py │ ├── imapper.py │ ├── mapper.py │ ├── pointtocell.py │ └── pointtopoint.py ├── model_collection.py ├── models.py ├── portprinter │ ├── __init__.py │ ├── port_printer.py │ └── utils.py ├── printers │ ├── __init__.py │ └── nc │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── database.py │ │ ├── read.py │ │ ├── ugrid.py │ │ ├── ugrid_read.py │ │ └── write.py ├── services │ ├── __init__.py │ ├── constant │ │ ├── __init__.py │ │ ├── constant.py │ │ └── river.py │ └── gridreader │ │ ├── __init__.py │ │ ├── gridreader.py │ │ ├── interpolate.py │ │ └── time_series_names.py ├── testing │ ├── __init__.py │ ├── ports.py │ └── services.py ├── timeline.py ├── units.py └── utils │ ├── __init__.py │ ├── prefix.py │ └── utils.py ├── pyproject.toml ├── requirements-dev.txt ├── requirements-docs.txt ├── requirements-notebooks.txt ├── requirements-testing.txt ├── requirements.txt ├── scripts ├── changelog.py ├── prm2input.py ├── prm2template.py ├── prmscan.py ├── quickstart.py └── vtu2ncu.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── component ├── __init__.py ├── conftest.py ├── test_grid_mixin.py ├── test_map_component.py ├── test_model.py ├── test_one_component.py ├── test_recursive.py └── test_two_components.py ├── events ├── __init__.py ├── conftest.py ├── test_chain_events.py ├── test_port_events.py ├── test_port_map_events.py └── test_print_events.py ├── framework ├── __init__.py ├── test_bmi_docstring.py ├── test_bmi_time_units.py ├── test_bmi_ugrid.py ├── test_bmi_var_units.py ├── test_setup.py └── test_timeinterp.py ├── grids ├── __init__.py ├── test_assertions.py ├── test_esmp.py ├── test_field.py ├── test_grid_type.py ├── test_raster.py ├── test_rectilinear.py ├── test_structured.py ├── test_unstructured.py └── test_utils.py ├── mappers ├── __init__.py ├── test_mapper.py └── test_pointtopoint.py ├── portprinter ├── __init__.py ├── conftest.py ├── test_nc_port_printer.py ├── test_port_printer_queue.py └── test_utils.py ├── printers └── nc │ ├── __init__.py │ ├── test_database.py │ ├── test_nc.py │ ├── test_nc_examples.py │ ├── test_ugrid.py │ ├── test_ugrid_read.py │ └── test_ugrid_read │ ├── rectilinear.1d.nc │ ├── rectilinear.2d.nc │ ├── rectilinear.3d.nc │ └── unstructured.2d.nc ├── services ├── constant │ ├── __init__.py │ └── test_constant_scalar.py └── gridreader │ └── test_time_series_names.py ├── test_models.py ├── test_timeline.py └── test_units.py /.gitattributes: -------------------------------------------------------------------------------- 1 | pymt/_version.py export-subst 2 | -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: Black 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | lint: 8 | name: Check code format 9 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 10 | # by the push to the branch. Without this if check, checks are duplicated since 11 | # internal PRs match both the push and pull_request events. 12 | if: 13 | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 14 | github.repository 15 | 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-python@v5 20 | - uses: psf/black@stable 21 | with: 22 | options: "--check --verbose --diff" 23 | src: "pymt tests" 24 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | paths: 6 | - "docs/**" 7 | - "AUTHORS.rst" 8 | - "CHANGELOG.rst" 9 | - "CONTRIBUTING.rst" 10 | - "LICENSE" 11 | - "README.rst" 12 | pull_request: 13 | paths: 14 | - "docs/**" 15 | - "AUTHORS.rst" 16 | - "CHANGELOG.rst" 17 | - "CONTRIBUTING.rst" 18 | - "LICENSE" 19 | - "README.rst" 20 | 21 | jobs: 22 | build: 23 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 24 | # by the push to the branch. Without this if check, checks are duplicated since 25 | # internal PRs match both the push and pull_request events. 26 | if: 27 | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 28 | github.repository 29 | 30 | runs-on: ubuntu-latest 31 | 32 | defaults: 33 | run: 34 | shell: bash -l {0} 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: conda-incubator/setup-miniconda@v3 39 | with: 40 | miniforge-version: latest 41 | python-version: 3.11 42 | 43 | - name: Show conda installation info 44 | run: | 45 | conda info 46 | conda list 47 | 48 | - name: Install dependencies 49 | run: | 50 | conda install --file=requirements.txt --file=requirements-docs.txt 51 | pip install -e . 52 | 53 | - name: Build documentation 54 | run: make docs 55 | -------------------------------------------------------------------------------- /.github/workflows/flake8.yml: -------------------------------------------------------------------------------- 1 | name: Flake8 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | lint: 8 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 9 | # by the push to the branch. Without this if check, checks are duplicated since 10 | # internal PRs match both the push and pull_request events. 11 | if: 12 | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 13 | github.repository 14 | 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python 3.8 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: 3.8 22 | 23 | - name: Lint 24 | run: | 25 | pip install flake8 26 | flake8 pymt tests 27 | -------------------------------------------------------------------------------- /.github/workflows/test-notebooks.yml: -------------------------------------------------------------------------------- 1 | name: Notebooks 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-and-test: 7 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 8 | # by the push to the branch. Without this if check, checks are duplicated since 9 | # internal PRs match both the push and pull_request events. 10 | if: 11 | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 12 | github.repository 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | defaults: 17 | run: 18 | shell: bash -l {0} 19 | 20 | strategy: 21 | matrix: 22 | os: [ubuntu-latest, macos-latest] 23 | python-version: ["3.10"] 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: conda-incubator/setup-miniconda@v3 29 | with: 30 | miniforge-version: latest 31 | python-version: ${{ matrix.python-version }} 32 | 33 | - name: Show conda installation info 34 | run: | 35 | conda info 36 | conda list 37 | 38 | - name: Install requirements 39 | run: | 40 | conda install \ 41 | --file=requirements.txt \ 42 | --file=requirements-testing.txt \ 43 | --file=requirements-notebooks.txt 44 | conda list 45 | 46 | - name: Build and install package 47 | run: | 48 | pip install -e . 49 | 50 | - name: Test jupyter notebooks 51 | timeout-minutes: 30 52 | run: | 53 | python -c 'import pymt; print(pymt.__version__)' 54 | pytest notebooks --run-notebook -vvv 55 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-and-test: 7 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 8 | # by the push to the branch. Without this if check, checks are duplicated since 9 | # internal PRs match both the push and pull_request events. 10 | if: 11 | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 12 | github.repository 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | defaults: 17 | run: 18 | shell: bash -l {0} 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: [ubuntu-latest, macos-latest, windows-latest] 24 | python-version: ["3.10", "3.11"] 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - uses: conda-incubator/setup-miniconda@v3 30 | with: 31 | miniforge-version: latest 32 | python-version: ${{ matrix.python-version }} 33 | 34 | - name: Show conda installation info 35 | run: | 36 | conda info 37 | conda list 38 | 39 | - name: Install requirements 40 | run: | 41 | conda install --file=requirements.txt --file=requirements-testing.txt 42 | conda list 43 | 44 | - name: Install Windows requirements 45 | if: matrix.os == 'windows-latest' 46 | run: | 47 | conda install pymt_child pymt_hydrotrend pymt_permamodel 48 | 49 | - name: Install Unix requirements 50 | if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' 51 | run: | 52 | conda install pymt_cem pymt_child pymt_hydrotrend pymt_permamodel 53 | 54 | - name: Build and install package 55 | run: | 56 | pip install -e . 57 | 58 | - name: Test 59 | run: | 60 | python -c 'import pymt; print(pymt.__version__)' 61 | pytest --cov=pymt --cov-report=xml:$(pwd)/coverage.xml -vvv 62 | 63 | - name: Coveralls 64 | if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' 65 | uses: AndreMiras/coveralls-python-action@v20201129 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.eggs 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | __pycache__ 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Build folders 32 | _build 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Auto-generated API docs 43 | docs/api/pymt.*rst 44 | docs/api/modules.rst 45 | 46 | # Jupyter Notebook 47 | .ipynb_checkpoints 48 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/scipy-sphinx-theme"] 2 | path = docs/scipy-sphinx-theme 3 | url = https://github.com/scipy/scipy-sphinx-theme.git 4 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | sphinx: 9 | builder: html 10 | configuration: docs/conf.py 11 | fail_on_warning: false 12 | 13 | python: 14 | install: 15 | - requirements: requirements-docs.txt 16 | - requirements: requirements.txt 17 | - method: pip 18 | path: . 19 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Credits 2 | ======= 3 | 4 | Development Leads 5 | ----------------- 6 | 7 | * Eric Hutton 8 | * Mark Piper 9 | 10 | Contributors 11 | ------------ 12 | 13 | * Niels Drost 14 | * Tian Gan 15 | * Albert Kettner 16 | * Irina Overeem 17 | * Scott Stewart 18 | * Mike Taves 19 | * Kang Wang 20 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # YAML 1.2 2 | --- 3 | authors: 4 | - 5 | family-names: Hutton 6 | given-names: Eric 7 | orcid: "https://orcid.org/0000-0002-5864-6459" 8 | - 9 | family-names: Piper 10 | given-names: Mark 11 | orcid: "https://orcid.org/0000-0001-6418-277X" 12 | - 13 | family-names: Drost 14 | given-names: Niels 15 | orcid: "https://orcid.org/0000-0001-9795-7981" 16 | - 17 | family-names: Gan 18 | given-names: Tian 19 | orcid: "https://orcid.org/0000-0003-3624-6910" 20 | - 21 | family-names: Kettner 22 | given-names: Albert 23 | orcid: "https://orcid.org/0000-0002-7191-6521" 24 | - 25 | family-names: Overeem 26 | given-names: Irina 27 | orcid: "https://orcid.org/0000-0002-8422-580X" 28 | - 29 | family-names: Stewart 30 | given-names: Scott 31 | - 32 | family-names: Wang 33 | given-names: Kang 34 | orcid: "https://orcid.org/0000-0003-3416-572X" 35 | cff-version: "1.1.0" 36 | date-released: 2021-03-18 37 | doi: "10.5281/zenodo.4985222" 38 | license: MIT 39 | message: "If you use this software, please cite it using these metadata." 40 | title: "The Python Modeling Toolkit (PyMT)" 41 | version: "1.3.1" 42 | ... -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) `2022` `Community Surface Dynamics Modeling System` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.rst 2 | include README.rst 3 | include CHANGELOG.rst 4 | include AUTHORS.rst 5 | include Makefile 6 | include requirements*.txt 7 | recursive-include tests *py 8 | recursive-exclude deprecated * 9 | recursive-exclude docs * 10 | recursive-exclude scripts *py 11 | recursive-exclude notebooks * 12 | 13 | exclude .gitmodules 14 | exclude .landscape.yml 15 | exclude .readthedocs.yaml 16 | exclude CONTRIBUTING.rst 17 | exclude conda-recipe/meta.yaml 18 | exclude conftest.py 19 | exclude environment.yml 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 pymt tests 55 | 56 | pretty: ## reformat files to make them look pretty 57 | find pymt -name '*.py' | xargs isort 58 | find tests -name '*.py' | xargs isort 59 | black pymt tests 60 | 61 | test: ## run tests quickly with the default Python 62 | pytest 63 | 64 | test-all: ## run tests on every Python version with tox 65 | tox 66 | 67 | coverage: ## check code coverage quickly with the default Python 68 | pytest --cov=pymt --cov-report= --cov-report=html --cov-config=setup.cfg 69 | $(BROWSER) htmlcov/index.html 70 | 71 | docs: ## generate Sphinx HTML documentation, including API docs 72 | rm -f docs/api/pymt.rst 73 | rm -f docs/api/modules.rst 74 | sphinx-apidoc --force -o docs/api pymt *tests 75 | $(MAKE) -C docs clean 76 | $(MAKE) -C docs html 77 | $(BROWSER) docs/_build/html/index.html 78 | 79 | changelog: 80 | changelog --force --batch 81 | 82 | servedocs: docs ## compile the docs watching for changes 83 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 84 | 85 | release: dist ## package and upload a release 86 | twine upload dist/* 87 | 88 | dist: clean ## builds source and wheel package 89 | python -m build 90 | ls -l dist 91 | 92 | install: clean ## install the package to the active Python's site-packages 93 | pip install . 94 | -------------------------------------------------------------------------------- /cmt/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pymt 4 | 5 | sys.modules["cmt"] = pymt 6 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | def pytest_addoption(parser): 2 | parser.addoption( 3 | "--run-notebook", action="store_true", default=False, help="run notebook tests" 4 | ) 5 | -------------------------------------------------------------------------------- /deprecated/grids/vtk_mixin.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import warnings 3 | 4 | import numpy as np 5 | 6 | try: 7 | from tvtk.api import tvtk 8 | except ImportError: 9 | warnings.warn("vtk is not installed") 10 | 11 | class VtkGridMixIn: 12 | pass 13 | 14 | 15 | else: 16 | 17 | class VtkGridMixIn: 18 | _EDGE_COUNT_TO_TYPE = { 19 | 1: tvtk.Vertex().cell_type, 20 | 2: tvtk.Line().cell_type, 21 | 3: tvtk.Triangle().cell_type, 22 | 4: tvtk.Quad().cell_type, 23 | } 24 | 25 | def to_vtk(self): 26 | points = self.vtk_points() 27 | cell_types = self.vtk_cell_types() 28 | cell_array = self.vtk_cell_array() 29 | offsets = self.vtk_offsets() 30 | 31 | vtk_grid = tvtk.UnstructuredGrid(points=points) 32 | vtk_grid.set_cells(cell_types, offsets, cell_array) 33 | 34 | return vtk_grid 35 | 36 | def vtk_points(self): 37 | pad = np.zeros((3 - self._coords.shape[0], self._coords.shape[1])) 38 | return np.vstack([self._coords, pad]).T 39 | 40 | def vtk_cell_array(self): 41 | cell_array = tvtk.CellArray() 42 | cell_array.set_cells(self.get_cell_count(), self.vtk_connectivity()) 43 | return cell_array 44 | 45 | def vtk_cell_types(self): 46 | cell_types = np.empty(self.get_cell_count(), dtype=int) 47 | for (id_, n_nodes) in enumerate(self.nodes_per_cell()): 48 | try: 49 | cell_types[id_] = self._EDGE_COUNT_TO_TYPE[n_nodes] 50 | except KeyError: 51 | cell_types[id_] = tvtk.Polygon().cell_type 52 | return cell_types 53 | 54 | def vtk_connectivity(self): 55 | cells = np.empty(self.get_vertex_count() + self.get_cell_count(), dtype=int) 56 | 57 | cell_nodes = self.get_connectivity() 58 | 59 | offset = 0 60 | for n_nodes in self.nodes_per_cell(): 61 | cells[offset] = n_nodes 62 | offset += n_nodes + 1 63 | 64 | offset = 1 65 | for cell in self.vtk_offsets(): 66 | n_nodes = cells[offset - 1] 67 | cells[offset : offset + n_nodes] = cell_nodes[cell : cell + n_nodes] 68 | 69 | offset += n_nodes + 1 70 | 71 | return cells 72 | 73 | def vtk_offsets(self): 74 | offsets = np.empty(self.get_cell_count(), dtype=int) 75 | (offsets[0], offsets[1:]) = (0, self._offset[:-1]) 76 | return offsets 77 | 78 | def vtk_write(self, file_name): 79 | writer = tvtk.XMLUnstructuredGridWriter() 80 | writer.set_input(self.to_vtk()) 81 | writer.file_name = file_name 82 | writer.write() 83 | -------------------------------------------------------------------------------- /deprecated/ordered_task_status.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ 3 | >>> from __future__ import print_function 4 | 5 | >>> status = OrderedTaskStatus() 6 | >>> status.start_task('create') 7 | >>> print(status) 8 | creating 9 | >>> status.complete_task() 10 | >>> print(status) 11 | created 12 | 13 | >>> status.start_task('initialize') 14 | >>> print(status) 15 | initializing 16 | >>> status.start_task('initialize') 17 | >>> print(status) 18 | initializing 19 | 20 | >>> status.complete_task() 21 | >>> print(status) 22 | initialized 23 | 24 | """ 25 | 26 | from pymt.task_status import TaskStatus 27 | 28 | 29 | class OrderedTaskStatus: 30 | """OrderedTaskStatus([task, [status]]) 31 | 32 | >>> status = OrderedTaskStatus() 33 | >>> print(status) 34 | idling 35 | >>> print(status.task) 36 | create 37 | 38 | >>> status.start_task('create') 39 | >>> print(status) 40 | creating 41 | >>> status.complete_task() 42 | >>> print(status) 43 | created 44 | """ 45 | 46 | def __init__(self, current=0, status="idling"): 47 | self._task_list = [ 48 | TaskStatus("create", started="creating", completed="created"), 49 | TaskStatus("initialize", started="initializing", completed="initialized"), 50 | TaskStatus("update", started="updating", completed="updated"), 51 | TaskStatus("finalize", started="finalizing", completed="finalized"), 52 | ] 53 | 54 | if isinstance(current, str): 55 | self._current = self.tasks.index(current) 56 | else: 57 | self._current = current 58 | self.current.update_to(status) 59 | 60 | def start_task(self, task, allow_restart=False): 61 | """ 62 | Start *task* with the named string and set its status to the appropriate 63 | started string. If the previous task has not been completed, or if the 64 | *task* is already complete, raise ValueError. 65 | """ 66 | if self._task_is_current(task): 67 | self.current.start(allow_restart=allow_restart) 68 | elif self._task_is_next(task): 69 | self._pop_next() 70 | self.current.start() 71 | else: 72 | raise ValueError("%s: task is not next" % task) 73 | 74 | def complete_task(self): 75 | """ 76 | Complete the current task by setting its status to its completed string. 77 | """ 78 | self.current.complete() 79 | 80 | @property 81 | def current(self): 82 | return self._task_list[self._current] 83 | 84 | @property 85 | def task(self): 86 | return self._task_list[self._current].name 87 | 88 | @property 89 | def status(self): 90 | return self._task_list[self._current].status 91 | 92 | @property 93 | def tasks(self): 94 | return [task.name for task in self._task_list] 95 | 96 | def _task_is_current(self, task): 97 | return task == self.current.name 98 | 99 | def _task_is_next(self, task): 100 | try: 101 | return self._task_list[self._current + 1].name == task 102 | except IndexError: 103 | return False 104 | 105 | def _pop_next(self): 106 | if self.current.is_completed(): 107 | self._current += 1 108 | else: 109 | raise ValueError("current task is not complete") 110 | 111 | def __str__(self): 112 | return self.status 113 | 114 | def __repr__(self): 115 | kwds = ["current=%d" % self._current, "status=%s" % repr(self.status)] 116 | return "OrderedTaskStatus(%s)" % ", ".join(kwds) 117 | -------------------------------------------------------------------------------- /deprecated/portprinter/tests/test_bov_port_printer.py: -------------------------------------------------------------------------------- 1 | from pymt.portprinter.port_printer import BovPortPrinter, PortPrinter 2 | from pymt.testing.ports import UniformRectilinearGridPort 3 | from pymt.testing.assertions import assert_isfile_and_remove 4 | 5 | 6 | def test_default(): 7 | port = UniformRectilinearGridPort() 8 | printer = BovPortPrinter(port, "landscape_surface__elevation") 9 | printer.open() 10 | printer.write() 11 | 12 | assert_isfile_and_remove("landscape_surface__elevation_0000.dat") 13 | assert_isfile_and_remove("landscape_surface__elevation_0000.bov") 14 | 15 | 16 | def test_multiple_files(): 17 | port = UniformRectilinearGridPort() 18 | for _ in range(5): 19 | printer = BovPortPrinter(port, "sea_surface__temperature") 20 | printer.open() 21 | printer.write() 22 | 23 | assert_isfile_and_remove("sea_surface__temperature_0000.dat") 24 | assert_isfile_and_remove("sea_surface__temperature_0000.bov") 25 | 26 | 27 | def test_time_series(): 28 | expected_files = [ 29 | "sea_floor_surface_sediment__mean_of_grain_size_0000.dat", 30 | "sea_floor_surface_sediment__mean_of_grain_size_0000.bov", 31 | "sea_floor_surface_sediment__mean_of_grain_size_0001.dat", 32 | "sea_floor_surface_sediment__mean_of_grain_size_0001.bov", 33 | "sea_floor_surface_sediment__mean_of_grain_size_0002.dat", 34 | "sea_floor_surface_sediment__mean_of_grain_size_0002.bov", 35 | "sea_floor_surface_sediment__mean_of_grain_size_0003.dat", 36 | "sea_floor_surface_sediment__mean_of_grain_size_0003.bov", 37 | "sea_floor_surface_sediment__mean_of_grain_size_0004.dat", 38 | "sea_floor_surface_sediment__mean_of_grain_size_0004.bov", 39 | ] 40 | 41 | port = UniformRectilinearGridPort() 42 | printer = BovPortPrinter(port, "sea_floor_surface_sediment__mean_of_grain_size") 43 | printer.open() 44 | for _ in range(5): 45 | printer.write() 46 | printer.close() 47 | 48 | for filename in expected_files: 49 | assert_isfile_and_remove(filename) 50 | 51 | 52 | def test_from_string(): 53 | ini_string = """ 54 | [print.air__temperature] 55 | format=bov 56 | port=air_port 57 | """ 58 | PortPrinter.from_string(ini_string, "print.air__temperature") 59 | -------------------------------------------------------------------------------- /deprecated/portprinter/tests/test_vtk_port_printer.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymt.portprinter.port_printer import VtkPortPrinter 4 | from pymt.testing.ports import UniformRectilinearGridPort 5 | 6 | 7 | def test_one_file(tmpdir): 8 | port = UniformRectilinearGridPort() 9 | 10 | with tmpdir.as_cwd(): 11 | printer = VtkPortPrinter(port, "landscape_surface__elevation") 12 | printer.open() 13 | printer.write() 14 | 15 | assert os.path.isfile("landscape_surface__elevation_0000.vtu") 16 | 17 | 18 | def test_time_series(tmpdir): 19 | expected_files = [ 20 | "sea_floor_surface_sediment__mean_of_grain_size_0000.vtu", 21 | "sea_floor_surface_sediment__mean_of_grain_size_0001.vtu", 22 | "sea_floor_surface_sediment__mean_of_grain_size_0002.vtu", 23 | "sea_floor_surface_sediment__mean_of_grain_size_0003.vtu", 24 | "sea_floor_surface_sediment__mean_of_grain_size_0004.vtu", 25 | ] 26 | 27 | port = UniformRectilinearGridPort() 28 | 29 | with tmpdir.as_cwd(): 30 | printer = VtkPortPrinter(port, "sea_floor_surface_sediment__mean_of_grain_size") 31 | printer.open() 32 | for _ in range(5): 33 | printer.write() 34 | printer.close() 35 | 36 | for filename in expected_files: 37 | assert os.path.isfile(filename) 38 | 39 | 40 | def test_multiple_files(tmpdir): 41 | port = UniformRectilinearGridPort() 42 | 43 | with tmpdir.as_cwd(): 44 | for _ in range(5): 45 | printer = VtkPortPrinter(port, "sea_surface__temperature") 46 | printer.open() 47 | printer.write() 48 | printer.close() 49 | 50 | assert os.path.isfile("sea_surface__temperature_0000.vtu") 51 | 52 | 53 | def test_port_as_string(tmpdir, with_two_components): 54 | with tmpdir.as_cwd(): 55 | printer = VtkPortPrinter("air_port", "air__density") 56 | printer.open() 57 | printer.write() 58 | printer.close() 59 | 60 | assert os.path.isfile("air__density_0000.vtu") 61 | -------------------------------------------------------------------------------- /deprecated/printers/bov/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/deprecated/printers/bov/__init__.py -------------------------------------------------------------------------------- /deprecated/printers/bov/database.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python 2 | 3 | import os 4 | 5 | from .bov_io import tofile 6 | 7 | 8 | class IDatabase: 9 | def __init__(self): 10 | pass 11 | 12 | def open(self, path, var_name): 13 | pass 14 | 15 | def write(self, field): 16 | pass 17 | 18 | def close(self): 19 | pass 20 | 21 | 22 | class Database(IDatabase): 23 | def __init__(self): 24 | super().__init__() 25 | 26 | self._var_name = "" 27 | self._path = "" 28 | self._template = "" 29 | self._options = {} 30 | 31 | def open(self, path, var_name, **kwargs): 32 | self.close() 33 | 34 | (root, ext) = os.path.splitext(path) 35 | 36 | self._var_name = var_name 37 | self._path = path 38 | self._template = "%s_%%04d%s" % (root, ext) 39 | 40 | self._options.update(**kwargs) 41 | 42 | def write(self, field): 43 | file_name = self._next_file_name() 44 | tofile(file_name, field, no_clobber=False, options=self._options) 45 | 46 | def close(self): 47 | try: 48 | del self._count 49 | except AttributeError: 50 | pass 51 | 52 | def _next_file_name(self): 53 | try: 54 | next_file_name = self._template % self._count 55 | except AttributeError: 56 | self._count = 0 57 | next_file_name = self._template % self._count 58 | finally: 59 | self._count += 1 60 | 61 | return next_file_name 62 | -------------------------------------------------------------------------------- /deprecated/printers/bov/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/deprecated/printers/bov/tests/__init__.py -------------------------------------------------------------------------------- /deprecated/printers/bov/tests/test_database.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | 5 | from pymt.grids import RasterField 6 | from pymt.printers.bov.database import Database 7 | 8 | 9 | def test_bov_database(tmpdir): 10 | data = np.arange(6.0) 11 | field = RasterField((3, 2), (1.0, 1.0), (0.0, 0.0)) 12 | field.add_field("Elevation", data, centering="point") 13 | 14 | with tmpdir.as_cwd(): 15 | db = Database() 16 | db.open("Bov_database.bov", "Elevation") 17 | 18 | # Write the field to the database. Since BOV files only 19 | # store one variable, append the variable name to the file name. 20 | 21 | db.write(field) 22 | assert os.path.isfile("Bov_database_0000.bov") 23 | 24 | data *= 2.0 25 | db.write(field) 26 | assert os.path.isfile("Bov_database_0001.bov") 27 | 28 | data *= 2.0 29 | db.write(field) 30 | assert os.path.isfile("Bov_database_0002.bov") 31 | 32 | db.close() 33 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/__init__.py: -------------------------------------------------------------------------------- 1 | from .vtu import tofile 2 | 3 | 4 | __all__ = ["tofile"] 5 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/encoders.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | import base64 3 | 4 | import numpy as np 5 | 6 | 7 | class EncoderError(Exception): 8 | pass 9 | 10 | 11 | class UnknownEncoder(EncoderError): 12 | def __init__(self, name): 13 | self._name = name 14 | 15 | def __str__(self): 16 | return "%s: Unknown encoder" % self._name 17 | 18 | 19 | class Encoder: 20 | def encode(self, array): 21 | pass 22 | 23 | 24 | class ASCIIEncoder: 25 | def encode(self, array): 26 | try: 27 | return " ".join([str(val) for val in array.flatten()]) 28 | except AttributeError: 29 | return " ".join([str(val) for val in array]) 30 | 31 | 32 | class RawEncoder: 33 | def encode(self, array): 34 | try: 35 | as_str = array.tostring() 36 | except AttributeError: 37 | as_str = np.array(array).tostring() 38 | block_size = np.array(len(as_str), dtype=np.int32).tostring() 39 | return block_size + as_str 40 | 41 | 42 | class Base64Encoder: 43 | def encode(self, array): 44 | try: 45 | as_str = array.tostring() 46 | except AttributeError: 47 | as_str = np.array(array).tostring() 48 | as_str = base64.b64encode(as_str) 49 | block_size = base64.b64encode(np.array(len(as_str), dtype=np.int32).tostring()) 50 | return block_size + as_str 51 | 52 | def decode(self, array): 53 | pass 54 | 55 | 56 | encoders = {"ascii": ASCIIEncoder(), "raw": RawEncoder(), "base64": Base64Encoder()} 57 | 58 | 59 | def encode(array, encoding="ascii"): 60 | return encoders[encoding].encode(array) 61 | 62 | 63 | def decode(array, encoding="raw"): 64 | return encoders[encoding].decode(array) 65 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/rect.vti: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 7 | 8 | 9 | 1.0 2.0 3.0 4.0 5.0 6.0 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/rect.vts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | _gAEAAA==AAAAAAAA8L8AAAAAAADgvwAAAAAAAACAAAAAAAAA8D8AAAAAAADgvwAAAAAAAAAAAAAAAAAACEAAAAAAAADgvwAAAAAAAAAAAAAAAAAAFEAAAAAAAADgvwAAAAAAAAAAAAAAAAAA8L8AAAAAAADgPwAAAAAAAACAAAAAAAAA8D8AAAAAAADgPwAAAAAAAAAAAAAAAAAACEAAAAAAAADgPwAAAAAAAAAAAAAAAAAAFEAAAAAAAADgPwAAAAAAAAAAAAAAAAAA8L8AAAAAAAD4PwAAAAAAAACAAAAAAAAA8D8AAAAAAAD4PwAAAAAAAAAAAAAAAAAACEAAAAAAAAD4PwAAAAAAAAAAAAAAAAAAFEAAAAAAAAD4PwAAAAAAAAAAgAAAAA==AAAAAAAAAAAAAAAAAADwPwAAAAAAAABAAAAAAAAACEAAAAAAAAAQQAAAAAAAABRAAAAAAAAAGEAAAAAAAAAcQAAAAAAAACBAAAAAAAAAIkAAAAAAAAAkQAAAAAAAACZAQAAAAA==AAAAAAAA8D8AAAAAAAAAQAAAAAAAAAhAAAAAAAAAEEAAAAAAAAAUQAAAAAAAABhA 17 | 18 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/deprecated/printers/vtk/tests/__init__.py -------------------------------------------------------------------------------- /deprecated/printers/vtk/vti.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python 2 | from .vtktypes import VtkUniformRectilinear 3 | from .vtkxml import ( 4 | VtkAppendedDataElement, 5 | VtkCellDataElement, 6 | VtkExtent, 7 | VtkGridElement, 8 | VtkOrigin, 9 | VtkPieceElement, 10 | VtkPointDataElement, 11 | VtkRootElement, 12 | VtkSpacing, 13 | ) 14 | 15 | 16 | def get_elements(field, data_format="ascii", encoding="ascii"): 17 | if data_format == "appended": 18 | data = VtkAppendedDataElement("", encoding=encoding) 19 | else: 20 | data = None 21 | 22 | extent = VtkExtent(field.get_shape()[::-1]) 23 | origin = VtkOrigin(field.get_origin()[::-1], field.get_spacing()[::-1]) 24 | spacing = VtkSpacing(field.get_spacing()[::-1]) 25 | 26 | element = { 27 | "VTKFile": VtkRootElement(VtkUniformRectilinear), 28 | "Grid": VtkGridElement( 29 | VtkUniformRectilinear, WholeExtent=extent, Origin=origin, Spacing=spacing 30 | ), 31 | "Piece": VtkPieceElement(Extent=extent), 32 | "PointData": VtkPointDataElement( 33 | field.get_point_fields(), append=data, encoding=encoding 34 | ), 35 | "CellData": VtkCellDataElement( 36 | field.get_cell_fields(), append=data, encoding=encoding 37 | ), 38 | } 39 | 40 | if data is not None: 41 | element["AppendedData"] = data 42 | 43 | return element 44 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/vtktypes.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python 2 | 3 | from .encoders import ASCIIEncoder, Base64Encoder, RawEncoder 4 | 5 | 6 | class VtkEndian: 7 | def __init__(self, endian): 8 | self.name = endian 9 | 10 | def __str__(self): 11 | return self.name 12 | 13 | 14 | VtkLittleEndian = VtkEndian("LittleEndian") 15 | VtkBigEndian = VtkEndian("BigEndian") 16 | 17 | sys_to_vtk_endian = {"little": VtkLittleEndian, "big": VtkBigEndian} 18 | 19 | 20 | class VtkType: 21 | def __init__(self, name, size): 22 | self.bytes = size 23 | self.name = name 24 | 25 | def __str__(self): 26 | return self.name 27 | 28 | 29 | VtkUInt8 = VtkType("UInt8", 1) 30 | VtkInt32 = VtkType("Int32", 4) 31 | VtkInt64 = VtkType("Int64", 8) 32 | VtkFloat32 = VtkType("Float32", 4) 33 | VtkFloat64 = VtkType("Float64", 8) 34 | 35 | np_to_vtk_type = { 36 | "uint8": VtkUInt8, 37 | "int32": VtkInt32, 38 | "int64": VtkInt64, 39 | "float32": VtkFloat32, 40 | "float64": VtkFloat64, 41 | } 42 | vtk_to_np_type = { 43 | "UInt8": "uint8", 44 | "Int32": "int32", 45 | "Int64": "int64", 46 | "Float32": "float32", 47 | "Float64": "float64", 48 | } 49 | 50 | 51 | class VtkCellType: 52 | def __init__(self, name, id): 53 | self.id = id 54 | self.name = name 55 | 56 | def __str__(self): 57 | return self.name 58 | 59 | def __int__(self): 60 | return self.id 61 | 62 | 63 | VtkVertex = VtkCellType("Vertex", 1) 64 | VtkLine = VtkCellType("Line", 3) 65 | VtkTriangle = VtkCellType("Triangle", 5) 66 | VtkQuad = VtkCellType("Quad", 9) 67 | VtkPolygon = VtkCellType("Polygon", 7) 68 | 69 | edge_count_to_type = {1: VtkVertex, 2: VtkLine, 3: VtkTriangle, 4: VtkQuad} 70 | 71 | 72 | class VtkGridType: 73 | def __init__(self, name): 74 | self.name = name 75 | 76 | def __str__(self): 77 | return self.name 78 | 79 | 80 | VtkUniformRectilinear = VtkGridType("ImageData") 81 | VtkRectilinear = VtkGridType("RectilinearGrid") 82 | VtkStructured = VtkGridType("StructuredGrid") 83 | VtkUnstructured = VtkGridType("UnstructuredGrid") 84 | 85 | encoders = {"ascii": ASCIIEncoder(), "raw": RawEncoder(), "base64": Base64Encoder()} 86 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/vtr.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python 2 | from .vtktypes import VtkRectilinear 3 | from .vtkxml import ( 4 | VtkAppendedDataElement, 5 | VtkCellDataElement, 6 | VtkCoordinatesElement, 7 | VtkExtent, 8 | VtkGridElement, 9 | VtkPieceElement, 10 | VtkPointDataElement, 11 | VtkRootElement, 12 | ) 13 | 14 | 15 | def get_elements(field, data_format="ascii", encoding="ascii"): 16 | if data_format == "appended": 17 | data = VtkAppendedDataElement("", encoding=encoding) 18 | else: 19 | data = None 20 | 21 | extent = VtkExtent(field.get_shape()[::-1]) 22 | 23 | element = { 24 | "VTKFile": VtkRootElement(VtkRectilinear), 25 | "Grid": VtkGridElement(VtkRectilinear, WholeExtent=extent), 26 | "Piece": VtkPieceElement(Extent=extent), 27 | "Coordinates": VtkCoordinatesElement( 28 | field.get_xyz_coordinates(), append=data, encoding=encoding 29 | ), 30 | "PointData": VtkPointDataElement( 31 | field.get_point_fields(), append=data, encoding=encoding 32 | ), 33 | "CellData": VtkCellDataElement( 34 | field.get_cell_fields(), append=data, encoding=encoding 35 | ), 36 | } 37 | 38 | if data is not None: 39 | element["AppendedData"] = data 40 | 41 | return element 42 | -------------------------------------------------------------------------------- /deprecated/printers/vtk/vts.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python 2 | from .vtktypes import VtkStructured 3 | from .vtkxml import ( 4 | VtkAppendedDataElement, 5 | VtkCellDataElement, 6 | VtkExtent, 7 | VtkGridElement, 8 | VtkPieceElement, 9 | VtkPointDataElement, 10 | VtkPointsElement, 11 | VtkRootElement, 12 | ) 13 | 14 | 15 | def get_elements(field, data_format="ascii", encoding="ascii"): 16 | if data_format == "appended": 17 | data = VtkAppendedDataElement("", encoding=encoding) 18 | else: 19 | data = None 20 | 21 | extent = VtkExtent(field.get_shape()[::-1]) 22 | 23 | element = { 24 | "VTKFile": VtkRootElement(VtkStructured), 25 | "Grid": VtkGridElement(VtkStructured, WholeExtent=extent), 26 | "Piece": VtkPieceElement(Extent=extent), 27 | "Points": VtkPointsElement(field.get_xyz(), append=data, encoding=encoding), 28 | "PointData": VtkPointDataElement( 29 | field.get_point_fields(), append=data, encoding=encoding 30 | ), 31 | "CellData": VtkCellDataElement( 32 | field.get_cell_fields(), append=data, encoding=encoding 33 | ), 34 | } 35 | 36 | if data is not None: 37 | element["AppendedData"] = data 38 | 39 | return element 40 | -------------------------------------------------------------------------------- /deprecated/status.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python 2 | """ 3 | >>> from __future__ import print_function 4 | >>> a = CREATED 5 | >>> print(a) 6 | Created 7 | >>> b = INITIALIZING 8 | >>> print(b) 9 | Initializing 10 | >>> c = INITIALIZED 11 | >>> print(c) 12 | Initialized 13 | >>> (a>b, a==b, a>> (a>=a, a==a, a<=a) 16 | (True, True, True) 17 | """ 18 | 19 | 20 | class Status: 21 | def __init__(self, string, val): 22 | self.string = string 23 | self.val = val 24 | 25 | def __str__(self): 26 | return self.string 27 | 28 | def __gt__(self, other): 29 | return self.val > other.val 30 | 31 | def __ge__(self, other): 32 | return self.val >= other.val 33 | 34 | def __eq__(self, other): 35 | return self.val == other.val 36 | 37 | def __lt__(self, other): 38 | return self.val < other.val 39 | 40 | def __le__(self, other): 41 | return self.val <= other.val 42 | 43 | 44 | class StatusConst(Status): 45 | def __init__(self): 46 | pass 47 | 48 | 49 | class StatusCreated(StatusConst): 50 | (string, val) = ("Created", 0) 51 | 52 | 53 | class StatusInitializing(StatusConst): 54 | (string, val) = ("Initializing", 10) 55 | 56 | 57 | class StatusInitialized(StatusConst): 58 | (string, val) = ("Initialized", 20) 59 | 60 | 61 | class StatusUpdating(StatusConst): 62 | (string, val) = ("Updating", 30) 63 | 64 | 65 | class StatusUpdated(StatusConst): 66 | (string, val) = ("Updated", 40) 67 | 68 | 69 | class StatusFinalizing(StatusConst): 70 | (string, val) = ("Finalizing", 50) 71 | 72 | 73 | class StatusFinalized(StatusConst): 74 | (string, val) = ("Finalized", 60) 75 | 76 | 77 | CREATED = StatusCreated() 78 | INITIALIZING = StatusInitializing() 79 | INITIALIZED = StatusInitialized() 80 | UPDATING = StatusUpdating() 81 | UPDATED = StatusUpdated() 82 | FINALIZING = StatusFinalizing() 83 | FINALIZED = StatusFinalized() 84 | 85 | if __name__ == "__main__": 86 | import doctest 87 | 88 | doctest.testmod() 89 | -------------------------------------------------------------------------------- /deprecated/testing/assertions.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def assert_isfile(filename): 5 | if not os.path.isfile(filename): 6 | raise AssertionError("%s is not a file" % filename) 7 | 8 | 9 | def assert_isfile_and_remove(filename): 10 | try: 11 | assert_isfile(filename) 12 | except AssertionError: 13 | raise 14 | else: 15 | os.remove(filename) 16 | -------------------------------------------------------------------------------- /deprecated/tests/test_ordered_tasks.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import unittest 4 | 5 | from pymt.ordered_task_status import OrderedTaskStatus 6 | 7 | 8 | class TestOrderedTaskStatus(unittest.TestCase): 9 | def test_init(self): 10 | queue = OrderedTaskStatus() 11 | self.assertEqual("create", queue.task) 12 | self.assertEqual("idling", queue.status) 13 | self.assertListEqual( 14 | ["create", "initialize", "update", "finalize"], queue.tasks 15 | ) 16 | 17 | def test_start_task(self): 18 | queue = OrderedTaskStatus() 19 | queue.start_task("create") 20 | self.assertEqual("create", queue.task) 21 | self.assertEqual("creating", queue.status) 22 | 23 | def test_start_completed_task(self): 24 | queue = OrderedTaskStatus() 25 | queue.start_task("create") 26 | queue.complete_task() 27 | 28 | with self.assertRaises(ValueError): 29 | queue.start_task("create") 30 | 31 | def test_restart_completed_task(self): 32 | queue = OrderedTaskStatus() 33 | queue.start_task("create") 34 | queue.complete_task() 35 | 36 | queue.start_task("create", allow_restart=True) 37 | self.assertEqual("create", queue.task) 38 | self.assertEqual("creating", queue.status) 39 | 40 | def test_restart_previous_task(self): 41 | queue = OrderedTaskStatus() 42 | queue.start_task("create") 43 | queue.complete_task() 44 | 45 | queue.start_task("initialize") 46 | queue.complete_task() 47 | 48 | with self.assertRaises(ValueError): 49 | queue.start_task("create", allow_restart=True) 50 | 51 | def test_start_next_before_finish_current(self): 52 | queue = OrderedTaskStatus() 53 | queue.start_task("create") 54 | with self.assertRaises(ValueError): 55 | queue.start_task("initialize") 56 | 57 | def test_complete_task(self): 58 | queue = OrderedTaskStatus() 59 | queue.start_task("create") 60 | queue.complete_task() 61 | self.assertEqual("create", queue.task) 62 | self.assertEqual("created", queue.status) 63 | 64 | def test_complete_before_started(self): 65 | queue = OrderedTaskStatus() 66 | with self.assertRaises(ValueError): 67 | queue.complete_task() 68 | 69 | def test_all_tasks(self): 70 | queue = OrderedTaskStatus() 71 | for task in queue.tasks: 72 | queue.start_task(task) 73 | queue.complete_task() 74 | 75 | def test_skip_task(self): 76 | queue = OrderedTaskStatus() 77 | queue.start_task("create") 78 | queue.complete_task() 79 | 80 | tasks = queue.tasks 81 | for task in tasks[2:]: 82 | with self.assertRaises(ValueError): 83 | queue.start_task(task) 84 | 85 | def test_move_backward(self): 86 | queue = OrderedTaskStatus() 87 | for task in queue.tasks: 88 | queue.start_task(task) 89 | queue.complete_task() 90 | 91 | tasks = queue.tasks 92 | for task in tasks[:-1]: 93 | with self.assertRaises(ValueError): 94 | queue.start_task(task) 95 | -------------------------------------------------------------------------------- /deprecated/tests/test_task_status.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import unittest 3 | 4 | from pymt.task_status import TaskStatus 5 | 6 | 7 | class TestTaskStatus(unittest.TestCase): 8 | def test_init(self): 9 | task = TaskStatus("task name") 10 | self.assertEqual(task.name, "task name") 11 | self.assertEqual(task.status, task.idling) 12 | 13 | def test_init_with_kwds(self): 14 | task = TaskStatus("initialize", started="initializing", completed="initialized") 15 | 16 | self.assertEqual(task.name, "initialize") 17 | self.assertEqual(task.idling, "idling") 18 | self.assertEqual(task.started, "initializing") 19 | self.assertEqual(task.completed, "initialized") 20 | 21 | def test_start(self): 22 | task = TaskStatus("task name") 23 | self.assertEqual(task.status, task.idling) 24 | 25 | task.start() 26 | self.assertEqual(task.status, task.started) 27 | 28 | def test_complete(self): 29 | task = TaskStatus("task name") 30 | task.start() 31 | task.complete() 32 | self.assertEqual(task.status, task.completed) 33 | 34 | def test_start_already_started(self): 35 | task = TaskStatus("task") 36 | 37 | for _ in range(10): 38 | task.start() 39 | self.assertEqual(task.status, task.started) 40 | 41 | def test_start_already_completed(self): 42 | task = TaskStatus("task") 43 | 44 | task.start() 45 | task.complete() 46 | with self.assertRaises(ValueError): 47 | task.start() 48 | 49 | task.start(allow_restart=True) 50 | self.assertTrue(task.is_started()) 51 | 52 | def test_complete_unstarted(self): 53 | task = TaskStatus("task name") 54 | with self.assertRaises(ValueError): 55 | task.complete() 56 | 57 | def test_complete_already_completed(self): 58 | task = TaskStatus("task name") 59 | task.start() 60 | task.complete() 61 | task.complete() 62 | 63 | def test_update_to(self): 64 | task = TaskStatus("task") 65 | task.update_to("completed") 66 | self.assertEqual(task.completed, task.status) 67 | self.assertTrue(task.is_completed()) 68 | 69 | def test_is_started(self): 70 | task = TaskStatus("task") 71 | self.assertFalse(task.is_started()) 72 | 73 | task.start() 74 | self.assertTrue(task.is_started()) 75 | 76 | task.complete() 77 | self.assertFalse(task.is_started()) 78 | 79 | def test_is_completed(self): 80 | task = TaskStatus("task") 81 | self.assertFalse(task.is_completed()) 82 | 83 | task.start() 84 | self.assertFalse(task.is_completed()) 85 | 86 | task.complete() 87 | self.assertTrue(task.is_completed()) 88 | 89 | def test_str(self): 90 | task = TaskStatus("my_task") 91 | self.assertEqual(str(task), "my_task: idling") 92 | 93 | task.start() 94 | self.assertEqual(str(task), "my_task: started") 95 | 96 | task.complete() 97 | self.assertEqual(str(task), "my_task: completed") 98 | 99 | task = TaskStatus("my_task", started="creating", completed="created") 100 | self.assertEqual(str(task), "my_task: idling") 101 | 102 | task.start() 103 | self.assertEqual(str(task), "my_task: creating") 104 | 105 | task.complete() 106 | self.assertEqual(str(task), "my_task: created") 107 | -------------------------------------------------------------------------------- /deprecated/utils/decorators.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 3 | import textwrap 4 | import warnings 5 | from functools import wraps 6 | 7 | 8 | class cache_result_in_object: 9 | def __init__(self, cache_as=None): 10 | self._attr = cache_as 11 | 12 | def __call__(self, func): 13 | name = self._attr or "_" + func.__name__ 14 | 15 | @wraps(func) 16 | def _wrapped(obj): 17 | if not hasattr(obj, name): 18 | setattr(obj, name, func(obj)) 19 | return getattr(obj, name) 20 | 21 | return _wrapped 22 | 23 | 24 | class deprecated: 25 | 26 | """Mark a function as deprecated.""" 27 | 28 | def __init__(self, use=None): 29 | self._use = use 30 | 31 | def __call__(self, func): 32 | doc_lines = (func.__doc__ or "").split(os.linesep) 33 | 34 | for lineno, line in enumerate(doc_lines): 35 | if len(line.rstrip()) == 0: 36 | break 37 | 38 | head = doc_lines[:lineno] 39 | body = doc_lines[lineno:] 40 | 41 | head = textwrap.dedent(os.linesep.join(head)) 42 | body = textwrap.dedent(os.linesep.join(body)) 43 | 44 | doc_lines = [head, ".. note:: deprecated", ""] 45 | if self._use is not None: 46 | doc_lines.extend( 47 | [" Use :func:`{use}` instead.".format(use=self._use), ""] 48 | ) 49 | doc_lines.append(body) 50 | 51 | func.__doc__ = os.linesep.join(doc_lines) 52 | 53 | @wraps(func) 54 | def _wrapped(*args, **kwargs): 55 | if func.__name__.startswith("_"): 56 | pass 57 | else: 58 | warnings.warn( 59 | message="Call to deprecated function {name}.".format( 60 | name=func.__name__ 61 | ) 62 | ) 63 | # category=DeprecationWarning) 64 | return func(*args, **kwargs) 65 | 66 | return _wrapped 67 | -------------------------------------------------------------------------------- /deprecated/utils/run_dir.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import shutil 4 | 5 | 6 | class cd: 7 | def __init__(self, path, create=False): 8 | self._dir = os.path.abspath(path) 9 | self._starting_dir = os.path.abspath(os.getcwd()) 10 | self._create = create 11 | 12 | def __enter__(self): 13 | if self._create and not os.path.isdir(self._dir): 14 | os.makedirs(self._dir) 15 | os.chdir(self._dir) 16 | return os.path.abspath(os.getcwd()) 17 | 18 | def __exit__(self, exception_type, value, traceback): 19 | os.chdir(self._starting_dir) 20 | 21 | 22 | class cd_temp: 23 | """Create a temporary directory and change to it. 24 | 25 | Parameters 26 | ---------- 27 | kwds : dict (optional) 28 | All keywords are passed directory to *tempfile.mkdtemp*. 29 | 30 | Returns 31 | ------- 32 | path : str 33 | Absolute path to the newly-created temporary directory. 34 | 35 | Examples 36 | -------- 37 | >>> from pymt.utils.run_dir import cd_temp 38 | >>> import os 39 | >>> starting_dir = os.getcwd() 40 | >>> with cd_temp() as path: 41 | ... temp_dir = os.getcwd() 42 | >>> temp_dir == path 43 | True 44 | >>> temp_dir != starting_dir 45 | True 46 | >>> os.path.exists(temp_dir) 47 | False 48 | """ 49 | 50 | def __init__(self, **kwds): 51 | self._dir = os.path.abspath(tempfile.mkdtemp(**kwds)) 52 | self._starting_dir = os.path.abspath(os.getcwd()) 53 | 54 | def __enter__(self): 55 | os.chdir(self._dir) 56 | return os.path.abspath(os.getcwd()) 57 | 58 | def __exit__(self, exception_type, value, traceback): 59 | os.chdir(self._starting_dir) 60 | shutil.rmtree(self._dir) 61 | 62 | 63 | class RunDir: 64 | def __init__(self, directory, create=False): 65 | self._run_dir = directory 66 | self._create = create 67 | 68 | def __enter__(self): 69 | self._starting_dir = os.path.abspath(os.getcwd()) 70 | if self._create and not os.path.isdir(self._run_dir): 71 | os.makedirs(self._run_dir) 72 | os.chdir(self._run_dir) 73 | 74 | def __exit__(self, exception_type, value, traceback): 75 | os.chdir(self._starting_dir) 76 | 77 | 78 | def open_run_dir(directory, **kwds): 79 | return RunDir(directory, **kwds) 80 | -------------------------------------------------------------------------------- /deprecated/utils/tests/test_run_dir.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | import pytest 5 | 6 | from pymt.utils.run_dir import open_run_dir 7 | 8 | 9 | def test_run_dir(): 10 | init_dir = os.getcwd() 11 | with open_run_dir("/"): 12 | assert os.getcwd() == os.path.normpath("/") 13 | assert os.getcwd() == init_dir 14 | 15 | 16 | def test_relative_dir(): 17 | init_dir = os.getcwd() 18 | with open_run_dir(".."): 19 | assert os.getcwd() == os.path.abspath(os.path.join(init_dir, "..")) 20 | assert os.getcwd() == init_dir 21 | 22 | 23 | def test_nested_dirs(): 24 | init_dir = os.getcwd() 25 | with open_run_dir("/"): 26 | assert os.getcwd() == os.path.normpath("/") 27 | with open_run_dir("/bin"): 28 | assert os.getcwd() == os.path.normpath("/bin") 29 | assert os.getcwd() == os.path.normpath("/") 30 | assert os.getcwd() == init_dir 31 | 32 | 33 | def test_nested_relative_dirs(): 34 | init_dir = os.getcwd() 35 | with open_run_dir("/usr/bin"): 36 | assert os.getcwd() == os.path.normpath("/usr/bin") 37 | with open_run_dir(".."): 38 | assert os.getcwd() == os.path.normpath("/usr") 39 | assert os.getcwd() == os.path.normpath("/usr/bin") 40 | assert os.getcwd() == init_dir 41 | 42 | 43 | def test_dir_does_not_exist(): 44 | with pytest.raises(OSError): 45 | with open_run_dir("/not/a/real/dir"): 46 | pass 47 | 48 | 49 | def test_negative_dir(): 50 | init_dir = os.getcwd() 51 | with open_run_dir("/"): 52 | assert os.getcwd() == "/" 53 | with open_run_dir(".."): 54 | assert os.getcwd() == "/" 55 | assert os.getcwd() == "/" 56 | assert os.getcwd() == init_dir 57 | 58 | 59 | def test_do_not_gobble_exception(): 60 | init_dir = os.getcwd() 61 | try: 62 | with open_run_dir("/usr"): 63 | assert os.getcwd() == "/usr" 64 | raise ValueError() 65 | except ValueError: 66 | assert os.getcwd() == init_dir 67 | else: 68 | raise AssertionError("statement should not be reached") 69 | 70 | 71 | def test_missing_dir(): 72 | init_dir = os.getcwd() 73 | with pytest.raises(OSError): 74 | with open_run_dir("./not/a/dir"): 75 | pass 76 | assert not os.path.isdir(os.path.join(init_dir, "not/a/dir")) 77 | 78 | 79 | def test_create_missing_dir(): 80 | init_dir = os.getcwd() 81 | with open_run_dir("./not/a/dir", create=True): 82 | assert os.getcwd() == os.path.normpath(os.path.join(init_dir, "not/a/dir")) 83 | assert os.getcwd() == init_dir 84 | assert os.path.isdir(os.path.normpath(os.path.join(init_dir, "not/a/dir"))) 85 | shutil.rmtree("./not") 86 | -------------------------------------------------------------------------------- /deprecated/utils/tests/test_which.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymt.utils.which import which 4 | 5 | 6 | def test_not_in_path(): 7 | assert which("does-not-exist") is None 8 | 9 | 10 | def test_path_is_dir(): 11 | assert which(os.getcwd()) is None 12 | 13 | 14 | def test_relative_path_is_executable(): 15 | assert isinstance(which("python"), str) 16 | assert os.path.isabs(which("python")) 17 | 18 | 19 | def test_abs_path_is_executable(): 20 | python = which("python") 21 | assert python == which(python) 22 | -------------------------------------------------------------------------------- /deprecated/utils/verbose.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import logging 4 | import os 5 | import sys 6 | 7 | _LEVEL_STRING = {"DEBUG": 10, "INFO": 20, "WARNING": 30, "ERROR": 40} 8 | _CMT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 9 | 10 | 11 | def log_level_string_to_int(level): 12 | if level.upper() in ["TRUE", "YES", "ON", "ENABLED"]: 13 | verbosity = sys.maxsize 14 | elif level.upper() in ["FALSE", "NO", "OFF", "DISABLED"]: 15 | verbosity = 0 16 | else: 17 | try: 18 | verbosity = _LEVEL_STRING[level] 19 | except KeyError: 20 | raise ValueError(level) 21 | 22 | return verbosity 23 | 24 | 25 | def get_log_level_from_environ(level): 26 | try: 27 | level = int(os.environ["CMT_VERBOSE"]) 28 | except KeyError: 29 | pass 30 | except ValueError: 31 | level = log_level_string_to_int(level) 32 | 33 | return level 34 | 35 | 36 | def setup_pymt_logging_channel(): 37 | ch = logging.StreamHandler() 38 | ch.setLevel(logging.DEBUG) 39 | formatter = logging.Formatter(_CMT_LOG_FORMAT) 40 | ch.setFormatter(formatter) 41 | return ch 42 | 43 | 44 | class CmtLogger(logging.Logger): 45 | def __init__(self, name, level): 46 | level = get_log_level_from_environ(level) 47 | 48 | logging.Logger.__init__(self, name, level=level) 49 | ch = setup_pymt_logging_channel() 50 | self.addHandler(ch) 51 | 52 | 53 | class Verbose: 54 | def __init__(self, verbosity=0, log=sys.stderr): 55 | self._verbosity = verbosity 56 | self._log = log 57 | 58 | def __call__(self, verbosity, msg): 59 | if verbosity <= self._verbosity: 60 | print(self._construct_msg(verbosity, msg), file=self._log) 61 | 62 | @staticmethod 63 | def _construct_msg(verbosity, msg): 64 | if verbosity == 0: 65 | return msg 66 | else: 67 | return "*" * verbosity + " " + msg 68 | 69 | 70 | class CmtVerbose(Verbose): 71 | """ 72 | >>> if 'CMT_VERBOSE' in os.environ: del os.environ['CMT_VERBOSE'] 73 | >>> verbose = CmtVerbose (1, log=sys.stdout) 74 | >>> verbose (1, "Level one") 75 | #CMT Level one 76 | >>> verbose (2, "Level two") 77 | >>> verbose (0, "Level %d" % 0) 78 | #CMT Level 0 79 | 80 | >>> os.environ['CMT_VERBOSE'] = '3' 81 | >>> verbose = CmtVerbose (log=sys.stdout) 82 | >>> verbose (1, "Level one") 83 | #CMT Level one 84 | >>> verbose (2, "Level two") 85 | #CMT Level two 86 | >>> verbose (4, "Level %d" % 0) 87 | 88 | >>> os.environ['CMT_VERBOSE'] = 'False' 89 | >>> verbose = CmtVerbose (log=sys.stdout) 90 | >>> verbose (0, 'No message') 91 | #CMT No message 92 | >>> verbose (1, 'No message') 93 | 94 | >>> os.environ['CMT_VERBOSE'] = 'True' 95 | >>> verbose = CmtVerbose (log=sys.stdout) 96 | >>> verbose (1, 'No message') 97 | #CMT No message 98 | >>> verbose (0, 'No message') 99 | #CMT No message 100 | """ 101 | 102 | def __init__(self, verbosity=1, log=sys.stderr): 103 | if "CMT_VERBOSE" in os.environ: 104 | level = os.environ["CMT_VERBOSE"] 105 | if level.upper() in ["TRUE", "YES", "ON", "ENABLED"]: 106 | verbosity = sys.maxsize 107 | elif level.upper() in ["FALSE", "NO", "OFF", "DISABLED"]: 108 | verbosity = 0 109 | else: 110 | try: 111 | verbosity = int(level) 112 | except ValueError: 113 | verbosity = 0 114 | 115 | Verbose.__init__(self, verbosity, log) 116 | 117 | @staticmethod 118 | def _construct_msg(verbosity, msg): 119 | return "#CMT " + msg.strip() 120 | -------------------------------------------------------------------------------- /deprecated/utils/which.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def file_is_executable(path_to_file, mode=os.F_OK | os.X_OK): 5 | return os.path.isfile(path_to_file) and os.access(path_to_file, mode) 6 | 7 | 8 | def find_executable_in_paths(prog, paths): 9 | for path in paths: 10 | path_to_file = os.path.join(path, prog) 11 | if file_is_executable(path_to_file): 12 | return path_to_file 13 | return None 14 | 15 | 16 | # def which(prog, mode=os.F_OK | os.X_OK, path=None): 17 | def which(prog, path=None): 18 | if os.path.isabs(prog) and file_is_executable(prog): 19 | return prog 20 | else: 21 | if path is None: 22 | try: 23 | path = os.environ["PATH"].split(os.pathsep) 24 | except KeyError: 25 | path = os.defpath.split(os.pathsep) 26 | return find_executable_in_paths(prog, path) 27 | return None 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = pymt 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/apple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_static/hydrotrend-discharge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/docs/_static/hydrotrend-discharge.png -------------------------------------------------------------------------------- /docs/_static/linux.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_static/powered-by-logo-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/docs/_static/powered-by-logo-header.png -------------------------------------------------------------------------------- /docs/_static/pymt-logo-header-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/docs/_static/pymt-logo-header-text.png -------------------------------------------------------------------------------- /docs/_static/pymt-logo-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/docs/_static/pymt-logo-header.png -------------------------------------------------------------------------------- /docs/_static/windows.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_templates/links.html: -------------------------------------------------------------------------------- 1 |

Useful Links

2 | 10 | 11 | -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 |

About pymt

2 |

3 | pymt is a Python toolkit for running and coupling Earth surface 4 | models. 5 |

6 | -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | Developer Documentation 2 | ----------------------- 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | modules 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conda-environments.rst: -------------------------------------------------------------------------------- 1 | Conda environments 2 | ================== 3 | 4 | *conda* environments is probably what you want to use for developing *pymt* 5 | applications. 6 | 7 | What problem does conda environments solve? Chances are that you want to use it 8 | for other projects besides your *pymt* script. But the more projects you 9 | have, the more likely it is that you will be working with different 10 | versions of Python itself, or at least different versions of Python 11 | libraries. Let's face it: quite often libraries break backwards 12 | compatibility, and it's unlikely that any serious application will have 13 | zero dependencies. So what do you do if two or more of your projects have 14 | conflicting dependencies? 15 | 16 | *conda* environments to the rescue! *conda* environments enables multiple 17 | side-by-side installations of Python, one for each project. It doesn't actually 18 | install separate copies of Python, but it does provide a clever way to 19 | keep different project environments isolated. 20 | 21 | If you are on Mac or Linux, and have *mamba* installed, from a terminal you 22 | can run the following to create a new Python environment, 23 | 24 | .. code-block:: bash 25 | 26 | $ mamba create -n myproject python 27 | 28 | Now, whenever you want to work on a project, you only have to activate the 29 | corresponding environment. On OS X and Linux, do the following, 30 | 31 | .. code-block:: bash 32 | 33 | $ mamba activate myproject 34 | 35 | To get out of the environment, 36 | 37 | .. code-block:: bash 38 | 39 | $ conda deactivate 40 | 41 | To remove the environment, 42 | 43 | .. code-block:: bash 44 | 45 | $ mamba remove -n myproject --all 46 | 47 | With this environment activated, you can install *pymt* into it with the 48 | following, 49 | 50 | .. code-block:: bash 51 | 52 | $ mamba install pymt -c conda-forge 53 | 54 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/environment.yml: -------------------------------------------------------------------------------- 1 | name: pymt_docs 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python=3.6 6 | - pandoc 7 | - pip 8 | - nbformat 9 | - jupyter_client 10 | - ipython 11 | - sphinx>=1.5.1 12 | - sphinx_rtd_theme 13 | - tornado 14 | - entrypoints 15 | - pip: 16 | - nbsphinx>=0.2.12 17 | - sphinxcontrib_github_alt 18 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: _static/pymt-logo-header-text.png 2 | :align: center 3 | :scale: 75% 4 | :alt: Python Modeling Tool 5 | :target: https://pymt.readthedocs.org/ 6 | 7 | .. title:: PyMT 8 | 9 | *pymt* is the *Python Modeling Toolkit*. 10 | It is an Open Source Python package, developed by the 11 | `Community Surface Dynamics Modeling System`_ (CSDMS), 12 | that provides the tools needed for coupling models that expose the 13 | `Basic Model Interface`_ (BMI). 14 | 15 | *pymt* in three points: 16 | 17 | * Tools for coupling models of disparate time and space scales 18 | * A collection of Earth-surface models 19 | * Extensible plug-in framework for adding new models 20 | 21 | 22 | What does it look like? Here is an example of a simple *pymt* program: 23 | 24 | .. code-block:: python 25 | 26 | from pymt.models import Cem, Waves 27 | 28 | waves = Waves() 29 | cem = Cem() 30 | 31 | waves.initialize(*waves.setup()) 32 | cem.initialize(*cem.setup()) 33 | 34 | for time in range(1000): 35 | waves.update() 36 | angle = waves.get_value("wave_angle") 37 | cem.set_value("wave_angle", angle) 38 | cem.update() 39 | 40 | 41 | *pymt* is an element of the `CSDMS Workbench`_, 42 | an integrated system of software tools, technologies, and standards 43 | for building and coupling models. 44 | 45 | 46 | User Guide 47 | ---------- 48 | 49 | If you are looking for information on using *pymt* 50 | to configure, run, and couple models, 51 | this part of the documentation is for you. 52 | 53 | .. toctree:: 54 | :maxdepth: 1 55 | :hidden: 56 | 57 | Summary 58 | 59 | * :doc:`Summary ` 60 | 61 | .. toctree:: 62 | :maxdepth: 2 63 | 64 | quickstart 65 | install 66 | usage 67 | models 68 | examples 69 | glossary 70 | 71 | 72 | API Reference 73 | ------------- 74 | 75 | If you are looking for information on a specific function, class, or 76 | method, this part of the documentation is for you. 77 | 78 | .. toctree:: 79 | :maxdepth: 2 80 | 81 | api/index 82 | 83 | 84 | Miscellaneous Pages 85 | ------------------- 86 | 87 | .. toctree:: 88 | :maxdepth: 2 89 | 90 | conda-environments 91 | contributing 92 | authors 93 | history 94 | license 95 | 96 | 97 | Help 98 | ---- 99 | 100 | If you encounter any problems when using *pymt*, 101 | please visit us at the `CSDMS Help Desk`_ 102 | and explain what occurred. 103 | Or, 104 | if you're developing with *pymt* 105 | feel free to open an `issue`_ 106 | in the *pymt* GitHub repository. 107 | 108 | 109 | Indices and tables 110 | ================== 111 | * :ref:`genindex` 112 | * :ref:`modindex` 113 | * :ref:`search` 114 | 115 | .. 116 | Links 117 | 118 | .. _Community Surface Dynamics Modeling System: https://csdms.colorado.edu 119 | .. _Basic Model Interface: https://bmi.readthedocs.io 120 | .. _CSDMS Workbench: https://csdms.colorado.edu/wiki/Workbench 121 | .. _CSDMS Help Desk: https://github.com/csdms/help-desk 122 | .. _issue: https://github.com/csdms/pymt/issues 123 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | There are two ways to install *pymt* on your local machine: 8 | 9 | * :ref:`stable-release` 10 | * :ref:`from-source` (advanced option) 11 | 12 | If you plan to use *pymt* to run and couple models, 13 | installing a :ref:`stable release` 14 | is a good option. 15 | However, if you intend to develop with *pymt*, 16 | possibly modifying it, 17 | it's best to install it 18 | :ref:`from source`. 19 | 20 | If you encounter any problems when installing *pymt*, 21 | please visit us at the `CSDMS Help Desk`_ 22 | and explain what occurred. 23 | 24 | .. _CSDMS Help Desk: https://github.com/csdms/help-desk 25 | 26 | 27 | .. _stable-release: 28 | 29 | Stable release 30 | -------------- 31 | 32 | .. include:: installation-environment.rst 33 | 34 | Install *pymt* into this conda environment with: 35 | 36 | .. code-block:: console 37 | 38 | $ mamba install pymt 39 | 40 | Note that *pymt* is built on several open source software 41 | libraries, so it may take a few minutes for *mamba* to find, 42 | download, and install all of them. 43 | 44 | .. include:: installation-check.rst 45 | 46 | 47 | .. _from-source: 48 | 49 | From source 50 | ----------- 51 | 52 | .. include:: installation-environment.rst 53 | 54 | The source code for *pymt* can be accessed from its `Github repository`_. 55 | 56 | You can either clone the public repository: 57 | 58 | .. code-block:: console 59 | 60 | $ git clone git://github.com/csdms/pymt 61 | 62 | Or download the `tarball`_: 63 | 64 | .. code-block:: console 65 | 66 | $ curl -OL https://github.com/csdms/pymt/tarball/master 67 | $ tar -xf master 68 | 69 | Once you have a copy of the source, 70 | change into the source directory and 71 | install the dependencies required by *pymt* into the conda environment 72 | you created above: 73 | 74 | .. code-block:: console 75 | 76 | $ mamba install --file=requirements.txt 77 | 78 | Then install *pymt* with: 79 | 80 | .. code-block:: console 81 | 82 | $ pip install -e . 83 | 84 | .. include:: installation-check.rst 85 | 86 | 87 | .. _Github repository: https://github.com/csdms/pymt 88 | .. _tarball: https://github.com/csdms/pymt/tarball/master 89 | -------------------------------------------------------------------------------- /docs/installation-check.rst: -------------------------------------------------------------------------------- 1 | You're now ready to start using *pymt*. 2 | Check the installation by starting a Python session 3 | and importing *pymt*: 4 | 5 | .. code-block:: python 6 | 7 | >>> import pymt 8 | 9 | A default set of models is included in the *pymt* install: 10 | 11 | .. code-block:: python 12 | 13 | >>> for model in pymt.MODELS: 14 | ... print(model) 15 | ... 16 | Avulsion 17 | Plume 18 | Sedflux3D 19 | Subside 20 | FrostNumber 21 | Ku 22 | Hydrotrend 23 | Child 24 | Cem 25 | Waves 26 | -------------------------------------------------------------------------------- /docs/installation-environment.rst: -------------------------------------------------------------------------------- 1 | We strongly recommend using the *Anaconda* scientific computing environment. 2 | If you don't have it installed, the `Anaconda installation guide`_ 3 | can help you through the process. 4 | 5 | Once you've installed Anaconda, we suggest using the :term:`mamba` package manager. 6 | *mamba* is pretty much the same as :term:`conda`, only faster. 7 | If you would rather stick with *conda*, just 8 | replace occurrences of *mamba* with *conda*. 9 | 10 | .. code-block:: console 11 | 12 | $ conda install mamba -c conda-forge 13 | 14 | Add the :term:`conda-forge` channel 15 | to the list of enabled conda channels on your machine: 16 | 17 | .. code-block:: console 18 | 19 | $ mamba config --add channels conda-forge 20 | 21 | We advise installing *pymt* into a :term:`conda environment`. 22 | Conda environments can easily be created and discarded. 23 | Create an environment for *pymt* with: 24 | 25 | .. code-block:: console 26 | 27 | $ mamba create -n pymt python=3 28 | 29 | Once the conda environment has been created, activate it with: 30 | 31 | .. code-block:: console 32 | 33 | $ conda activate pymt 34 | 35 | .. _Anaconda installation guide: http://docs.anaconda.com/anaconda/install/ 36 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | *pymt* is licensed under the MIT license, a short, permissive software license. 5 | Basically, you can do whatever you want as long as you include the original 6 | copyright and license notice in any copy of the software/source. 7 | 8 | License Text 9 | ------------ 10 | 11 | .. include:: ../LICENSE 12 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=pymt 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. title:: Summary 2 | 3 | .. include:: ../README.rst 4 | -------------------------------------------------------------------------------- /docs/scripts/make_table.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 3 | import textwrap 4 | 5 | import click 6 | import yaml 7 | from tabulate import tabulate 8 | 9 | 10 | def construct_rows(notebook_index=None): 11 | import pymt.models 12 | 13 | notebook_index = notebook_index or {} 14 | 15 | rows = [] 16 | for name in pymt.models.__all__: 17 | summary = pymt.models.__dict__[name]().summary 18 | if name in notebook_index: 19 | summary = os.linesep.join( 20 | [ 21 | summary, 22 | "", 23 | f"|binder-{name}|", 24 | ] 25 | ) 26 | rows.append((name, summary)) 27 | 28 | return rows 29 | 30 | 31 | @click.command() 32 | @click.argument("dest", type=click.File("w")) 33 | @click.option( 34 | "--notebook-index", 35 | type=click.File("r"), 36 | help="index mapping model names to notebooks", 37 | ) 38 | def make_table(dest, notebook_index): 39 | if notebook_index: 40 | index = yaml.safe_load(notebook_index) 41 | else: 42 | index = {} 43 | 44 | header = """ 45 | .. _available_models: 46 | 47 | Available Models 48 | ================ 49 | 50 | The following table lists the models that are currently available through 51 | pymt. 52 | 53 | """ 54 | print(textwrap.dedent(header).lstrip(), file=dest) 55 | 56 | rows = construct_rows(notebook_index=index) 57 | print(tabulate(sorted(rows), headers=("Summary",), tablefmt="rst"), file=dest) 58 | 59 | footer = [] 60 | for name, notebooks in index.items(): 61 | footer.append( 62 | f""" 63 | .. |binder-{name}| image:: https://mybinder.org/badge_logo.svg 64 | :target: https://mybinder.org/v2/gh/csdms/pymt.git/master?filepath=notebooks%2F{notebooks[0]} 65 | """ 66 | ) 67 | print(textwrap.dedent(os.linesep.join(footer)), file=dest) 68 | 69 | 70 | if __name__ == "__main__": 71 | make_table() 72 | -------------------------------------------------------------------------------- /environment-docs.yml: -------------------------------------------------------------------------------- 1 | name: pymt-docs 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - gimli.units 6 | - pip 7 | - pip: 8 | - -r requirements.txt 9 | - -r requirements-docs.txt 10 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: test-environment 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python=3.9 6 | - pymt 7 | - seaborn 8 | - matplotlib 9 | - pandas 10 | - numpy 11 | - pymt_cem 12 | - pymt_child 13 | - pymt_ecsimplesnow 14 | - pymt_gipl 15 | - pymt_hydrotrend 16 | - pymt_permamodel 17 | - pymt_sedflux 18 | -------------------------------------------------------------------------------- /news/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /notebooks/Ganges/HYDRO.IN: -------------------------------------------------------------------------------- 1 | Ganges AE V1 (Q0 1975-2100) developed from Darby 2015 2 | ON 2) Write output to ASCII files (ON/OFF) 3 | ./HYDRO_OUTPUT/ 3) Location where the output data will be stored (not optional for web) 4 | 1 4) No. of epochs to run (leave 1 line blank between epochs; start copying from nr 5) 5 | 1975 124 D 5) Start year; no. of years to run; averaging interval: D,M,S or Y 6 | 4 6) Number of suspended sed. grain sizes to simulate (max 10) 7 | .2 .2 .25 .35 7) Proportion of sediment in each grain size (sum=1) 8 | 21.05 0 0 8) Yrly Tbar: start (C), change/yr (C/a), std dev 9 | 0.44 0 0 9) Yrly P sum: start (m/a), change/yr (m/a/a), std. dev (m). 10 | 1 1.5 5 10) Rain: Mass Balance Coef, Distribution Exp, Distribution Range 11 | 884 11) Constant annual base flow (m^3/s) 12 | Jan 13.8 0.1 12 0.1 12-23) monthly climate variables 13 | Feb 17.3 0.1 9 0.1 column variable description 14 | Mar 22.6 0.1 6 0.1 ------ -------- ----------- 15 | Apr 27.5 0.1 5 0.1 1 moname month name (not used) 16 | May 31.3 0.1 12 0.1 2 tmpinm monthly mean T. (C) 17 | Jun 30.6 0.1 51 0.1 3 tmpstd within month SD of T 18 | Jul 27.8 0.1 119 0.1 4 raininm monthly total P. (mm) 19 | Aug 26.7 0.1 130 0.1 5 rainstd Std Dev of monthly P. 20 | Sep 26.5 0.1 64 0.1 . 21 | Oct 24.2 0.1 21 0.1 . 22 | Nov 19.6 0.1 5 0.1 . 23 | Dec 14.9 0.1 6 0.1 . 24 | 6.4 24) Lapse rate to calculate freezing line (degC/km) 25 | 5500 0 25) Starting glacier ELA (m) and ELA change per year (m/a) 26 | 0.45 26) Dry precip (nival and ice) evaporation fraction 27 | -0.1 0.85 26a) canopy interception alphag(-0.1(mm/d)), betag(0.85(-)) 28 | 10 1 26b) groundwater pole evapotranspiration alpha_gwe (common 10 (mm/d)), beta_gwe (common 1 (-)) 29 | 0.0000827 27) Delta plain gradient (m/m) 30 | 1 27a) Bedload rating term (-)(typically 1.0; if set to -9999, 1.0 will be used) 31 | 2525 28) River basin length (km) 32 | 42.0077 a80 29) Mean volume, (a)ltitude or (d)rainage area of reservoir (km^3)(m) or (km^2) 33 | 0.0842 0.34 30) River mouth velocity coef. (k) and exponent (m); v=kQ^m, w=aQ^b, d=cQ^f 34 | 197.426 0.26 31) River mouth width coef.(a) and exponent (b); Q=wvd so ack=1 and b+m+f=1 35 | 2 32) Average river velocity (m/s) 36 | 4.3e9 1 33) Maximum/minimum groundwater storage (m^3) 37 | 5.76e8 34) Initial groundwater storage (m^3) 38 | 5e4 1.25 35) Groundwater (subsurface storm flow) coefficient (m^3/s) and exp (unitless) 39 | 85 36) Saturated hydraulic conductivity (mm/day) 40 | 87.93 24.80 37) Longitude, latitude position of the river mouth (decimal degrees) 41 | 1 38) Nr. of outlets in a delta, 1 = no outlets; 42 | 1 39) Fraction Q for each outlet 43 | n1 40) Certain Qpeak, above this, it change the nr of outlets or the Q fr. distribution 44 | 0 41) Fraction sediment trapped in delta (0 - 0.9; only if 39 > 1 or u or r) 45 | 2 42) 0)=QRT; 1) =ART) 2) =BQART 46 | 1.05 43) if line 42 is 2: Lithology factor from hard - weak material (0.3 - 3) 47 | 2 44) if line 42 is 2: Anthropogenic factor (0.5 - 8), human disturbance of the landscape -------------------------------------------------------------------------------- /notebooks/Ganges/hydro_config.txt: -------------------------------------------------------------------------------- 1 | . 2 | HYDRO 3 | . -------------------------------------------------------------------------------- /notebooks/README.md: -------------------------------------------------------------------------------- 1 | # PyMT Demo Jupyter Notebooks 2 | This folder includes Jupyter Notebooks that provide an introduction to PyMT and its use for different models. 3 | 4 | ## Run Jupyter Notebooks on the CSDMS JupyterHub 5 | 6 | 1. [Create an account](https://csdms.rc.colorado.edu/hub/signup) on the CSDMS JupyterHub, providing a username and password -- they can be whatever you like 7 | 2. [Request authorization](https://github.com/csdms/help-desk/issues/new?assignees=mdpiper&labels=jupyterhub&template=new-csdms-jupyterhub-account.md&title=CSDMS+JupyterHub+account) for your new account through the CSDMS Help Desk -- if you don't already have a GitHub account, you'll be asked to make one 8 | 3. Once approved, [run Jupyter Notebooks](https://csdms.rc.colorado.edu/hub/user-redirect/git-pull?repo=https%3A%2F%2Fgithub.com%2Fcsdms%2Fpymt&urlpath=tree%2Fpymt%2Fnotebooks%2Fwelcome.ipynb&branch=master) 9 | 10 | ## Links 11 | 12 | * [Community Surface Dynamics Modeling System (CSDMS)](http://csdms.colorado.edu) 13 | * [Basic Model Interface (BMI)](http://bmi.readthedocs.io) 14 | * [Python Modeling Toolkit (pymt)](http://pymt.readthedocs.io) 15 | -------------------------------------------------------------------------------- /notebooks/RhineBedload.csv: -------------------------------------------------------------------------------- 1 | 4075,59.89 2 | 5405,74.03 3 | 5961,74.26 4 | 6173,78.28 5 | 5982,77.43 6 | 5494,93.07 7 | 4851,93.56 8 | 4101,103.71 9 | 3760,102.56 10 | 3425,107.23 11 | 6173,127.25 12 | 5982,112.88 13 | 5494,110.15 14 | 4851,107.66 15 | 3760,61.20 16 | 3425,55.76 17 | 3259,54.32 18 | 3041,58.18 19 | 1388,11.33 20 | 2113,19.89 21 | 1955,16.47 22 | 1629,19.21 23 | 2220,17.00 24 | 1796,10.93 25 | 1278,5.99 26 | 937,5.02 27 | 1629,9.18 28 | 2220,8.31 29 | 1796,6.59 30 | 1278,8.02 31 | 553,0.04 32 | 909,10.10 33 | 1267,12.29 34 | 958,9.10 35 | 709,7.36 36 | 503,7.62 37 | 90,0.00 38 | 319,0.00 39 | 3146,29.34 40 | 3769,72.77 41 | 1309,51.05 42 | 4527,77.26 43 | 3905,72.16 44 | 2358,9.53 45 | 2134,4.14 46 | 3146,21.82 47 | 3769,72.00 48 | 1309,84.73 49 | 4527,88.41 50 | 3905,69.81 51 | 2358,4.53 52 | 2134,2.17 53 | 1995,6.88 54 | 2276,35.66 55 | 2302,41.92 56 | 2092,25.97 57 | 1522,2.09 58 | 1369,6.63 59 | 1499,19.49 60 | 1641,20.76 61 | 1531,19.43 62 | 528,4.36 -------------------------------------------------------------------------------- /notebooks/conftest.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import pytest 4 | 5 | _TEST_DIR = pathlib.Path(__file__).absolute().parent 6 | 7 | 8 | def collect_notebooks(src): 9 | p = pathlib.Path(src) 10 | if p.is_dir(): 11 | return {_p.absolute() for _p in iter_notebooks_in_dir(p, src)} 12 | else: 13 | raise ValueError(f"{src}: not a directory") 14 | 15 | 16 | def iter_notebooks_in_dir(path, root): 17 | for s in path.iterdir(): 18 | p = pathlib.Path(s) 19 | normalized_path = "/" + p.resolve().relative_to(root).as_posix() 20 | 21 | if p.is_dir(): 22 | normalized_path += "/" 23 | if p.is_dir() and p.name not in (".git", ".ipynb_checkpoints"): 24 | yield from iter_notebooks_in_dir(p, root) 25 | elif p.is_file() and p.suffix == ".ipynb": 26 | yield p 27 | 28 | 29 | def pytest_generate_tests(metafunc): 30 | if "notebook" in metafunc.fixturenames: 31 | metafunc.parametrize( 32 | "notebook", sorted([str(name) for name in collect_notebooks(_TEST_DIR)]) 33 | ) 34 | 35 | 36 | def pytest_configure(config): 37 | config.addinivalue_line("markers", "notebook: mark test as a notebook to run") 38 | 39 | 40 | def pytest_collection_modifyitems(config, items): 41 | if not config.getoption("--run-notebook"): 42 | skip_notebook = pytest.mark.skip(reason="need --run-notebook option to run") 43 | for item in items: 44 | if "notebook" in item.keywords: 45 | item.add_marker(skip_notebook) 46 | -------------------------------------------------------------------------------- /notebooks/exclude.yml: -------------------------------------------------------------------------------- 1 | - file: cem.ipynb 2 | reason: "incomplete notebook" 3 | -------------------------------------------------------------------------------- /notebooks/test_notebooks.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import subprocess 3 | 4 | import pytest 5 | import yaml 6 | 7 | _exclude_file = pathlib.Path(__file__).absolute().parent / "exclude.yml" 8 | if _exclude_file.exists(): 9 | with open(_exclude_file) as fp: 10 | _EXCLUDE = {item["file"]: item["reason"] for item in yaml.safe_load(fp)} 11 | else: 12 | _EXCLUDE = {} 13 | 14 | 15 | def _notebook_check(notebook): 16 | """Check a notebook for errors. 17 | 18 | Parameters 19 | ---------- 20 | notebook : Notebook node 21 | Path the to notebook to execute. 22 | 23 | Returns 24 | ------- 25 | errors : list of str 26 | A list of the errors encountered in the notebook cells. 27 | """ 28 | errors = [ 29 | output 30 | for cell in notebook.cells 31 | if "outputs" in cell 32 | for output in cell["outputs"] 33 | if output.output_type == "error" 34 | ] 35 | 36 | return errors 37 | 38 | 39 | def _notebook_run(path_to_notebook): 40 | """Execute a notebook via nbconvert and collect output. 41 | 42 | Parameters 43 | ---------- 44 | path_to_notebook : str or Path 45 | Path the to notebook to execute. 46 | 47 | Returns 48 | ------- 49 | nb : NotebookNode 50 | The parsed notebook object 51 | """ 52 | import uuid 53 | 54 | import nbformat 55 | 56 | unique_name = pathlib.Path( 57 | "{prefix}{suffix}".format(prefix=str(uuid.uuid4()), suffix=".ipynb") 58 | ) 59 | 60 | try: 61 | subprocess.check_call( 62 | [ 63 | "jupyter", 64 | "nbconvert", 65 | "--to", 66 | "notebook", 67 | "--execute", 68 | "--ExecutePreprocessor.kernel_name=python", 69 | "--ExecutePreprocessor.timeout=-1", 70 | "--output", 71 | str(unique_name), 72 | "--output-dir=.", 73 | str(path_to_notebook), 74 | ] 75 | ) 76 | except subprocess.CalledProcessError: 77 | raise 78 | else: 79 | nb = nbformat.read(unique_name, nbformat.current_nbformat) 80 | finally: 81 | try: 82 | unique_name.unlink() 83 | except FileNotFoundError: 84 | pass 85 | 86 | return nb 87 | 88 | 89 | @pytest.mark.notebook 90 | def test_notebook(tmpdir, notebook): 91 | try: 92 | pytest.skip(_EXCLUDE[pathlib.Path(notebook).name]) 93 | except KeyError: 94 | pass 95 | 96 | with tmpdir.as_cwd(): 97 | nb = _notebook_run(notebook) 98 | assert _notebook_check(nb) == [] 99 | -------------------------------------------------------------------------------- /notebooks/welcome.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Welcome to Pymt demo Notebooks\n", 8 | "\n", 9 | "This page provides links to notebooks that provide an introduction to Pymt and its use for different models. \n", 10 | "\n", 11 | " * [Coastline Evolution Model](cem.ipynb)\n", 12 | " * [Coastline Evolution Model + Waves](cem_and_waves.ipynb)\n", 13 | " * [ECSimpleSnow component](ecsimplesnow.ipynb)\n", 14 | " \n", 15 | " \n", 16 | " * [Frost Number Model](frost_number.ipynb)\n", 17 | " * [HydroTrend Model](hydrotrend.ipynb)\n", 18 | " * [HydroTrend Model for Ganges River Research](hydrotrend_Ganges.ipynb)\n", 19 | " * [Kudryavtsev Model](ku.ipynb)\n", 20 | " \n", 21 | " * [Flexural Subsidence Model](subside.ipynb)\n", 22 | "\n", 23 | " \n" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.7.3" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 2 55 | } 56 | -------------------------------------------------------------------------------- /pymt/__init__.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from gimli.units import UnitSystem 3 | 4 | from ._version import __version__ 5 | from .model_collection import ModelCollection 6 | 7 | np.set_printoptions(legacy="1.21") 8 | 9 | MODELS = ModelCollection() 10 | 11 | __all__ = ["__version__", "UnitSystem", "MODELS"] 12 | -------------------------------------------------------------------------------- /pymt/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.3.3.dev0" 2 | -------------------------------------------------------------------------------- /pymt/bmi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/bmi/__init__.py -------------------------------------------------------------------------------- /pymt/bmi/bmi.py: -------------------------------------------------------------------------------- 1 | class Error(Exception): 2 | """Base class for BMI exceptions""" 3 | 4 | pass 5 | 6 | 7 | class VarNameError(Error): 8 | """Exception to indicate a bad input/output variable name""" 9 | 10 | def __init__(self, name): 11 | self.name = name 12 | 13 | def __str__(self): 14 | return self.name 15 | 16 | 17 | class BMI: 18 | def initialize(self, filename): 19 | pass 20 | 21 | def run(self, time): 22 | pass 23 | 24 | def finalize(self): 25 | pass 26 | 27 | def get_input_var_names(self): 28 | pass 29 | 30 | def get_output_var_names(self): 31 | pass 32 | 33 | def get_var_grid(self, var_name): 34 | pass 35 | 36 | def get_var_type(self, var_name): 37 | pass 38 | 39 | def get_var_units(self, var_name): 40 | pass 41 | 42 | def get_time_step(self): 43 | pass 44 | 45 | def get_start_time(self): 46 | pass 47 | 48 | def get_current_time(self): 49 | pass 50 | 51 | def get_end_time(self): 52 | pass 53 | 54 | def get_grid_rank(self, grid_id): 55 | pass 56 | 57 | def get_grid_spacing(self, grid_id): 58 | pass 59 | 60 | def get_grid_shape(self, grid_id): 61 | pass 62 | 63 | def get_grid_x(self, grid_id): 64 | pass 65 | 66 | def get_grid_y(self, grid_id): 67 | pass 68 | 69 | def get_grid_z(self, grid_id): 70 | pass 71 | 72 | def get_grid_connectivity(self, grid_id): 73 | pass 74 | 75 | def get_grid_offset(self, grid_id): 76 | pass 77 | -------------------------------------------------------------------------------- /pymt/cmd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/cmd/__init__.py -------------------------------------------------------------------------------- /pymt/component/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/component/__init__.py -------------------------------------------------------------------------------- /pymt/errors.py: -------------------------------------------------------------------------------- 1 | class PymtError(Exception): 2 | pass 3 | 4 | 5 | class BmiError(PymtError): 6 | def __init__(self, fname, status): 7 | self._fname = fname 8 | self._status = status 9 | 10 | def __str__(self): 11 | return "error calling BMI function: {fname} ({code})".format( 12 | fname=self._fname, code=self._status 13 | ) 14 | 15 | 16 | class BadUnitError(PymtError): 17 | def __init__(self, unit): 18 | self._unit = unit 19 | 20 | def __str__(self): 21 | return f"unknown unit ({self._unit!r})" 22 | 23 | 24 | class IncompatibleUnitsError(PymtError): 25 | def __init__(self, src, dst): 26 | self._src = src 27 | self._dst = dst 28 | 29 | def __str__(self): 30 | return f"incompatible units ({self._src!r}, {self._dst!r})" 31 | -------------------------------------------------------------------------------- /pymt/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/events/__init__.py -------------------------------------------------------------------------------- /pymt/events/chain.py: -------------------------------------------------------------------------------- 1 | class ChainEvent: 2 | def __init__(self, events): 3 | self._events = events 4 | 5 | def initialize(self): 6 | for event in self._events: 7 | event.initialize() 8 | 9 | def run(self, stop_time): 10 | for event in self._events: 11 | event.run(stop_time) 12 | 13 | def update(self, stop_time): 14 | for event in self._events: 15 | event.run(stop_time) 16 | 17 | def finalize(self): 18 | for event in self._events: 19 | event.finalize() 20 | -------------------------------------------------------------------------------- /pymt/events/empty.py: -------------------------------------------------------------------------------- 1 | """An empty event. 2 | 3 | :class:`PassEvent` implements an event-like interface but doesn't do 4 | anything. This can be a useful event to give to an :class:`EventManager` if 5 | you want to force the manager to stop at particular intervals. 6 | """ 7 | 8 | 9 | class PassEvent: 10 | """An event that doesn't do anything.""" 11 | 12 | def initialize(self): 13 | pass 14 | 15 | def run(self, stop_time): 16 | pass 17 | 18 | def finalize(self, stop_time): 19 | pass 20 | -------------------------------------------------------------------------------- /pymt/events/printer.py: -------------------------------------------------------------------------------- 1 | from ..portprinter.port_printer import PortPrinter 2 | from ..utils import as_cwd 3 | 4 | 5 | class PrintEvent: 6 | def __init__(self, *args, **kwds): 7 | # self._printer = PortPrinter.from_dict(kwds) 8 | self._run_dir = kwds.pop("run_dir", ".") 9 | self._kwds = kwds 10 | 11 | def initialize(self, *args): 12 | with as_cwd(self._run_dir): 13 | self._printer = PortPrinter.from_dict(self._kwds) 14 | self._printer.open() 15 | 16 | def run(self, time): 17 | with as_cwd(self._run_dir): 18 | self._printer.write() 19 | 20 | def finalize(self): 21 | with as_cwd(self._run_dir): 22 | self._printer.close() 23 | -------------------------------------------------------------------------------- /pymt/framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/framework/__init__.py -------------------------------------------------------------------------------- /pymt/framework/bmi_plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | 5 | def quick_plot(bmi, name, **kwds): 6 | if bmi.var_location(name) == "none": 7 | raise ValueError(f"{name} does not have an associated grid to plot") 8 | 9 | gid = bmi.var_grid(name) 10 | gtype = bmi.grid_type(gid) 11 | grid = bmi.grid[gid] 12 | 13 | x_label = "{name} ({units})".format( 14 | name=grid.node_x.standard_name, units=grid.node_x.units 15 | ) 16 | y_label = "{name} ({units})".format( 17 | name=grid.node_y.standard_name, units=grid.node_y.units 18 | ) 19 | 20 | z = bmi.get_value(name) 21 | 22 | if gtype.startswith("unstructured"): 23 | x, y = grid.node_x.values, grid.node_y.values 24 | nodes_per_face = bmi.grid_nodes_per_face(gid) 25 | if np.all(nodes_per_face == 3): 26 | tris = bmi.grid_face_nodes(gid).reshape((-1, 3)) 27 | # tris = bmi.grid_face_node_connectivity(gid).reshape((-1, 3)) 28 | plt.tripcolor(x, y, tris, z, **kwds) 29 | else: 30 | raise ValueError( 31 | "quickplot is only able to plot unstructured meshes of triangles" 32 | ) 33 | elif gtype in ("uniform_rectilinear", "structured_quad"): 34 | shape = bmi.grid_shape(gid) 35 | spacing = bmi.grid_spacing(gid) 36 | origin = bmi.grid_origin(gid) 37 | x = np.arange(shape[-1]) * spacing[-1] + origin[-1] 38 | y = np.arange(shape[-2]) * spacing[-2] + origin[-2] 39 | plt.pcolormesh(x, y, z.reshape(shape), **kwds) 40 | else: 41 | raise ValueError(f"no plotter for {gtype}") 42 | 43 | plt.axis("tight") 44 | plt.gca().set_aspect("equal") 45 | plt.xlabel(x_label) 46 | plt.ylabel(y_label) 47 | 48 | cbar = plt.colorbar() 49 | cbar.ax.set_ylabel(f"{name} ({bmi.var_units(name)})") 50 | -------------------------------------------------------------------------------- /pymt/framework/bmi_timeinterp.py: -------------------------------------------------------------------------------- 1 | from ..errors import BmiError 2 | from ..utils import as_cwd 3 | from .timeinterp import TimeInterpolator 4 | 5 | 6 | class BmiTimeInterpolator: 7 | def __init__(self, *args, **kwds): 8 | method = kwds.pop("method", "linear") 9 | self._interpolators = { 10 | name: None for name in self.output_var_names if "__" in name 11 | } 12 | self.reset(method=method) 13 | 14 | super().__init__(*args, **kwds) 15 | 16 | def reset(self, method="linear"): 17 | for name in self._interpolators: 18 | self._interpolators[name] = TimeInterpolator(method=method) 19 | 20 | def add_data(self): 21 | for name in self._interpolators: 22 | try: 23 | self._interpolators[name].add_data([(self.time, self.get_value(name))]) 24 | except BmiError: 25 | self._interpolators.pop(name) 26 | print(f"unable to get value for {name}. ignoring") 27 | 28 | def interpolate(self, name, at): 29 | return self._interpolators[name].interpolate(at) 30 | 31 | def update_until(self, then, method=None, units=None): 32 | with as_cwd(self.initdir): 33 | then = self.time_from(then, units) 34 | 35 | if hasattr(self.bmi, "update_until"): 36 | try: 37 | self.bmi.update_until(then) 38 | except NotImplementedError: 39 | pass 40 | 41 | self.reset() 42 | while self.time < then: 43 | if self.time + self.time_step > then: 44 | self.add_data() 45 | self.update() 46 | 47 | if self.time > then: 48 | self.add_data() 49 | -------------------------------------------------------------------------------- /pymt/grids/__init__.py: -------------------------------------------------------------------------------- 1 | from .assertions import ( 2 | is_rectilinear, 3 | is_structured, 4 | is_uniform_rectilinear, 5 | is_unstructured, 6 | ) 7 | from .field import RasterField, RectilinearField, StructuredField, UnstructuredField 8 | from .igrid import DimensionError 9 | from .raster import UniformRectilinear, UniformRectilinearPoints 10 | from .rectilinear import Rectilinear, RectilinearPoints 11 | from .structured import Structured, StructuredPoints 12 | from .unstructured import Unstructured, UnstructuredPoints 13 | 14 | __all__ = [ 15 | "UniformRectilinear", 16 | "UniformRectilinearPoints", 17 | "Rectilinear", 18 | "RectilinearPoints", 19 | "Structured", 20 | "StructuredPoints", 21 | "Unstructured", 22 | "UnstructuredPoints", 23 | "RasterField", 24 | "RectilinearField", 25 | "StructuredField", 26 | "UnstructuredField", 27 | "is_uniform_rectilinear", 28 | "is_rectilinear", 29 | "is_structured", 30 | "is_unstructured", 31 | "DimensionError", 32 | ] 33 | -------------------------------------------------------------------------------- /pymt/grids/assertions.py: -------------------------------------------------------------------------------- 1 | def is_callable_method(obj, method): 2 | return hasattr(obj, method) and callable(getattr(obj, method)) 3 | 4 | 5 | def assert_is_uniform_rectilinear(grid): 6 | if not is_uniform_rectilinear(grid): 7 | raise AssertionError("Grid is not uniform rectilinear") 8 | 9 | 10 | def assert_is_rectilinear(grid, strict=True): 11 | if not is_rectilinear(grid, strict=strict): 12 | if strict: 13 | raise AssertionError("Grid is not strictly rectilinear") 14 | else: 15 | raise AssertionError("Grid is not rectilinear") 16 | 17 | 18 | def assert_is_structured(grid, strict=True): 19 | if not is_structured(grid, strict=strict): 20 | if strict: 21 | raise AssertionError("Grid is not strictly structured") 22 | else: 23 | raise AssertionError("Grid is not structured") 24 | 25 | 26 | def assert_is_unstructured(grid, strict=True): 27 | if not is_unstructured(grid, strict=True): 28 | if strict: 29 | raise AssertionError("Grid is not strictly unstructured") 30 | else: 31 | raise AssertionError("Grid is not unstructured") 32 | 33 | 34 | def is_uniform_rectilinear(grid): 35 | return ( 36 | is_callable_method(grid, "get_spacing") 37 | and is_callable_method(grid, "get_origin") 38 | and is_callable_method(grid, "get_shape") 39 | ) 40 | 41 | 42 | def is_rectilinear(grid, strict=True): 43 | loosely_rectilinear = is_callable_method(grid, "get_shape") and is_callable_method( 44 | grid, "get_xyz_coordinates" 45 | ) 46 | if not strict: 47 | return loosely_rectilinear 48 | else: 49 | return loosely_rectilinear and not is_uniform_rectilinear(grid) 50 | 51 | 52 | def is_structured(grid, strict=True): 53 | loosely_structured = is_callable_method(grid, "get_shape") 54 | 55 | if not strict: 56 | return loosely_structured 57 | else: 58 | return loosely_structured and not ( 59 | is_rectilinear(grid) or is_uniform_rectilinear(grid) 60 | ) 61 | 62 | 63 | def is_unstructured(grid, strict=True): 64 | if not strict: 65 | return True 66 | else: 67 | return not ( 68 | is_structured(grid) or is_rectilinear(grid) or is_uniform_rectilinear(grid) 69 | ) 70 | -------------------------------------------------------------------------------- /pymt/grids/grid_type.py: -------------------------------------------------------------------------------- 1 | class GridType: 2 | _type = None 3 | 4 | def __eq__(self, that): 5 | return isinstance(that, self.__class__) or str(self) == str(that) 6 | 7 | def __str__(self): 8 | return self._type 9 | 10 | def __repr__(self): 11 | return "%s()" % self.__class__.__name__ 12 | 13 | 14 | class GridTypeRectilinear(GridType): 15 | _type = "rectilinear" 16 | 17 | 18 | class GridTypeStructured(GridType): 19 | _type = "structured" 20 | 21 | 22 | class GridTypeUnstructured(GridType): 23 | _type = "unstructured" 24 | -------------------------------------------------------------------------------- /pymt/grids/igrid.py: -------------------------------------------------------------------------------- 1 | CENTERING_CHOICES = ["zonal", "point"] 2 | 3 | 4 | class Error(Exception): 5 | """Base class for grid and field errors""" 6 | 7 | pass 8 | 9 | 10 | class DimensionError(Error): 11 | """Error to indicate a dimension mismatch when adding a value field to a grid.""" 12 | 13 | def __init__(self, dim0, dim1): 14 | try: 15 | self.src_dim = tuple(dim0) 16 | except TypeError: 17 | self.src_dim = dim0 18 | 19 | try: 20 | self.dst_dim = tuple(dim1) 21 | except TypeError: 22 | self.dst_dim = dim1 23 | 24 | def __str__(self): 25 | return f"{self.src_dim} != {self.dst_dim}" 26 | 27 | 28 | class CenteringValueError(Error): 29 | """Error to indicate an invalid value for value centering.""" 30 | 31 | def __init__(self, val): 32 | self.val = val 33 | 34 | def __str__(self): 35 | return "%s: Bad value for 'centering'" % self.val 36 | 37 | 38 | class GridTypeError(Error): 39 | def __str__(self): 40 | try: 41 | return "Grid is not %s" % self.type 42 | except AttributeError: 43 | return "Grid is not of required type" 44 | 45 | 46 | class NonUniformGridError(GridTypeError): 47 | """Error to indicate a grid is not a uniform rectilinear grid""" 48 | 49 | type = "uniform rectilinear" 50 | 51 | 52 | class NonStructuredGridError(GridTypeError): 53 | """Error to indicate a grid is not a structured grid""" 54 | 55 | type = "structured" 56 | 57 | 58 | class IGrid: 59 | """An interface for a grid object that represents a structured or unstructured 60 | grid of nodes and elements. 61 | """ 62 | 63 | def get_x(self): 64 | """Return the x-coordinates of the grid nodes.""" 65 | pass 66 | 67 | def get_y(self): 68 | """Return the y-coordinates of the grid nodes.""" 69 | pass 70 | 71 | def get_connectivity(self): 72 | """Return the connectivity array for the grid.""" 73 | pass 74 | 75 | def get_offset(self): 76 | """Return an array of offsets in to the connectivity array for each 77 | element of the grid.""" 78 | pass 79 | 80 | 81 | class IField(IGrid): 82 | def get_field(self, field_name): 83 | """Return an array of values for the requested field""" 84 | pass 85 | -------------------------------------------------------------------------------- /pymt/grids/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pymt.grids.assertions import is_rectilinear, is_structured, is_unstructured 4 | 5 | 6 | def get_default_coordinate_units(n_dims): 7 | if n_dims <= 0 or n_dims > 3: 8 | raise ValueError("dimension must be between one and three") 9 | return ["-"] * n_dims 10 | 11 | 12 | def get_default_coordinate_names(n_dims): 13 | if n_dims <= 0 or n_dims > 3: 14 | raise ValueError("dimension must be between one and three") 15 | return ["z", "y", "x"][-n_dims:] 16 | 17 | 18 | def assert_arrays_are_equal_size(*args): 19 | first_size = args[0].size 20 | for arg in args[1:]: 21 | if arg.size != first_size: 22 | raise AssertionError("arrays are not the same length") 23 | 24 | 25 | def args_as_numpy_arrays(*args): 26 | np_arrays = [] 27 | for arg in args: 28 | if isinstance(arg, np.ndarray): 29 | np_array = arg.view() 30 | else: 31 | np_array = np.array(arg) 32 | np_array.shape = (np_array.size,) 33 | np_arrays.append(np_array) 34 | return tuple(np_arrays) 35 | 36 | 37 | def coordinates_to_numpy_matrix(*args): 38 | args = args_as_numpy_arrays(*args) 39 | assert_arrays_are_equal_size(*args) 40 | 41 | coords = np.empty((len(args), len(args[0])), dtype=float) 42 | for dim, arg in enumerate(args): 43 | coords[dim][:] = arg.flatten() 44 | return coords 45 | 46 | 47 | def non_singleton_axes(grid): 48 | try: 49 | shape = grid.get_shape() 50 | except AttributeError: 51 | return np.arange(grid.get_dim_count()) 52 | else: 53 | (indices,) = np.where(shape > 1) 54 | return indices 55 | 56 | 57 | def non_singleton_shape(grid): 58 | shape = grid.get_shape() 59 | (indices,) = np.where(shape > 1) 60 | return shape[indices] 61 | 62 | 63 | def non_singleton_coordinate_names(grid): 64 | indices = non_singleton_axes(grid) 65 | return grid.get_coordinate_name(indices) 66 | 67 | 68 | def non_singleton_dimension_names(grid): 69 | if is_structured(grid, strict=False): 70 | coordinate_names = non_singleton_coordinate_names(grid) 71 | # return np.array(['n' + name for name in coordinate_names]) 72 | return coordinate_names 73 | else: 74 | return np.array(["n_node"]) 75 | 76 | 77 | def non_singleton_dimension_shape(grid): 78 | if is_rectilinear(grid, strict=False): 79 | shape = non_singleton_dimension_names(grid) 80 | return shape[:, np.newaxis] 81 | elif is_structured(grid, strict=True): 82 | shape = non_singleton_dimension_names(grid) 83 | return np.tile(shape, (len(shape), 1)) 84 | elif is_unstructured(grid, strict=True): 85 | shape = np.array(["n_node"]) 86 | return np.tile(shape, (grid.get_dim_count(), 1)) 87 | 88 | 89 | def _find_first(array, value): 90 | try: 91 | return np.where(array == value)[0][0] 92 | except IndexError: 93 | return len(array) 94 | 95 | 96 | def connectivity_matrix_as_array(face_nodes, bad_val): 97 | nodes_per_face = np.empty(face_nodes.shape[0], dtype=int) 98 | for face_id, face in enumerate(face_nodes): 99 | nnodes = _find_first(face, bad_val) 100 | if nnodes > 0: 101 | nodes_per_face[face_id] = _find_first(face, bad_val) 102 | else: 103 | raise ValueError("face contains no nodes") 104 | offsets = np.cumsum(nodes_per_face) 105 | 106 | connectivity = np.empty(offsets[-1], dtype=int) 107 | offset = 0 108 | for n_nodes, face in zip(nodes_per_face, face_nodes): 109 | connectivity[offset : offset + n_nodes] = face[:n_nodes] 110 | offset += n_nodes 111 | 112 | return (connectivity, offsets) 113 | -------------------------------------------------------------------------------- /pymt/mappers/__init__.py: -------------------------------------------------------------------------------- 1 | from .celltopoint import CellToPoint 2 | from .mapper import IncompatibleGridError, find_mapper 3 | from .pointtocell import PointToCell 4 | from .pointtopoint import NearestVal 5 | 6 | __all__ = [ 7 | "find_mapper", 8 | "IncompatibleGridError", 9 | "CellToPoint", 10 | "NearestVal", 11 | "PointToCell", 12 | ] 13 | -------------------------------------------------------------------------------- /pymt/mappers/celltopoint.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.spatial import KDTree 3 | 4 | from .imapper import IGridMapper, IncompatibleGridError 5 | 6 | # from .mapper import IncompatibleGridError 7 | 8 | 9 | def map_points_to_cells(coords, src_grid, src_point_ids, bad_val=-1): 10 | (dst_x, dst_y) = coords 11 | 12 | point_to_cell_id = np.empty(len(dst_x), dtype=int) 13 | point_to_cell_id.fill(bad_val) 14 | 15 | for j, point_id in enumerate(src_point_ids): 16 | for cell_id in src_grid.get_shared_cells(point_id): 17 | if src_grid.is_in_cell(dst_x[j], dst_y[j], cell_id): 18 | point_to_cell_id[j] = cell_id 19 | 20 | return point_to_cell_id 21 | 22 | 23 | class CellToPoint(IGridMapper): 24 | _name = "CellToPoint" 25 | 26 | def initialize(self, dest_grid, src_grid, **kwds): 27 | if not CellToPoint.test(dest_grid, src_grid): 28 | raise IncompatibleGridError(dest_grid.name, src_grid.name) 29 | dst_x = dest_grid.get_x() 30 | dst_y = dest_grid.get_y() 31 | 32 | tree = KDTree(list(zip(src_grid.get_x(), src_grid.get_y()))) 33 | (_, self._nearest_src_id) = tree.query(list(zip(dst_x, dst_y))) 34 | 35 | self._map = map_points_to_cells( 36 | (dst_x, dst_y), src_grid, self._nearest_src_id, bad_val=-1 37 | ) 38 | self._bad = self._map == -1 39 | 40 | def run(self, src_values, **kwds): 41 | dst_vals = kwds.get("dst_vals", None) 42 | bad_val = kwds.get("bad_val", -999.0) 43 | 44 | if dst_vals is None: 45 | dst_vals = np.zeros(len(self._map)) + bad_val 46 | 47 | rtn = np.choose( 48 | src_values.take(self._map) < bad_val, (src_values.take(self._map), dst_vals) 49 | ) 50 | rtn[self._bad] = bad_val 51 | dst_vals[:] = rtn 52 | 53 | return rtn 54 | 55 | @staticmethod 56 | def test(dst_grid, src_grid): 57 | return all(np.diff(src_grid.get_offset()) > 2) 58 | 59 | @property 60 | def name(self): 61 | return self._name 62 | -------------------------------------------------------------------------------- /pymt/mappers/imapper.py: -------------------------------------------------------------------------------- 1 | class MapperError(Exception): 2 | """Base class for error in this package.""" 3 | 4 | pass 5 | 6 | 7 | class IncompatibleGridError(MapperError): 8 | """Error to indicate that the source grid cannot be mapped to the 9 | destination. 10 | """ 11 | 12 | def __init__(self, dst, src): 13 | self._src = src 14 | self._dst = dst 15 | 16 | def __str__(self): 17 | return f"Unable to map {self._src} to {self._dst}" 18 | 19 | 20 | class NoMapperError(MapperError): 21 | def __init__(self, dst, src): 22 | self._src = src 23 | self._dst = dst 24 | 25 | def __str__(self): 26 | return f"No mapper to map {self._src} to {self._dst}" 27 | 28 | 29 | class IGridMapper: 30 | """Interface for a grid mapper.""" 31 | 32 | def initialize(self, dest_grid, src_grid, **kwds): 33 | """Initialize the mapper to map from a source grid to a destination 34 | grid. 35 | """ 36 | pass 37 | 38 | def run(self, src_values, **kwds): 39 | """Map values on the source grid to the destination grid.""" 40 | pass 41 | -------------------------------------------------------------------------------- /pymt/mappers/pointtocell.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | import numpy as np 4 | from scipy.spatial import KDTree 5 | 6 | from .imapper import IGridMapper, IncompatibleGridError 7 | 8 | # from .mapper import IncompatibleGridError 9 | 10 | 11 | def map_cells_to_points(coords, dst_grid, dst_point_ids, bad_val=-1): 12 | src_x, src_y = coords 13 | 14 | cell_to_point_id = defaultdict(list) 15 | for j, point_id in enumerate(dst_point_ids): 16 | for cell_id in dst_grid.get_shared_cells(point_id): 17 | if dst_grid.is_in_cell(src_x[j], src_y[j], cell_id): 18 | cell_to_point_id[cell_id].append(j) 19 | 20 | return cell_to_point_id 21 | 22 | 23 | class PointToCell(IGridMapper): 24 | _name = "PointToCell" 25 | 26 | def initialize(self, dest_grid, src_grid, **kwds): 27 | if not self.test(dest_grid, src_grid): 28 | raise IncompatibleGridError(dest_grid.name, src_grid.name) 29 | src_x = src_grid.get_x() 30 | src_y = src_grid.get_y() 31 | 32 | tree = KDTree(list(zip(dest_grid.get_x(), dest_grid.get_y()))) 33 | (_, nearest_dest_id) = tree.query(list(zip(src_x, src_y))) 34 | 35 | self._map = map_cells_to_points( 36 | (src_x, src_y), dest_grid, nearest_dest_id, bad_val=-1 37 | ) 38 | 39 | self._dst_cell_count = dest_grid.get_cell_count() 40 | self._src_point_count = src_grid.get_point_count() 41 | 42 | def run(self, src_values, **kwds): 43 | dst_vals = kwds.get("dst_vals", None) 44 | bad_val = kwds.get("bad_val", -999) 45 | method = kwds.get("method", np.mean) 46 | 47 | if src_values.size != self._src_point_count: 48 | raise ValueError("size mismatch between source and point count") 49 | 50 | if dst_vals is None: 51 | dst_vals = np.array([bad_val] * self._dst_cell_count, dtype=float) 52 | if dst_vals.size != self._dst_cell_count: 53 | raise ValueError("size mismatch between destination and cell count") 54 | 55 | for cell_id, point_ids in self._map.items(): 56 | if all(src_values[point_ids] > bad_val): 57 | dst_vals[cell_id] = method(src_values[point_ids]) 58 | 59 | return dst_vals 60 | 61 | @staticmethod 62 | def test(dst_grid, src_grid): 63 | return all(np.diff(dst_grid.get_offset()) > 2) and src_grid is not None 64 | 65 | @property 66 | def name(self): 67 | return self._name 68 | -------------------------------------------------------------------------------- /pymt/model_collection.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | import pkg_resources 4 | 5 | from .framework.bmi_bridge import bmi_factory 6 | 7 | 8 | class ModelLoadError(Exception): 9 | def __init__(self, name, reason=None): 10 | self._name = name 11 | self._reason = reason or "no reason given" 12 | 13 | def __str__(self): 14 | return f"unable to load model ({self._name}):\n{self._reason}" 15 | 16 | 17 | class ModelCollection: 18 | def __new__(cls): 19 | models = [] 20 | errors = [] 21 | for entry_point in pkg_resources.iter_entry_points(group="pymt.plugins"): 22 | try: 23 | models.append(ModelCollection.load_entry_point(entry_point)) 24 | except ModelLoadError as error: 25 | errors.append((entry_point.name, str(error))) 26 | for name, model in models: 27 | setattr(cls, name, property(lambda self, name=name: self._data[name])) 28 | 29 | inst = super().__new__(cls) 30 | inst._errors = tuple(errors) 31 | inst._data = dict(models) 32 | 33 | return inst 34 | 35 | def __getitem__(self, name): 36 | return self._data[name] 37 | 38 | def keys(self): 39 | return self._data.keys() 40 | 41 | def items(self): 42 | return self._data.items() 43 | 44 | def values(self): 45 | return self._data.values() 46 | 47 | def __iter__(self): 48 | return self._data.__iter__() 49 | 50 | def __len__(self): 51 | return len(self._data) 52 | 53 | def __str__(self): 54 | return str(self._data) 55 | 56 | def __repr__(self): 57 | return repr(set(self.keys())) 58 | 59 | @property 60 | def errors(self): 61 | return self._errors 62 | 63 | @staticmethod 64 | def load_entry_point(entry_point): 65 | try: 66 | model = entry_point.load() 67 | except Exception: 68 | raise ModelLoadError(entry_point.name, reason=traceback.format_exc()) 69 | else: 70 | Model = bmi_factory(model) 71 | Model.__name__ = entry_point.name 72 | Model.__qualname__ = entry_point.name 73 | Model.__module__ = "pymt.models" 74 | 75 | return entry_point.name, Model 76 | -------------------------------------------------------------------------------- /pymt/models.py: -------------------------------------------------------------------------------- 1 | __all__ = [] 2 | 3 | import sys 4 | 5 | from .model_collection import ModelCollection 6 | 7 | for name, cls in ModelCollection().items(): 8 | __all__.append(name) 9 | setattr(sys.modules[__name__], name, cls) 10 | 11 | try: 12 | del name, cls 13 | except NameError: 14 | pass 15 | del sys, ModelCollection 16 | -------------------------------------------------------------------------------- /pymt/portprinter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/portprinter/__init__.py -------------------------------------------------------------------------------- /pymt/portprinter/port_printer.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | from io import StringIO 3 | 4 | from ..framework import services 5 | from ..printers.nc.database import Database as NcDatabase 6 | 7 | # from ..printers.vtk.vtu import Database as VtkDatabase 8 | from ..utils.prefix import names_with_prefix, strip_prefix 9 | from .utils import ( 10 | construct_file_name, 11 | construct_port_as_field, 12 | next_unique_file_name, 13 | reconstruct_port_as_field, 14 | ) 15 | 16 | 17 | class PortPrinter: 18 | _format = "" 19 | _printer_class = None 20 | 21 | def __init__(self, port, var_name, filename=None): 22 | if isinstance(port, str): 23 | self._port = services.get_component_instance(port) 24 | else: 25 | self._port = port 26 | self._var_name = var_name 27 | self._filename = filename 28 | if filename is None: 29 | self._filename = var_name 30 | 31 | self._field = construct_port_as_field(self._port, var_name) 32 | self._printer = self._printer_class() # pylint: disable=not-callable 33 | 34 | @property 35 | def var_name(self): 36 | return self._var_name 37 | 38 | @property 39 | def format(self): 40 | return self._format 41 | 42 | def open(self, clobber=False): 43 | file_name = construct_file_name(self._filename, fmt=self.format, prefix="") 44 | if not clobber: 45 | file_name = next_unique_file_name(file_name) 46 | 47 | self._printer.open(file_name, self.var_name) 48 | 49 | def close(self): 50 | self._printer.close() 51 | self._printer = self._printer_class() # pylint: disable=not-callable 52 | 53 | def write(self): 54 | self.resync_field_to_port() 55 | self._printer.write(self._field) 56 | 57 | def resync_field_to_port(self): 58 | self._field = reconstruct_port_as_field(self._port, self._field) 59 | 60 | @classmethod 61 | def from_string(cls, source, prefix="print"): 62 | config = ConfigParser() 63 | config.read_file(StringIO(source)) 64 | return cls._from_config(config, prefix=prefix) 65 | 66 | @classmethod 67 | def from_path(cls, path, prefix="print"): 68 | config = ConfigParser() 69 | config.read(path) 70 | return cls._from_config(config, prefix=prefix) 71 | 72 | @classmethod 73 | def _from_config(cls, config, prefix="print"): 74 | printers = [] 75 | for section in names_with_prefix(config.sections(), prefix): 76 | printers.append( 77 | cls.from_dict( 78 | { 79 | "port": config.get(section, "port"), 80 | "format": config.get(section, "format"), 81 | "name": strip_prefix(section, prefix), 82 | } 83 | ) 84 | ) 85 | if len(printers) == 1: 86 | return printers[0] 87 | else: 88 | return printers 89 | 90 | @classmethod 91 | def from_dict(cls, d): 92 | try: 93 | printer_class = _FORMAT_TO_PRINTER[d["format"]] 94 | except KeyError: 95 | raise ValueError("%s: unknown printer format" % d["format"]) 96 | return printer_class( 97 | d["port"], d["name"], filename=d.get("filename", d["name"]) 98 | ) 99 | 100 | 101 | # class VtkPortPrinter(PortPrinter): 102 | # _format = "vtk" 103 | # _printer_class = VtkDatabase 104 | 105 | 106 | class NcPortPrinter(PortPrinter): 107 | _format = "nc" 108 | _printer_class = NcDatabase 109 | 110 | 111 | _FORMAT_TO_PRINTER = { 112 | # "vtk": VtkPortPrinter, 113 | "nc": NcPortPrinter, 114 | "netcdf": NcPortPrinter, 115 | } 116 | -------------------------------------------------------------------------------- /pymt/printers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/printers/__init__.py -------------------------------------------------------------------------------- /pymt/printers/nc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/printers/nc/__init__.py -------------------------------------------------------------------------------- /pymt/printers/nc/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | 4 | from scipy.io import netcdf as nc3 5 | 6 | _VALID_NETCDF_FORMATS = {"NETCDF3_CLASSIC", "NETCDF3_64BIT"} 7 | 8 | _NP_TO_NC_TYPE = { 9 | "float32": "f4", 10 | "float64": "f8", 11 | "int8": "i1", 12 | "int16": "i2", 13 | "int32": "i4", 14 | "int64": "i8", 15 | "uint8": "u1", 16 | "uint16": "u2", 17 | "uint32": "u4", 18 | "uint64": "u8", 19 | } 20 | 21 | 22 | try: 23 | import netCDF4 as nc4 24 | except ImportError: 25 | warnings.warn("Unable to import netCDF4.", ImportWarning) 26 | else: 27 | _VALID_NETCDF_FORMATS |= {"NETCDF4_CLASSIC", "NETCDF4"} 28 | 29 | 30 | def assert_valid_netcdf_format(fmt): 31 | if fmt not in _VALID_NETCDF_FORMATS: 32 | raise ValueError( 33 | "{}: format is one of {}".format(fmt, ", ".join(_VALID_NETCDF_FORMATS)) 34 | ) 35 | 36 | 37 | def open_netcdf(path, mode="r", fmt="NETCDF3_CLASSIC", append=False): 38 | assert_valid_netcdf_format(fmt) 39 | 40 | if mode != "r": 41 | if os.path.isfile(path) and append: 42 | mode = "a" 43 | else: 44 | mode = "w" 45 | 46 | if fmt == "NETCDF3_CLASSIC": 47 | root = nc3.netcdf_file(path, mode, version=1) 48 | elif fmt == "NETCDF3_64BIT": 49 | root = nc3.netcdf_file(path, mode, version=2) 50 | else: 51 | root = nc4.Dataset(path, mode, format=fmt) 52 | 53 | return root 54 | -------------------------------------------------------------------------------- /pymt/printers/nc/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .ugrid import close as ugrid_close 4 | from .write import field_tofile 5 | 6 | 7 | class IDatabase: 8 | def __init__(self): 9 | pass 10 | 11 | def open(self, path, var_name, **kwds): 12 | pass 13 | 14 | def write(self, field, **kwds): 15 | pass 16 | 17 | def close(self): 18 | pass 19 | 20 | 21 | def field_changed_size(field, n_points, n_cells): 22 | return n_points != field.get_point_count() or n_cells != field.get_cell_count() 23 | 24 | 25 | class Database(IDatabase): 26 | def open(self, path, var_name, **kwds): 27 | self.close() 28 | 29 | (root, ext) = os.path.splitext(path) 30 | 31 | self._var_name = var_name 32 | self._path = path 33 | self._template = f"{root}_%04d{ext}" 34 | 35 | self._point_count = None 36 | self._cell_count = None 37 | 38 | def write(self, field, **kwds): 39 | kwds.setdefault("append", True) 40 | 41 | if kwds["append"]: 42 | if self._point_count is not None and self._cell_count is not None: 43 | if field_changed_size(field, self._point_count, self._cell_count): 44 | self._path = self._next_file_name() 45 | 46 | self._point_count = field.get_point_count() 47 | self._cell_count = field.get_cell_count() 48 | else: 49 | self._path = self._next_file_name() 50 | 51 | field_tofile(field, self._path, **kwds) 52 | 53 | def close(self): 54 | try: 55 | del self._count 56 | except AttributeError: 57 | pass 58 | try: 59 | del self._point_count 60 | del self._cell_count 61 | except AttributeError: 62 | pass 63 | try: 64 | ugrid_close(self._path) 65 | except AttributeError: 66 | pass 67 | 68 | def _next_file_name(self): 69 | try: 70 | next_file_name = self._template % self._count 71 | except AttributeError: 72 | self._count = 0 73 | next_file_name = self._template % self._count 74 | finally: 75 | self._count += 1 76 | 77 | return next_file_name 78 | -------------------------------------------------------------------------------- /pymt/printers/nc/read.py: -------------------------------------------------------------------------------- 1 | from pymt.grids.grid_type import ( 2 | GridTypeRectilinear, 3 | GridTypeStructured, 4 | GridTypeUnstructured, 5 | ) 6 | 7 | from .constants import open_netcdf 8 | from .ugrid_read import ( 9 | NetcdfRectilinearFieldReader, 10 | NetcdfStructuredFieldReader, 11 | NetcdfUnstructuredFieldReader, 12 | ) 13 | 14 | _NETCDF_MESH_TYPE = { 15 | "rectilinear": GridTypeRectilinear(), 16 | "structured": GridTypeStructured(), 17 | "unstructured": GridTypeUnstructured(), 18 | } 19 | 20 | _NETCDF_READERS = { 21 | "rectilinear": NetcdfRectilinearFieldReader, 22 | "structured": NetcdfStructuredFieldReader, 23 | "unstructured": NetcdfUnstructuredFieldReader, 24 | } 25 | 26 | 27 | def query_netcdf_mesh_type(path, fmt="NETCDF4"): 28 | root = open_netcdf(path, mode="r", fmt=fmt) 29 | 30 | try: 31 | type_string = root.variables["mesh"].type 32 | except AttributeError: 33 | raise AttributeError("netcdf file is missing type attribute") 34 | except KeyError: 35 | raise AttributeError("netcdf file is missing mesh attribute") 36 | finally: 37 | root.close() 38 | 39 | try: 40 | mesh_type = _NETCDF_MESH_TYPE[type_string] 41 | except KeyError: 42 | raise TypeError("%s: mesh type not understood" % mesh_type) 43 | 44 | return mesh_type 45 | 46 | 47 | def field_fromfile(path, fmt="NETCDF4"): 48 | mesh_type = query_netcdf_mesh_type(path) 49 | 50 | try: 51 | reader = _NETCDF_READERS[str(mesh_type)] 52 | except KeyError: 53 | raise TypeError("%s: no reader available for file" % mesh_type) 54 | else: 55 | nc_file = reader(path, fmt=fmt) 56 | 57 | if len(nc_file.times) > 0: 58 | return (nc_file.fields, nc_file.times) 59 | else: 60 | return nc_file.fields 61 | -------------------------------------------------------------------------------- /pymt/printers/nc/write.py: -------------------------------------------------------------------------------- 1 | from warnings import warn 2 | 3 | from ...grids.assertions import is_rectilinear, is_structured 4 | from .ugrid import ( 5 | NetcdfRectilinearField, 6 | NetcdfStructuredField, 7 | NetcdfUnstructuredField, 8 | ) 9 | 10 | 11 | def field_tofile( 12 | field, 13 | path, 14 | append=False, 15 | attrs=None, 16 | time=None, 17 | time_units=None, 18 | time_reference=None, 19 | long_name=None, 20 | fmt="NETCDF4", 21 | keep_open=False, 22 | ): 23 | if time_units is not None: 24 | warn('ignoring keyword "time_units"', UserWarning) 25 | if time_reference is not None: 26 | warn('ignoring keyword "time_reference"', UserWarning) 27 | if not keep_open: 28 | warn('resetting "keep_open" to True', UserWarning) 29 | 30 | attrs = attrs or {} 31 | long_name = long_name or {} 32 | args = (path, field) 33 | kwds = dict(append=append, fmt=fmt, time=time, keep_open=True) 34 | 35 | if is_rectilinear(field, strict=False): 36 | NetcdfRectilinearField(*args, **kwds) 37 | elif is_structured(field, strict=False): 38 | NetcdfStructuredField(*args, **kwds) 39 | else: 40 | NetcdfUnstructuredField(*args, **kwds) 41 | -------------------------------------------------------------------------------- /pymt/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/services/__init__.py -------------------------------------------------------------------------------- /pymt/services/constant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/services/constant/__init__.py -------------------------------------------------------------------------------- /pymt/services/constant/river.py: -------------------------------------------------------------------------------- 1 | from .constant import ConstantScalars 2 | 3 | 4 | class River(ConstantScalars): 5 | def __init__(self): 6 | pass 7 | -------------------------------------------------------------------------------- /pymt/services/gridreader/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/services/gridreader/__init__.py -------------------------------------------------------------------------------- /pymt/services/gridreader/interpolate.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | from scipy import interpolate 4 | 5 | from .time_series_names import sort_time_series_names 6 | 7 | 8 | def get_time_series_values(field, ordering="ascending", prefix=""): 9 | values = defaultdict(list) 10 | 11 | names = sort_time_series_names(field, ordering=ordering, prefix=prefix) 12 | 13 | for var_name, ordered_names in names.items(): 14 | for name in ordered_names: 15 | values[var_name].append(field.get_values(name)) 16 | 17 | return values 18 | 19 | 20 | def get_field_values(fields, names): 21 | values = [] 22 | for name in names: 23 | values.append(fields.get_values(name)) 24 | return values 25 | 26 | 27 | def create_interpolators(times, fields, prefix="", kind="linear"): 28 | interpolators = {} 29 | 30 | names = sort_time_series_names(fields.keys(), prefix=prefix, ordering="ascending") 31 | 32 | for var_name, ordered_names in names.items(): 33 | interpolators[var_name] = create_interpolator( 34 | fields, zip(ordered_names, times), kind=kind 35 | ) 36 | 37 | return interpolators 38 | 39 | 40 | def create_interpolator(fields, time_stamps, kind="linear"): 41 | names, times = zip(*time_stamps) 42 | 43 | values = get_field_values(fields, names) 44 | return interpolate.interp1d(times, values, axis=0, kind=kind) 45 | -------------------------------------------------------------------------------- /pymt/services/gridreader/time_series_names.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import re 3 | 4 | _TIME_SERIES_NAME_RE_PATTERN = r"(?P[a-zA-Z_]+)@t=(?P\d+)$" 5 | _TIME_SERIES_NAME_RE = re.compile(_TIME_SERIES_NAME_RE_PATTERN) 6 | 7 | 8 | class Error(Exception): 9 | pass 10 | 11 | 12 | class TimeSeriesNameError(Error): 13 | def __init__(self, name): 14 | self._name = name 15 | 16 | def __str__(self): 17 | return "invalid time series name: %s" % self._name 18 | 19 | 20 | class TimeStampError(Error): 21 | def __init__(self, time_stamp): 22 | self._time_stamp = time_stamp 23 | 24 | def __str__(self): 25 | return "invalid time stamp: %s" % self._time_stamp 26 | 27 | 28 | def time_stamp_as_string(time_stamp): 29 | if isinstance(time_stamp, str): 30 | try: 31 | return str(int(time_stamp)) 32 | except ValueError: 33 | raise TimeStampError(time_stamp) 34 | elif isinstance(time_stamp, int): 35 | return str(time_stamp) 36 | else: 37 | raise TimeStampError(time_stamp) 38 | 39 | 40 | def split(name): 41 | match = _TIME_SERIES_NAME_RE.match(name) 42 | if match is not None: 43 | return match.group("name"), int(match.group("time_stamp")) 44 | else: 45 | raise TimeSeriesNameError(name) 46 | 47 | 48 | def unsplit(name, time_stamp): 49 | return name + "@t=" + time_stamp_as_string(time_stamp) 50 | 51 | 52 | def extract_time_stamps_from_names(names, ordering="ascending", prefix=""): 53 | if ordering not in ["ascending", "descending", None]: 54 | raise TypeError("ordering not understood: %s" % ordering) 55 | 56 | time_stamps = collections.defaultdict(list) 57 | 58 | for name in names: 59 | if name.startswith(prefix): 60 | try: 61 | (base, time_stamp) = split(name) 62 | except TimeSeriesNameError: 63 | pass 64 | else: 65 | time_stamps[base].append(time_stamp) 66 | 67 | if ordering is not None: 68 | for name in time_stamps: 69 | time_stamps[name].sort(reverse=(ordering == "descending")) 70 | 71 | return time_stamps 72 | 73 | 74 | def sort_time_series_names(names, ordering="ascending", prefix=""): 75 | ordered_names = dict() 76 | 77 | time_stamps = extract_time_stamps_from_names( 78 | names, ordering=ordering, prefix=prefix 79 | ) 80 | for name, stamps in time_stamps.items(): 81 | ordered_names[name] = [unsplit(name, stamp) for stamp in stamps] 82 | 83 | return ordered_names 84 | 85 | 86 | def get_time_series_names(names): 87 | unique_names = set() 88 | 89 | for name in names: 90 | try: 91 | unique_names.add(split(name)[0]) 92 | except TimeSeriesNameError: 93 | pass 94 | 95 | return unique_names 96 | -------------------------------------------------------------------------------- /pymt/testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/pymt/testing/__init__.py -------------------------------------------------------------------------------- /pymt/testing/ports.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class UniformRectilinearGridPort: 5 | def __init__(self): 6 | self._shape = (4, 5) 7 | self._spacing = (1.0, 2.0) 8 | self._origin = (0.0, 1.0) 9 | self._values = { 10 | "landscape_surface__elevation": np.ones(self._shape), 11 | "sea_surface__temperature": np.zeros(self._shape), 12 | "sea_floor_surface_sediment__mean_of_grain_size": np.zeros(self._shape), 13 | "air__density": np.zeros(self._shape), 14 | "glacier_top_surface__slope": np.zeros(self._shape), 15 | } 16 | 17 | def get_var_grid(self, var_name): 18 | if var_name in self._values: 19 | return 0 20 | else: 21 | raise KeyError(var_name) 22 | 23 | def get_grid_shape(self, grid_id): 24 | if grid_id == 0: 25 | return self._shape 26 | else: 27 | raise KeyError(grid_id) 28 | 29 | def get_grid_spacing(self, grid_id): 30 | if grid_id == 0: 31 | return self._spacing 32 | else: 33 | raise KeyError(grid_id) 34 | 35 | def get_grid_origin(self, grid_id): 36 | if grid_id == 0: 37 | return self._origin 38 | else: 39 | raise KeyError(grid_id) 40 | 41 | def get_value(self, var_name): 42 | return self._values[var_name] 43 | -------------------------------------------------------------------------------- /pymt/units.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def transform_math_to_azimuth(angle, units="rad"): 5 | angle *= -1.0 6 | if units == "rad": 7 | angle += np.pi * 0.5 8 | else: 9 | angle += 90.0 10 | return angle 11 | 12 | 13 | def transform_azimuth_to_math(angle, units="rad"): 14 | angle *= -1.0 15 | if units == "rad": 16 | angle += np.pi * 0.5 17 | else: 18 | angle += 90.0 19 | return angle 20 | -------------------------------------------------------------------------------- /pymt/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import as_cwd, err, out 2 | 3 | __all__ = ["as_cwd", "err", "out"] 4 | -------------------------------------------------------------------------------- /pymt/utils/prefix.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | 4 | def prefix_is_empty(prefix): 5 | """Check if a namespace prefix is empty. 6 | 7 | A prefix is empty if it is ``None``, just a "." or an empty string. 8 | 9 | Return ``True`` if empty, otherwise ``False``. 10 | """ 11 | return prefix is None or prefix == "." or len(prefix) == 0 12 | 13 | 14 | def names_with_prefix(names, prefix): 15 | """Find names that begin with a common *prefix*. In this case, names 16 | are a series ``.``-separated words, much like module names. 17 | 18 | Returns a ``list`` of all names that begin with *prefix*. The order of 19 | matched names is maintained. 20 | 21 | >>> names_with_prefix(['foo.bar', 'foobar.baz'], 'foo') 22 | ['foo.bar'] 23 | >>> names_with_prefix(['foo.bar', 'foo.bar', 'foo.foo'], 'foo') 24 | ['foo.bar', 'foo.foo'] 25 | """ 26 | if prefix_is_empty(prefix): 27 | return set(names) 28 | 29 | if not prefix.endswith("."): 30 | prefix = prefix + "." 31 | 32 | matching_names = OrderedDict() 33 | for name in names: 34 | if name.startswith(prefix): 35 | matching_names[name] = None 36 | 37 | return list(matching_names.keys()) 38 | 39 | 40 | def strip_prefix(name, prefix): 41 | """Remove a prefix from a name, including any leading "."s. 42 | 43 | >>> strip_prefix('foo.bar', 'foo') 44 | 'bar' 45 | >>> strip_prefix('foo.bar', '') 46 | 'foo.bar' 47 | """ 48 | if prefix_is_empty(prefix): 49 | return name 50 | 51 | if not prefix.endswith("."): 52 | prefix += "." 53 | 54 | if name.startswith(prefix): 55 | return name[len(prefix) :] 56 | else: 57 | raise ValueError(f"{name} does not start with {prefix}") 58 | -------------------------------------------------------------------------------- /pymt/utils/utils.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | from functools import partial 4 | 5 | import click 6 | 7 | out = partial(click.secho, bold=True, err=True) 8 | err = partial(click.secho, fg="red", err=True) 9 | 10 | 11 | @contextlib.contextmanager 12 | def as_cwd(path): 13 | prev_cwd = os.getcwd() 14 | os.chdir(path) 15 | yield 16 | os.chdir(prev_cwd) 17 | 18 | 19 | @contextlib.contextmanager 20 | def suppress_stdout(): 21 | null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)] 22 | # Save the actual stdout (1) and stderr (2) file descriptors. 23 | save_fds = [os.dup(1), os.dup(2)] 24 | 25 | os.dup2(null_fds[0], 1) 26 | os.dup2(null_fds[1], 2) 27 | 28 | yield 29 | 30 | # Re-assign the real stdout/stderr back to (1) and (2) 31 | os.dup2(save_fds[0], 1) 32 | os.dup2(save_fds[1], 2) 33 | # Close the null files 34 | for fd in null_fds + save_fds: 35 | os.close(fd) 36 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pymt" 7 | description = "The CSDMS Python Modeling Toolkit" 8 | authors = [ 9 | {email = "mcflugen@gmail.com"}, 10 | {name = "The CSDMS team"} 11 | ] 12 | maintainers = [ 13 | {email = "mcflugen@gmail.com"}, 14 | {name = "The CSDMS team"} 15 | ] 16 | keywords = ["earth science", "model coupling"] 17 | license = {file = "LICENSE.rst"} 18 | classifiers = [ 19 | "Development Status :: 4 - Beta", 20 | "Intended Audience :: Science/Research", 21 | "License :: OSI Approved :: MIT License", 22 | "Operating System :: OS Independent", 23 | "Programming Language :: Python :: 3", 24 | "Topic :: Scientific/Engineering :: Physics", 25 | ] 26 | requires-python = ">=3.8" 27 | dependencies = [ 28 | "click", 29 | "deprecated", 30 | "gimli.units", 31 | "jinja2", 32 | "landlab >= 2", 33 | "matplotlib", 34 | "model_metadata >= 0.7", 35 | "netcdf4", 36 | "numpy", 37 | "pyyaml", 38 | "scipy", 39 | "shapely", 40 | "xarray", 41 | ] 42 | dynamic = ["readme", "version"] 43 | 44 | [project.urls] 45 | homepage = "https://csdms.colorado.edu" 46 | documentation = "https://pymt.readthedocs.io" 47 | repository = "https://github.com/csdms/pymt" 48 | changelog = "https://github.com/csdms/pymt/blob/develop/CHANGELOG.rst" 49 | 50 | [project.optional-dependencies] 51 | dev = [ 52 | "black", 53 | "flake8", 54 | "isort", 55 | "zest.releaser[recommended]" 56 | ] 57 | docs = [ 58 | "pandoc", 59 | "nbformat", 60 | "jupyter_client", 61 | "ipython", 62 | "sphinx >= 1.5.1", 63 | "sphinx_rtd_theme", 64 | "tornado", 65 | "entrypoints", 66 | "nbsphinx >= 0.2.12", 67 | ] 68 | testing = [ 69 | "h5netcdf", 70 | "pytest", 71 | "pytest-benchmark", 72 | "pytest-cov", 73 | "pytest-datadir", 74 | "pytest-mypy", 75 | ] 76 | 77 | [tool.setuptools] 78 | include-package-data = true 79 | 80 | [tool.setuptools.packages.find] 81 | where = ["."] 82 | 83 | [tool.setuptools.dynamic] 84 | readme = {file = ["README.rst", "AUTHORS.rst", "CHANGELOG.rst"]} 85 | version = {attr = "pymt._version.__version__"} 86 | 87 | [tool.pytest.ini_options] 88 | minversion = "6.0" 89 | testpaths = ["notebooks", "pymt", "tests"] 90 | norecursedirs = [".*", "*.egg*", "build", "dist"] 91 | addopts = """ 92 | --tb native 93 | --strict-markers 94 | --durations 16 95 | --doctest-modules 96 | -vvv 97 | """ 98 | doctest_optionflags = [ 99 | "NORMALIZE_WHITESPACE", 100 | "IGNORE_EXCEPTION_DETAIL", 101 | "ALLOW_UNICODE" 102 | ] 103 | markers = """ 104 | slow: marks tests as slow (deselect with '-m "not slow"') 105 | notebook: marks tests as notebook (deselect with '-m "not notebook"') 106 | """ 107 | 108 | [tool.isort] 109 | multi_line_output = 3 110 | include_trailing_comma = true 111 | force_grid_wrap = 0 112 | combine_as_imports = true 113 | line_length = 88 114 | 115 | [tool.towncrier] 116 | directory = "news" 117 | package = "pymt" 118 | filename = "CHANGELOG.rst" 119 | single_file = true 120 | underlines = "-`^" 121 | issue_format = "`#{issue} `_" 122 | title_format = "{version} ({project_date})" 123 | 124 | [[tool.towncrier.type]] 125 | directory = "notebook" 126 | name = "New Tutorial Notebooks" 127 | showcontent = true 128 | 129 | [[tool.towncrier.type]] 130 | directory = "feature" 131 | name = "New Features" 132 | showcontent = true 133 | 134 | [[tool.towncrier.type]] 135 | directory = "bugfix" 136 | name = "Bug Fixes" 137 | showcontent = true 138 | 139 | [[tool.towncrier.type]] 140 | directory = "docs" 141 | name = "Documentation Enhancements" 142 | showcontent = true 143 | 144 | [[tool.towncrier.type]] 145 | directory = "misc" 146 | name = "Other Changes and Additions" 147 | showcontent = true 148 | 149 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black 2 | flake8 3 | isort 4 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | pandoc 2 | nbformat 3 | jupyter_client 4 | ipython 5 | sphinx>=1.5.1 6 | sphinx_rtd_theme 7 | tornado 8 | entrypoints 9 | nbsphinx>=0.2.12 10 | # sphinxcontrib_github_alt 11 | -------------------------------------------------------------------------------- /requirements-notebooks.txt: -------------------------------------------------------------------------------- 1 | jupyter 2 | jupyter_contrib_nbextensions 3 | nbformat 4 | seaborn 5 | pymt_cem 6 | pymt_child 7 | pymt_ecsimplesnow 8 | pymt_gipl 9 | pymt_hydrotrend 10 | pymt_permamodel 11 | pymt_sedflux 12 | -------------------------------------------------------------------------------- /requirements-testing.txt: -------------------------------------------------------------------------------- 1 | h5netcdf 2 | pytest 3 | pytest-benchmark 4 | pytest-cov 5 | pytest-datadir 6 | pytest-mypy 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | deprecated 3 | gimli.units>=0.3.2 4 | jinja2 5 | landlab>=2 6 | matplotlib 7 | model_metadata<0.8 8 | netcdf4 9 | numpy<2 # see #173 10 | pyyaml 11 | scipy 12 | shapely 13 | xarray 14 | # cfunits 15 | # esmpy 16 | -------------------------------------------------------------------------------- /scripts/prm2input.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from cmt.scanners import InputParameterScanner 4 | 5 | 6 | def main(): 7 | import argparse 8 | 9 | parser = argparse.ArgumentParser(description="Parse CSDMS parameter annotation") 10 | parser.add_argument("file", nargs="+") 11 | 12 | args = parser.parse_args() 13 | 14 | if len(args.file) == 1: 15 | with open(args.file[0]) as f: 16 | scanner = InputParameterScanner(f) 17 | scanner.scan_file() 18 | print(scanner.fill_contents()) 19 | else: 20 | for file in args.file: 21 | with open(file) as f: 22 | scanner = InputParameterScanner(f) 23 | scanner.scan_file() 24 | 25 | out_file = scanner.get_text("file", "default", default=file + ".in") 26 | 27 | print("Writing input file %s..." % out_file) 28 | try: 29 | with open(out_file, "w") as f: 30 | f.write(scanner.fill_contents()) 31 | except IndexError: 32 | print(scanner.fill_contents()) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /scripts/prm2template.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from cmt.scanners import InputParameterScanner 4 | 5 | 6 | def main(): 7 | import argparse 8 | 9 | parser = argparse.ArgumentParser(description="Parse CSDMS parameter annotation") 10 | parser.add_argument("file", nargs="+") 11 | 12 | args = parser.parse_args() 13 | 14 | if len(args.file) == 1: 15 | with open(args.file[0]) as f: 16 | scanner = InputParameterScanner(f) 17 | scanner.scan_file() 18 | print(scanner.contents()) 19 | else: 20 | for file in args.file: 21 | with open(file) as f: 22 | scanner = InputParameterScanner(f) 23 | scanner.scan_file() 24 | 25 | out_file = scanner.get_text("file", "default", default=file) + ".in" 26 | 27 | print("Writing template file %s..." % out_file) 28 | try: 29 | with open(out_file, "w") as f: 30 | f.write(scanner.contents()) 31 | except IndexError: 32 | print(scanner.contents()) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /scripts/prmscan.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from cmt.scanners import CmtScanner, InputParameterScanner 4 | 5 | 6 | def main(): 7 | import argparse 8 | 9 | parser = argparse.ArgumentParser(description="Parse CSDMS parameter annotation") 10 | parser.add_argument("file", type=argparse.FileType("r"), nargs="+") 11 | parser.add_argument("-o", "--output", type=argparse.FileType("w"), default="-") 12 | parser.add_argument( 13 | "--format", choices=["raw", "cmt"], default="raw", help="Output format" 14 | ) 15 | 16 | args = parser.parse_args() 17 | 18 | scanners = [] 19 | for file in args.file: 20 | if args.format in ["raw", "fill"]: 21 | scanner = InputParameterScanner(file) 22 | elif args.format == "cmt": 23 | scanner = CmtScanner(file) 24 | 25 | scanner.scan_file() 26 | 27 | scanners.append(scanner) 28 | 29 | root = scanners[0] 30 | for scanner in scanners[1:]: 31 | root.update(scanner) 32 | 33 | print(root.to_xml(), file=args.output) 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /scripts/vtu2ncu.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import argparse 4 | 5 | import cmt.nc 6 | import cmt.vtk 7 | 8 | parser = argparse.ArgumentParser(description="Convert VTK file to NCU files") 9 | parser.add_argument("files", metavar="vtk_file", nargs="+", help="VTK formatted file") 10 | parser.add_argument("--start-time", type=int, default=0, help="Time of first cycle") 11 | parser.add_argument("--inc-time", type=int, default=1, help="Time between cycles") 12 | parser.add_argument("--x-units", default="-", help="Units for x") 13 | parser.add_argument("--y-units", default="-", help="Units for y") 14 | parser.add_argument("--var-units", default="-", help="Units for variable") 15 | 16 | 17 | def vtu2ncu(vtk_file, nc_file, time=None, x_units="-", y_units="-", var_units="-"): 18 | field = cmt.vtk.fromfile(vtk_file) 19 | 20 | field.set_x_units(x_units) 21 | field.set_y_units(y_units) 22 | for var in field.keys(): 23 | field.set_field_units(var, var_units) 24 | 25 | cmt.nc.field_tofile(field, nc_file, append=True, time=time) 26 | 27 | 28 | def main(): 29 | 30 | args = parser.parse_args() 31 | 32 | to_file = args.files[-1] 33 | time = args.start_time 34 | for file in args.files[:-1]: 35 | vtu2ncu( 36 | file, 37 | to_file, 38 | time=time, 39 | x_units=args.x_units, 40 | y_units=args.y_units, 41 | var_units=args.var_units, 42 | ) 43 | time += args.inc_time 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = docs 3 | ignore = 4 | E203 5 | E501 6 | W503 7 | max-line-length = 88 8 | 9 | [zest.releaser] 10 | tag-format = v{version} 11 | python-file-with-version = pymt/_version.py 12 | 13 | 14 | [tool:changelog] 15 | force = true 16 | changelog_name = CHANGELOG 17 | project_slug = csdms/pymt 18 | remote_end_point = https://github.com 19 | 20 | [coverage:run] 21 | relative_files = True -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="pymt", # for GitHub dependency graph 5 | ) 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/__init__.py -------------------------------------------------------------------------------- /tests/component/__init__.py: -------------------------------------------------------------------------------- 1 | def setup(): 2 | from pymt.framework.services import del_services, register_component_classes 3 | 4 | del_services() 5 | 6 | register_component_classes( 7 | ["pymt.testing.services.AirPort", "pymt.testing.services.EarthPort"] 8 | ) 9 | -------------------------------------------------------------------------------- /tests/component/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="session") 5 | def with_no_components(): 6 | from pymt.framework.services import del_services, register_component_classes 7 | 8 | del_services() 9 | 10 | register_component_classes( 11 | ["pymt.testing.services.AirPort", "pymt.testing.services.EarthPort"] 12 | ) 13 | -------------------------------------------------------------------------------- /tests/component/test_map_component.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymt.component.component import Component 4 | 5 | 6 | def test_print_events(tmpdir, with_no_components): 7 | air = Component.load( 8 | """ 9 | name: air_port 10 | class: AirPort 11 | print: 12 | - name: air__temperature 13 | interval: 25. 14 | format: nc 15 | """ 16 | ) 17 | earth = Component.load( 18 | """ 19 | name: earth_port 20 | class: EarthPort 21 | print: 22 | - name: glacier_top_surface__slope 23 | interval: 20. 24 | format: netcdf 25 | """ 26 | ) 27 | with tmpdir.as_cwd(): 28 | earth.connect( 29 | "air_port", 30 | air, 31 | vars_to_map=[ 32 | ("glacier_top_surface__slope", "air__temperature"), 33 | ("earth_surface__temperature", "air__temperature"), 34 | ], 35 | ) 36 | earth.go() 37 | 38 | assert os.path.isfile("glacier_top_surface__slope.nc") 39 | assert os.path.isfile("air__temperature.nc") 40 | -------------------------------------------------------------------------------- /tests/component/test_model.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymt.component.model import Model 4 | from pymt.framework.services import del_component_instances 5 | 6 | AIR_PORT_CONTENTS = """ 7 | name: air_port 8 | class: AirPort 9 | argv: [] 10 | initialize_args: air.txt 11 | print: [] 12 | run_dir: air 13 | time_step: 1.0 14 | connectivity: [] 15 | """ 16 | 17 | 18 | def test_model_load(tmpdir, with_no_components): 19 | del_component_instances(["air_port"]) 20 | with tmpdir.as_cwd(): 21 | os.mkdir("air") 22 | model = Model.load(AIR_PORT_CONTENTS) 23 | 24 | assert model.components == ["air_port"] 25 | 26 | 27 | def test_model_from_file(tmpdir, with_no_components): 28 | del_component_instances(["air_port"]) 29 | with tmpdir.as_cwd(): 30 | os.mkdir("air") 31 | with open("components.yml", "w") as fp: 32 | fp.write(AIR_PORT_CONTENTS) 33 | model = Model.from_file("components.yml") 34 | 35 | assert model.components == ["air_port"] 36 | 37 | 38 | def test_model_from_file_like(tmpdir, with_no_components): 39 | del_component_instances(["air_port"]) 40 | with tmpdir.as_cwd(): 41 | os.mkdir("air") 42 | with open("components.yml", "w") as fp: 43 | fp.write(AIR_PORT_CONTENTS) 44 | with open("components.yml") as fp: 45 | model = Model.from_file_like(fp) 46 | 47 | assert model.components == ["air_port"] 48 | -------------------------------------------------------------------------------- /tests/component/test_one_component.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pytest import approx 4 | 5 | from pymt.component.component import Component 6 | from pymt.framework.services import del_component_instances 7 | 8 | 9 | def test_no_events(with_no_components): 10 | del_component_instances(["AirPort"]) 11 | 12 | comp = Component("AirPort", uses=[], provides=[], events=[]) 13 | comp.go() 14 | assert comp._port.current_time == approx(100.0) 15 | 16 | 17 | def test_from_string(with_no_components): 18 | del_component_instances(["air_port"]) 19 | 20 | contents = """ 21 | name: air_port 22 | class: AirPort 23 | """ 24 | comp = Component.from_string(contents) 25 | comp.go() 26 | assert comp._port.current_time == 100.0 27 | 28 | 29 | def test_print_events(tmpdir, with_no_components): 30 | del_component_instances(["earth_port"]) 31 | 32 | contents = """ 33 | name: earth_port 34 | class: EarthPort 35 | print: 36 | - name: earth_surface__temperature 37 | interval: 0.1 38 | format: nc 39 | - name: earth_surface__density 40 | interval: 20. 41 | format: netcdf 42 | - name: glacier_top_surface__slope 43 | interval: 0.3 44 | format: nc 45 | """ 46 | with tmpdir.as_cwd(): 47 | comp = Component.from_string(contents) 48 | comp.go() 49 | 50 | assert comp._port.current_time == 100.0 51 | assert os.path.isfile("earth_surface__temperature.nc") 52 | assert os.path.isfile("glacier_top_surface__slope.nc") 53 | assert os.path.isfile("earth_surface__density.nc") 54 | 55 | 56 | def test_rerun(with_no_components): 57 | del_component_instances(["AirPort"]) 58 | 59 | comp = Component("AirPort", uses=[], provides=[], events=[]) 60 | comp.go() 61 | assert comp._port.current_time == 100.0 62 | 63 | comp.go() 64 | assert comp._port.current_time == 100.0 65 | 66 | 67 | def test_rerun_with_print(tmpdir, with_no_components): 68 | del_component_instances(["earth_port"]) 69 | 70 | contents = """ 71 | name: earth_port 72 | class: EarthPort 73 | 74 | print: 75 | - name: earth_surface__temperature 76 | interval: 20 77 | format: netcdf 78 | """ 79 | with tmpdir.as_cwd(): 80 | comp = Component.from_string(contents) 81 | comp.go() 82 | 83 | assert comp._port.current_time == approx(100.0) 84 | assert os.path.isfile("earth_surface__temperature.nc") 85 | # os.remove("earth_surface__temperature.nc") 86 | 87 | del_component_instances(["earth_port"]) 88 | 89 | comp = Component.from_string(contents) 90 | comp.go() 91 | 92 | assert comp._port.current_time == approx(100.0) 93 | assert os.path.isfile("earth_surface__temperature.nc") 94 | -------------------------------------------------------------------------------- /tests/component/test_recursive.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymt.component.component import Component 4 | from pymt.framework.services import del_component_instances 5 | 6 | 7 | def test_no_events(with_no_components): 8 | del_component_instances(["air_port", "earth_port"]) 9 | 10 | air = Component("AirPort", name="air_port", uses=[], provides=[], events=[]) 11 | earth = Component( 12 | "EarthPort", name="earth_port", uses=["air_port"], provides=[], events=[] 13 | ) 14 | earth.connect("air_port", air) 15 | air.connect("earth_port", earth) 16 | earth.go() 17 | 18 | assert air._port.current_time == 100.0 19 | assert earth._port.current_time == 100.0 20 | 21 | 22 | def test_print_events(tmpdir, with_no_components): 23 | air_init_string = """ 24 | name: air_port 25 | class: AirPort 26 | print: 27 | - name: air__temperature 28 | interval: 25. 29 | format: nc 30 | """ 31 | earth_init_string = """ 32 | name: earth_port 33 | class: EarthPort 34 | print: 35 | - name: glacier_top_surface__slope 36 | interval: 20. 37 | format: nc 38 | """ 39 | with tmpdir.as_cwd(): 40 | for _ in range(2): 41 | del_component_instances(["air_port", "earth_port"]) 42 | 43 | air = Component.from_string(air_init_string) 44 | earth = Component.from_string(earth_init_string) 45 | earth.connect("air_port", air) 46 | air.connect("earth_port", earth) 47 | earth.go() 48 | 49 | assert os.path.isfile("glacier_top_surface__slope.nc") 50 | assert os.path.isfile("air__temperature.nc") 51 | 52 | # os.remove("glacier_top_surface__slope.nc") 53 | # os.remove("air__temperature.nc") 54 | -------------------------------------------------------------------------------- /tests/component/test_two_components.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymt.component.component import Component 4 | from pymt.framework.services import del_component_instances 5 | 6 | 7 | def test_no_events(with_no_components): 8 | del_component_instances(["air_port", "earth_port"]) 9 | 10 | air = Component("AirPort", name="air_port", uses=[], provides=[], events=[]) 11 | earth = Component( 12 | "EarthPort", name="earth_port", uses=["air_port"], provides=[], events=[] 13 | ) 14 | earth.connect("air_port", air) 15 | earth.go() 16 | 17 | assert earth._port.current_time == 100.0 18 | assert air._port.current_time == 100.0 19 | 20 | 21 | def test_print_events(tmpdir, with_no_components): 22 | del_component_instances(["air_port", "earth_port"]) 23 | 24 | air = Component.from_string( 25 | """ 26 | name: air_port 27 | class: AirPort 28 | print: 29 | - name: air__temperature 30 | interval: 25. 31 | format: nc 32 | """ 33 | ) 34 | earth = Component.from_string( 35 | """ 36 | name: earth_port 37 | class: EarthPort 38 | print: 39 | - name: glacier_top_surface__slope 40 | interval: 20. 41 | format: nc 42 | """ 43 | ) 44 | earth.connect("air_port", air) 45 | 46 | with tmpdir.as_cwd(): 47 | earth.go() 48 | 49 | assert os.path.isfile("glacier_top_surface__slope.nc") 50 | assert os.path.isfile("air__temperature.nc") 51 | -------------------------------------------------------------------------------- /tests/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/events/__init__.py -------------------------------------------------------------------------------- /tests/events/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="session") 5 | def with_earth_and_air(): 6 | from pymt.framework.services import ( 7 | del_services, 8 | instantiate_component, 9 | register_component_classes, 10 | ) 11 | 12 | del_services() 13 | 14 | register_component_classes( 15 | ["pymt.testing.services.AirPort", "pymt.testing.services.EarthPort"] 16 | ) 17 | instantiate_component("AirPort", "air_port") 18 | instantiate_component("EarthPort", "earth_port") 19 | -------------------------------------------------------------------------------- /tests/events/test_chain_events.py: -------------------------------------------------------------------------------- 1 | from pytest import approx 2 | 3 | from pymt.events.chain import ChainEvent 4 | from pymt.events.manager import EventManager 5 | from pymt.events.port import PortEvent 6 | from pymt.framework.services import get_component_instance 7 | 8 | 9 | def assert_port_value_equal(port, name, value): 10 | assert port.get_value(name) == approx(value) 11 | 12 | 13 | def test_length_zero(): 14 | foo = ChainEvent([]) 15 | 16 | with EventManager(((foo, 1.0),)) as mngr: 17 | assert mngr.time == approx(0.0) 18 | 19 | mngr.run(2.0) 20 | assert mngr.time == approx(2.0) 21 | 22 | 23 | def test_length_one(tmpdir, with_earth_and_air): 24 | with tmpdir.as_cwd(): 25 | air = get_component_instance("air_port") 26 | foo = ChainEvent([PortEvent(port=air)]) 27 | 28 | with EventManager(((foo, 1.0),)) as mngr: 29 | assert_port_value_equal(air, "air__density", 0.0) 30 | mngr.run(2.0) 31 | assert_port_value_equal(air, "air__density", 2.0) 32 | 33 | assert_port_value_equal(air, "air__density", 0.0) 34 | 35 | 36 | def test_length_two(tmpdir, with_earth_and_air): 37 | with tmpdir.as_cwd(): 38 | air = get_component_instance("air_port") 39 | earth = get_component_instance("earth_port") 40 | 41 | foo = ChainEvent([PortEvent(port=air), PortEvent(port=earth)]) 42 | 43 | with EventManager([(foo, 1.2)]) as mngr: 44 | assert_port_value_equal(earth, "earth_surface__temperature", 0.0) 45 | assert_port_value_equal(air, "air__density", 0.0) 46 | 47 | mngr.run(1.0) 48 | assert_port_value_equal(earth, "earth_surface__temperature", 0.0) 49 | assert_port_value_equal(air, "air__density", 0.0) 50 | 51 | mngr.run(2.0) 52 | assert_port_value_equal(earth, "earth_surface__temperature", 1.2) 53 | assert_port_value_equal(air, "air__density", 1.2) 54 | 55 | 56 | def test_repeated_events(tmpdir, with_earth_and_air): 57 | with tmpdir.as_cwd(): 58 | air = get_component_instance("air_port") 59 | earth = get_component_instance("earth_port") 60 | 61 | foo = ChainEvent( 62 | [PortEvent(port=air), PortEvent(port=earth), PortEvent(port=air)] 63 | ) 64 | 65 | with EventManager([(foo, 1.2)]) as mngr: 66 | assert_port_value_equal(earth, "earth_surface__temperature", 0.0) 67 | assert_port_value_equal(air, "air__density", 0.0) 68 | 69 | mngr.run(1.0) 70 | assert_port_value_equal(earth, "earth_surface__temperature", 0.0) 71 | assert_port_value_equal(air, "air__density", 0.0) 72 | 73 | mngr.run(2.0) 74 | assert_port_value_equal(earth, "earth_surface__temperature", 1.2) 75 | assert_port_value_equal(air, "air__density", 1.2) 76 | -------------------------------------------------------------------------------- /tests/events/test_port_events.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pytest import approx 3 | 4 | from pymt.events.manager import EventManager 5 | from pymt.events.port import PortEvent 6 | 7 | 8 | def assert_port_value_equal(port, name, value): 9 | assert port.get_value(name) == approx(value) 10 | 11 | 12 | def test_one_event(tmpdir, with_earth_and_air): 13 | with tmpdir.as_cwd(): 14 | foo = PortEvent(port="air_port") 15 | 16 | with EventManager(((foo, 1.0),)) as mngr: 17 | assert_port_value_equal(foo._port, "air__density", 0.0) 18 | 19 | mngr.run(1.0) 20 | assert_port_value_equal(foo._port, "air__density", 1.0) 21 | 22 | mngr.run(1.0) 23 | assert_port_value_equal(foo._port, "air__density", 1.0) 24 | 25 | for time in np.arange(1.0, 2.0, 0.1): 26 | mngr.run(time) 27 | assert_port_value_equal(foo._port, "air__density", 1.0) 28 | 29 | mngr.run(2.0) 30 | assert_port_value_equal(foo._port, "air__density", 2.0) 31 | 32 | for time in np.arange(2.0, 5.0, 0.1): 33 | mngr.run(time) 34 | assert mngr.time == approx(4.9) 35 | assert_port_value_equal(foo._port, "air__density", 4.0) 36 | 37 | assert_port_value_equal(foo._port, "air__density", 0.0) 38 | 39 | 40 | def test_two_events(tmpdir, with_earth_and_air): 41 | with tmpdir.as_cwd(): 42 | foo = PortEvent(port="air_port") 43 | bar = PortEvent(port="earth_port") 44 | 45 | with EventManager(((foo, 1.0), (bar, 1.2))) as mngr: 46 | assert_port_value_equal(foo._port, "air__density", 0.0) 47 | assert_port_value_equal(bar._port, "earth_surface__temperature", 0.0) 48 | 49 | mngr.run(1.0) 50 | assert_port_value_equal(foo._port, "air__density", 1.0) 51 | assert_port_value_equal(bar._port, "earth_surface__temperature", 0.0) 52 | 53 | mngr.run(1.0) 54 | assert_port_value_equal(foo._port, "air__density", 1.0) 55 | assert_port_value_equal(bar._port, "earth_surface__temperature", 0.0) 56 | 57 | mngr.run(1.3) 58 | assert_port_value_equal(foo._port, "air__density", 1.0) 59 | assert_port_value_equal(bar._port, "earth_surface__temperature", 1.2) 60 | 61 | mngr.run(2.0) 62 | assert_port_value_equal(foo._port, "air__density", 2.0) 63 | assert_port_value_equal(bar._port, "earth_surface__temperature", 1.2) 64 | 65 | for time in np.arange(2.0, 5.0, 0.1): 66 | mngr.run(time) 67 | assert mngr.time == approx(4.9) 68 | assert_port_value_equal(foo._port, "air__density", 4.0) 69 | assert_port_value_equal(bar._port, "earth_surface__temperature", 4.8) 70 | 71 | assert_port_value_equal(foo._port, "air__density", 0.0) 72 | assert_port_value_equal(bar._port, "earth_surface__temperature", 0.0) 73 | -------------------------------------------------------------------------------- /tests/events/test_port_map_events.py: -------------------------------------------------------------------------------- 1 | from numpy.testing import assert_array_equal 2 | 3 | from pymt.events.chain import ChainEvent 4 | from pymt.events.manager import EventManager 5 | from pymt.events.port import PortEvent, PortMapEvent 6 | from pymt.framework.services import get_component_instance 7 | 8 | 9 | def assert_port_value_equal(port, name, value): 10 | assert_array_equal(port.get_value(name), value) 11 | 12 | 13 | def test_one_event(tmpdir, with_earth_and_air): 14 | with tmpdir.as_cwd(): 15 | foo = PortMapEvent( 16 | src_port="air_port", 17 | dst_port="earth_port", 18 | vars_to_map=[("earth_surface__temperature", "air__density")], 19 | ) 20 | 21 | foo._src.initialize() 22 | foo._dst.initialize() 23 | 24 | with EventManager(((foo, 1.0),)): 25 | assert_port_value_equal(foo._src, "air__density", 0.0) 26 | assert_port_value_equal(foo._dst, "earth_surface__temperature", 0.0) 27 | 28 | 29 | def test_chain(tmpdir, with_earth_and_air): 30 | with tmpdir.as_cwd(): 31 | air = get_component_instance("air_port") 32 | earth = get_component_instance("earth_port") 33 | 34 | foo = ChainEvent( 35 | [ 36 | PortEvent(port=air), 37 | PortMapEvent( 38 | dst_port=air, 39 | src_port=earth, 40 | vars_to_map=[("air__density", "earth_surface__temperature")], 41 | ), 42 | ] 43 | ) 44 | 45 | bar = PortEvent(port=earth) 46 | 47 | with EventManager(((foo, 1.0), (bar, 1.2))) as mngr: 48 | assert_port_value_equal(bar._port, "earth_surface__temperature", 0.0) 49 | assert_port_value_equal(air, "air__density", 0.0) 50 | 51 | mngr.run(1.0) 52 | assert_port_value_equal(earth, "earth_surface__temperature", 0.0) 53 | assert_port_value_equal(air, "air__density", 0.0) 54 | 55 | mngr.run(2.0) 56 | assert_port_value_equal(air, "air__density", 1.2) 57 | -------------------------------------------------------------------------------- /tests/events/test_print_events.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | from pytest import approx 5 | 6 | from pymt.events.manager import EventManager 7 | from pymt.events.printer import PrintEvent 8 | 9 | 10 | def test_one_event(tmpdir, with_earth_and_air): 11 | with tmpdir.as_cwd(): 12 | foo = PrintEvent(port="air_port", name="air__density", format="netcdf") 13 | 14 | with EventManager(((foo, 1.0),)) as mngr: 15 | assert mngr.time == approx(0.0) 16 | 17 | mngr.run(1.0) 18 | assert mngr.time == approx(1.0) 19 | 20 | mngr.run(1.0) 21 | assert mngr.time == approx(1.0) 22 | 23 | for time in np.arange(1.0, 2.0, 0.1): 24 | mngr.run(time) 25 | assert mngr.time == approx(time) 26 | 27 | mngr.run(2.0) 28 | assert mngr.time == approx(2.0) 29 | 30 | for time in np.arange(2.0, 5.0, 0.1): 31 | mngr.run(time) 32 | assert mngr.time == approx(time) 33 | 34 | assert os.path.isfile("air__density.nc") 35 | # assert os.path.isfile("air__density_0001.vtu") 36 | # assert os.path.isfile("air__density_0002.vtu") 37 | # assert os.path.isfile("air__density_0003.vtu") 38 | 39 | 40 | def test_two_events(tmpdir, with_earth_and_air): 41 | with tmpdir.as_cwd(): 42 | foo = PrintEvent(port="air_port", name="air__density", format="nc") 43 | bar = PrintEvent(port="air_port", name="air__temperature", format="nc") 44 | 45 | with EventManager(((foo, 1.0), (bar, 1.2))) as mngr: 46 | mngr.run(1.0) 47 | assert os.path.isfile("air__density.nc") 48 | 49 | mngr.run(2.0) 50 | assert os.path.isfile("air__temperature.nc") 51 | assert mngr.time == approx(2.0) 52 | 53 | mngr.run(5.0) 54 | assert mngr.time == approx(5.0) 55 | -------------------------------------------------------------------------------- /tests/framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/framework/__init__.py -------------------------------------------------------------------------------- /tests/framework/test_bmi_docstring.py: -------------------------------------------------------------------------------- 1 | from pymt.framework.bmi_docstring import bmi_docstring 2 | 3 | DOCSTRING = """Basic Model Interface for Model. 4 | 5 | 6 | 7 | 8 | Version: None 9 | License: None 10 | DOI: None 11 | URL: None 12 | 13 | 14 | Examples 15 | -------- 16 | >>> from pymt.models import Model 17 | >>> model = Model() 18 | >>> (fname, initdir) = model.setup() 19 | >>> model.initialize(fname, dir=initdir) # doctest: +SKIP 20 | >>> for _ in range(10): # doctest: +SKIP 21 | ... model.update() 22 | >>> model.finalize() # doctest: +SKIP 23 | """.strip() 24 | 25 | 26 | def test_empty_docstring(): 27 | docstring = bmi_docstring("Model") 28 | assert docstring == DOCSTRING 29 | 30 | 31 | def test_full_docstring(): 32 | docstring = bmi_docstring( 33 | "DirtyHarry", 34 | author="Clint Eastwood", 35 | summary="The Good, The Bad, and the Ugly", 36 | version="0.1", 37 | license="To kill", 38 | doi="1977.12.23", 39 | url="welldoyapunk.org", 40 | ) 41 | assert ( 42 | docstring 43 | == """ 44 | Basic Model Interface for DirtyHarry. 45 | 46 | The Good, The Bad, and the Ugly 47 | 48 | Author: 49 | - Clint Eastwood 50 | Version: 0.1 51 | License: To kill 52 | DOI: 1977.12.23 53 | URL: welldoyapunk.org 54 | 55 | 56 | Examples 57 | -------- 58 | >>> from pymt.models import DirtyHarry 59 | >>> model = DirtyHarry() 60 | >>> (fname, initdir) = model.setup() 61 | >>> model.initialize(fname, dir=initdir) # doctest: +SKIP 62 | >>> for _ in range(10): # doctest: +SKIP 63 | ... model.update() 64 | >>> model.finalize() # doctest: +SKIP 65 | """.strip() 66 | ) 67 | 68 | 69 | def test_cite_as(): 70 | docstring = bmi_docstring( 71 | "DirtyHarry", 72 | author="Clint Eastwood", 73 | summary="The Good, The Bad, and the Ugly", 74 | version="0.1", 75 | license="To kill", 76 | doi="1977.12.23", 77 | url="welldoyapunk.org", 78 | cite_as=["ref1", "ref2"], 79 | ) 80 | 81 | assert ( 82 | docstring 83 | == """ 84 | Basic Model Interface for DirtyHarry. 85 | 86 | The Good, The Bad, and the Ugly 87 | 88 | Author: 89 | - Clint Eastwood 90 | Version: 0.1 91 | License: To kill 92 | DOI: 1977.12.23 93 | URL: welldoyapunk.org 94 | 95 | Cite as: 96 | 97 | ref1 98 | 99 | ref2 100 | 101 | Examples 102 | -------- 103 | >>> from pymt.models import DirtyHarry 104 | >>> model = DirtyHarry() 105 | >>> (fname, initdir) = model.setup() 106 | >>> model.initialize(fname, dir=initdir) # doctest: +SKIP 107 | >>> for _ in range(10): # doctest: +SKIP 108 | ... model.update() 109 | >>> model.finalize() # doctest: +SKIP 110 | """.strip() 111 | ) 112 | -------------------------------------------------------------------------------- /tests/framework/test_bmi_time_units.py: -------------------------------------------------------------------------------- 1 | from pytest import approx 2 | 3 | from pymt.framework.bmi_bridge import _BmiCap 4 | 5 | 6 | class SimpleTimeBmi: 7 | def get_time_units(self): 8 | return "h" 9 | 10 | def get_start_time(self): 11 | return 1.0 12 | 13 | def get_current_time(self): 14 | return 10.5 15 | 16 | def get_end_time(self): 17 | return 72 18 | 19 | def get_time_step(self): 20 | return 0.25 21 | 22 | 23 | class Bmi(_BmiCap): 24 | _cls = SimpleTimeBmi 25 | 26 | 27 | def test_time_wrap(): 28 | """Test wrapping BMI time methods.""" 29 | bmi = Bmi() 30 | 31 | assert bmi.time_units == "h" 32 | assert bmi.start_time == approx(1.0) 33 | assert bmi.time == approx(10.5) 34 | assert bmi.end_time == approx(72.0) 35 | assert bmi.time_step == approx(0.25) 36 | assert bmi.time_units == "h" 37 | 38 | 39 | def test_time_conversion(): 40 | """Test unit conversion through units keyword.""" 41 | bmi = Bmi() 42 | 43 | bmi.time_units = "h" 44 | assert bmi.start_time == approx(1.0) 45 | 46 | bmi.time_units = "min" 47 | assert bmi.time_units == "min" 48 | assert bmi.start_time == approx(60.0) 49 | assert bmi.time == approx(630.0) 50 | 51 | bmi.time_units = "d" 52 | assert bmi.end_time == approx(3) 53 | 54 | # assert bmi.get_start_time(units="h") == approx(1.0) 55 | 56 | # assert bmi.get_start_time(units="min") == approx(60.0) 57 | # assert bmi.get_current_time(units="min") == approx(630.0) 58 | # assert bmi.get_end_time(units="d") == approx(3) 59 | 60 | 61 | def test_change_time_units(): 62 | """Test changing a component's time units.""" 63 | bmi = Bmi() 64 | 65 | assert bmi.time_units == "h" 66 | bmi.time_units = "min" 67 | assert bmi.time_units == "min" 68 | 69 | assert bmi.start_time == approx(60.0) 70 | assert bmi.time == approx(630.0) 71 | assert bmi.end_time == approx(72 * 60) 72 | 73 | bmi.time_units = "h" 74 | assert bmi.start_time == approx(1.0) 75 | assert bmi.time == approx(10.5) 76 | assert bmi.end_time == approx(72) 77 | -------------------------------------------------------------------------------- /tests/framework/test_bmi_var_units.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from gimli._udunits2 import UdunitsError 4 | from numpy.testing import assert_array_equal 5 | 6 | from pymt.framework.bmi_bridge import BmiTimeInterpolator, GridMapperMixIn, _BmiCap 7 | 8 | 9 | class SimpleBmi: 10 | def get_output_var_names(self): 11 | return ("elevation",) 12 | 13 | def get_var_grid(self, name): 14 | return 0 15 | 16 | def get_var_units(self, name): 17 | return "m" 18 | 19 | def get_var_type(self, name): 20 | return "float" 21 | 22 | def get_var_location(self, name): 23 | return "node" 24 | 25 | def get_var_nbytes(self, name): 26 | return self.get_var_itemsize(name) * 4 27 | 28 | def get_var_itemsize(self, name): 29 | return np.dtype("float").itemsize 30 | 31 | def get_value(self, name, out): 32 | out[:] = [1, 2, 3, 4] 33 | 34 | def get_grid_node_count(self, grid): 35 | return 4 36 | 37 | 38 | class Bmi(GridMapperMixIn, _BmiCap, BmiTimeInterpolator): 39 | _cls = SimpleBmi 40 | 41 | 42 | def test_value_wrap(): 43 | """Test wrapping BMI time methods.""" 44 | bmi = Bmi() 45 | 46 | assert bmi.var_units("elevation") == "m" 47 | assert_array_equal(bmi.get_value("elevation"), [1, 2, 3, 4]) 48 | 49 | 50 | def test_unit_conversion(): 51 | """Test wrapping BMI time methods.""" 52 | bmi = Bmi() 53 | 54 | assert_array_equal(bmi.get_value("elevation", units=None), [1.0, 2.0, 3.0, 4.0]) 55 | assert_array_equal(bmi.get_value("elevation", units="m"), [1.0, 2.0, 3.0, 4.0]) 56 | assert_array_equal( 57 | bmi.get_value("elevation", units="km"), [0.001, 0.002, 0.003, 0.004] 58 | ) 59 | assert_array_equal( 60 | bmi.get_value("elevation", units="m2 km-1"), [1000.0, 2000.0, 3000.0, 4000.0] 61 | ) 62 | 63 | 64 | def test_incompatible_units(): 65 | """Test wrapping BMI time methods.""" 66 | bmi = Bmi() 67 | with pytest.raises(UdunitsError): 68 | bmi.get_value("elevation", units="kg") 69 | 70 | 71 | def test_bad_units(): 72 | """Test wrapping BMI time methods.""" 73 | bmi = Bmi() 74 | with pytest.raises(UdunitsError): 75 | bmi.get_value("elevation", units="not_real_units") 76 | -------------------------------------------------------------------------------- /tests/framework/test_setup.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pymt.framework.bmi_setup import _parse_author_info 4 | 5 | 6 | @pytest.mark.parametrize("key", ("author", "authors")) 7 | def test_author(key): 8 | assert _parse_author_info({key: "John Cleese"}) == ("John Cleese",) 9 | 10 | 11 | def test_author_empty_list(): 12 | assert _parse_author_info({}) == ("",) 13 | 14 | 15 | @pytest.mark.parametrize("key", ("author", "authors")) 16 | @pytest.mark.parametrize("iter", (tuple, list)) 17 | def test_author_as_list(key, iter): 18 | assert _parse_author_info({key: iter(("John Cleese",))}) == ("John Cleese",) 19 | 20 | 21 | @pytest.mark.parametrize("key", ("author", "authors")) 22 | @pytest.mark.parametrize("iter", (tuple, list)) 23 | def test_author_multiple_authors(key, iter): 24 | assert _parse_author_info({key: iter(("John Cleese", "Eric Idle"))}) == ( 25 | "John Cleese", 26 | "Eric Idle", 27 | ) 28 | -------------------------------------------------------------------------------- /tests/framework/test_timeinterp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from pytest import approx, raises 4 | 5 | from pymt.framework.timeinterp import TimeInterpolator 6 | 7 | 8 | def test_timeinterp(): 9 | interp = TimeInterpolator(((0.0, 1.0), (1.0, 2.0), (2.0, 3.0))) 10 | assert interp(0.5) == approx(1.5) 11 | assert interp(1.75) == approx(2.75) 12 | 13 | 14 | def test_timeinterp_with_scalars(): 15 | interp = TimeInterpolator(((0.0, 1.0),)) 16 | with raises(AssertionError): 17 | assert interp(0.5) == approx(1.5) 18 | interp.add_data(((1.0, 2.0),)) 19 | assert interp(0.5) == approx(1.5) 20 | 21 | 22 | def test_timeinterp_add_data(): 23 | interp = TimeInterpolator() 24 | interp.add_data(((0.0, 1.0),)) 25 | interp.add_data(((1.0, 2.0),)) 26 | assert interp(0.5) == approx(1.5) 27 | 28 | 29 | def test_timeinterp_multiple_interps(): 30 | interp = TimeInterpolator() 31 | interp.add_data(((0.0, 1.0), (1.0, 2.0))) 32 | assert interp(0.5) == approx(1.5) 33 | 34 | interp.add_data(((2.0, 3.0),)) 35 | assert interp(0.5) == approx(1.5) 36 | assert interp(2.5) == approx(3.5) 37 | 38 | 39 | def test_timeinterp_interpolate_method(): 40 | interp = TimeInterpolator(((0.0, 1.0), (1.0, 2.0), (2.0, 3.0))) 41 | assert interp.interpolate(0.5) == approx(1.5) 42 | assert interp.interpolate(1.75) == approx(2.75) 43 | 44 | 45 | def test_timeinterp_with_arrays(): 46 | shape = (2, 3) 47 | arr = np.ones(shape, dtype=float) 48 | interp = TimeInterpolator(((0.0, arr * 1), (1.0, arr * 2), (2.0, arr * 3))) 49 | 50 | assert interp(0.5) == approx(np.full(shape, 1.5)) 51 | assert interp(1.75) == approx(np.full(shape, 2.75)) 52 | 53 | 54 | def test_timeinterp_with_dict_items(): 55 | shape = (2, 3) 56 | data = { 57 | 0.0: np.full(shape, 1.0), 58 | 1.0: np.full(shape, 2.0), 59 | 2.0: np.full(shape, 3.0), 60 | } 61 | interp = TimeInterpolator(data.items()) 62 | 63 | assert interp(0.5) == approx(np.full(shape, 1.5)) 64 | assert interp(1.75) == approx(np.full(shape, 2.75)) 65 | 66 | 67 | @pytest.mark.parametrize("method", TimeInterpolator.METHODS) 68 | def test_interp_on_a_point(method): 69 | interp = TimeInterpolator( 70 | ((0.0, 1.0), (1.0, 2.0), (2.0, 3.0), (3.0, 4.0)), method=method 71 | ) 72 | assert interp(2.0) == approx(3.0) 73 | 74 | 75 | @pytest.mark.parametrize("method", TimeInterpolator.METHODS) 76 | def test_interp_between_points(method): 77 | interp = TimeInterpolator( 78 | ((0.0, 1.0), (1.0, 2.0), (2.0, 3.0), (3.0, 4.0)), method=method 79 | ) 80 | assert 3.0 <= interp(2.5) <= 4.0 81 | 82 | 83 | @pytest.mark.parametrize("method", TimeInterpolator.METHODS) 84 | @pytest.mark.xfail(condition="pytest.param('next')", raises=AssertionError) 85 | def test_interp_outside_range(method): 86 | interp = TimeInterpolator( 87 | ((0.0, 1.0), (1.0, 2.0), (2.0, 3.0), (3.0, 4.0)), method=method 88 | ) 89 | assert np.isfinite(interp(4.5)) and interp(4.5) >= 4.0 90 | 91 | 92 | @pytest.mark.parametrize("method", TimeInterpolator.METHODS) 93 | @pytest.mark.xfail(condition="pytest.param('previous')", raises=AssertionError) 94 | def test_interp_below_range(method): 95 | interp = TimeInterpolator( 96 | ((0.0, 4.0), (1.0, 3.0), (2.0, 2.0), (3.0, 1.0)), method=method 97 | ) 98 | assert np.isfinite(interp(-0.5)) and interp(-0.5) >= 4.0 99 | 100 | 101 | @pytest.mark.parametrize("method", TimeInterpolator.METHODS) 102 | def test_interp_with_fill_value(method): 103 | interp = TimeInterpolator( 104 | ((0.0, 1.0), (1.0, 2.0), (2.0, 3.0), (3.0, 4.0)), method=method, fill_value=-1.0 105 | ) 106 | assert interp(4.5) == approx(-1.0) 107 | assert interp(-0.5) == approx(-1.0) 108 | -------------------------------------------------------------------------------- /tests/grids/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/grids/__init__.py -------------------------------------------------------------------------------- /tests/grids/test_assertions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pymt.grids import ( 4 | Rectilinear, 5 | Structured, 6 | UniformRectilinear, 7 | Unstructured, 8 | is_rectilinear, 9 | is_structured, 10 | is_uniform_rectilinear, 11 | is_unstructured, 12 | ) 13 | 14 | 15 | class TestUniformRectilinearAssertions(unittest.TestCase): 16 | def test_is_uniform_rectilinear(self): 17 | grid = UniformRectilinear((5, 4), (1, 1), (0, 0)) 18 | self.assertTrue(is_uniform_rectilinear(grid)) 19 | 20 | def test_is_not_uniform_rectilinear(self): 21 | grid = Rectilinear([1.0, 2.0, 4.0, 8.0], [1.0, 2.0, 3.0]) 22 | self.assertFalse(is_uniform_rectilinear(grid)) 23 | 24 | 25 | class TestRectilinearAssertions(unittest.TestCase): 26 | def test_is_rectilinear(self): 27 | grid = Rectilinear([1.0, 2.0, 4.0, 8.0], [1.0, 2.0, 3.0]) 28 | self.assertTrue(is_rectilinear(grid)) 29 | 30 | def test_is_not_rectilinear(self): 31 | grid = Structured([0, 1, 2, 0, 1, 2], [0, 0, 0, 1, 1, 1], (3, 2)) 32 | self.assertFalse(is_rectilinear(grid)) 33 | 34 | def test_is_strictly_rectilinear(self): 35 | grid = UniformRectilinear((5, 4), (1, 1), (0, 0)) 36 | self.assertFalse(is_rectilinear(grid)) 37 | 38 | def test_is_not_strictly_rectilinear(self): 39 | grid = UniformRectilinear((5, 4), (1, 1), (0, 0)) 40 | self.assertTrue(is_rectilinear(grid, strict=False)) 41 | 42 | 43 | class TestStructuredAssertions(unittest.TestCase): 44 | def test_is_structured(self): 45 | grid = Structured([0, 1, 2, 0, 1, 2], [0, 0, 0, 1, 1, 1], (3, 2)) 46 | self.assertTrue(is_structured(grid)) 47 | 48 | def test_is_not_structured(self): 49 | grid = Unstructured( 50 | [0, 1, 2, 0, 1, 2], [0, 0, 0, 1, 1, 1], [0, 1, 4, 3, 1, 2, 5, 4], [4, 8] 51 | ) 52 | self.assertFalse(is_structured(grid)) 53 | 54 | def test_is_strictly_structured(self): 55 | grid = UniformRectilinear((5, 4), (1, 1), (0, 0)) 56 | self.assertFalse(is_structured(grid)) 57 | 58 | def test_is_not_strictly_structured(self): 59 | grid = UniformRectilinear((5, 4), (1, 1), (0, 0)) 60 | self.assertTrue(is_structured(grid, strict=False)) 61 | 62 | 63 | class TestUnstructuredAssertions(unittest.TestCase): 64 | def test_is_unstructured(self): 65 | grid = Unstructured( 66 | [0, 1, 2, 0, 1, 2], [0, 0, 0, 1, 1, 1], [0, 1, 4, 3, 1, 2, 5, 4], [4, 8] 67 | ) 68 | self.assertTrue(is_unstructured(grid)) 69 | 70 | def test_is_strictly_unstructured(self): 71 | grid = UniformRectilinear((5, 4), (1, 1), (0, 0)) 72 | self.assertFalse(is_unstructured(grid)) 73 | 74 | def test_is_not_strictly_unstructured(self): 75 | grid = UniformRectilinear((5, 4), (1, 1), (0, 0)) 76 | self.assertTrue(is_unstructured(grid, strict=False)) 77 | 78 | 79 | def suite(): 80 | suite = unittest.TestLoader().loadTestsFromTestCase( 81 | TestUniformRectilinearAssertions 82 | ) 83 | suite = unittest.TestLoader().loadTestsFromTestCase(TestRectilinearAssertions) 84 | suite = unittest.TestLoader().loadTestsFromTestCase(TestStructuredAssertions) 85 | suite = unittest.TestLoader().loadTestsFromTestCase(TestUnstructuredAssertions) 86 | return suite 87 | 88 | 89 | if __name__ == "__main__": 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /tests/grids/test_grid_type.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pymt.grids.grid_type import ( 4 | GridType, 5 | GridTypeRectilinear, 6 | GridTypeStructured, 7 | GridTypeUnstructured, 8 | ) 9 | 10 | 11 | class TestGridType(unittest.TestCase): 12 | def test_rectilinear(self): 13 | type = GridTypeRectilinear() 14 | self.assertEqual(str(type), "rectilinear") 15 | self.assertEqual(type, "rectilinear") 16 | self.assertEqual(type, GridTypeRectilinear()) 17 | self.assertNotEqual(type, GridTypeStructured) 18 | self.assertNotEqual(type, GridType) 19 | self.assertIsInstance(type, GridType) 20 | 21 | def test_structured(self): 22 | type = GridTypeStructured() 23 | self.assertEqual(str(type), "structured") 24 | self.assertEqual(type, "structured") 25 | self.assertEqual(type, GridTypeStructured()) 26 | self.assertNotEqual(type, GridTypeRectilinear) 27 | self.assertNotEqual(type, GridType) 28 | self.assertIsInstance(type, GridType) 29 | 30 | def test_unstructured(self): 31 | type = GridTypeUnstructured() 32 | self.assertEqual(str(type), "unstructured") 33 | self.assertEqual(type, "unstructured") 34 | self.assertEqual(type, GridTypeUnstructured()) 35 | self.assertNotEqual(type, GridTypeRectilinear) 36 | self.assertNotEqual(type, GridType) 37 | self.assertIsInstance(type, GridType) 38 | -------------------------------------------------------------------------------- /tests/mappers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/mappers/__init__.py -------------------------------------------------------------------------------- /tests/mappers/test_mapper.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_equal 3 | 4 | from pymt.grids.map import ( 5 | UniformRectilinearMap as UniformRectilinear, 6 | UnstructuredPointsMap as UnstructuredPoints, 7 | ) 8 | from pymt.mappers import CellToPoint, PointToCell 9 | 10 | 11 | def test_cell_to_point(): 12 | # The source grid looks like, 13 | # 14 | # (0) ------ (1) 15 | # | | 16 | # | | 17 | # (2) ------ (3) 18 | # | | 19 | # | | 20 | # (4) ------ (5) 21 | # | | 22 | # | | 23 | # (7) ------ (7) 24 | # 25 | 26 | (dst_x, dst_y) = (np.array([0.45, 1.25, 3.5]), np.array([0.75, 2.25, 3.25])) 27 | 28 | src = UniformRectilinear((2, 4), (2, 1), (0, 0)) 29 | dst = UnstructuredPoints(dst_x, dst_y) 30 | 31 | src_vals = np.arange(src.get_cell_count(), dtype=float) 32 | 33 | mapper = CellToPoint() 34 | mapper.initialize(dst, src) 35 | dst_vals = mapper.run(src_vals, bad_val=-999) 36 | 37 | assert_array_equal(dst_vals, [0.0, 2.0, -999.0]) 38 | 39 | src_vals = np.arange(src.get_cell_count(), dtype=float) 40 | src_vals[0] = -9999 41 | dst_vals = np.zeros(dst.get_point_count()) + 100 42 | mapper.run(src_vals, dst_vals=dst_vals) 43 | 44 | assert_array_equal(dst_vals, [100.0, 2.0, -999.0]) 45 | 46 | 47 | def test_point_to_cell(): 48 | (src_x, src_y) = ( 49 | np.array([0.45, 1.25, 3.5, 0.0, 1.0]), 50 | np.array([0.75, 2.25, 3.25, 0.9, 1.1]), 51 | ) 52 | 53 | src = UnstructuredPoints(src_x, src_y) 54 | dst = UniformRectilinear((2, 4), (2, 1), (0, 0)) 55 | 56 | src_vals = np.arange(src.get_point_count(), dtype=float) 57 | 58 | mapper = PointToCell() 59 | mapper.initialize(dst, src) 60 | 61 | dst_vals = mapper.run(src_vals, bad_val=-999) 62 | assert_array_equal(dst_vals, [1.5, 4.0, 1.0]) 63 | 64 | dst_vals = mapper.run(src_vals, bad_val=-999, method=np.sum) 65 | assert_array_equal(dst_vals, [3.0, 4.0, 1.0]) 66 | 67 | src_vals[0] = -9999 68 | dst_vals = np.zeros(dst.get_cell_count()) - 1 69 | mapper.run(src_vals, dst_vals=dst_vals) 70 | assert_array_equal(dst_vals, [-1.0, 4.0, 1.0]) 71 | 72 | 73 | def test_point_to_cell_on_edges(): 74 | (src_x, src_y) = ( 75 | np.array([0, 0.5, 1.0, 2, 3.5]), 76 | np.array([1.0, 1.0, 0.0, 3, 3.0]), 77 | ) 78 | src = UnstructuredPoints(src_x, src_y) 79 | dst = UniformRectilinear((2, 4), (2, 1), (0, 0)) 80 | 81 | mapper = PointToCell() 82 | mapper.initialize(dst, src) 83 | 84 | src_vals = np.arange(src.get_point_count(), dtype=float) 85 | dst_vals = np.zeros(dst.get_cell_count()) - 1 86 | mapper.run(src_vals, dst_vals=dst_vals) 87 | assert_array_equal(dst_vals, [1.0, 0.5, 3.0]) 88 | 89 | 90 | def test_point_to_cell_big(): 91 | (m, n) = (20, 40) 92 | (src_x, src_y) = np.meshgrid(range(m), range(n)) 93 | src = UnstructuredPoints(src_y, src_x) 94 | dst = UniformRectilinear((n + 1, m + 1), (1, 1), (-0.5, -0.5)) 95 | 96 | mapper = PointToCell() 97 | mapper.initialize(dst, src) 98 | 99 | src_vals = np.arange(src.get_point_count(), dtype=float) 100 | dst_vals = np.zeros(dst.get_cell_count(), dtype=float) - 1 101 | mapper.run(src_vals, dst_vals=dst_vals) 102 | assert_array_equal(dst_vals, src_vals) 103 | -------------------------------------------------------------------------------- /tests/mappers/test_pointtopoint.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from numpy.testing import assert_array_almost_equal 4 | 5 | from pymt.grids.map import RectilinearMap as Rectilinear 6 | from pymt.mappers import NearestVal, find_mapper 7 | 8 | 9 | def test_all_good(): 10 | src = Rectilinear([0, 1, 2], [0, 2]) 11 | dst = Rectilinear([0.5, 1.5, 2.5], [0.25, 1.25]) 12 | 13 | src_vals = np.arange(src.get_point_count()) 14 | 15 | mapper = NearestVal() 16 | mapper.initialize(dst, src) 17 | dst_vals = mapper.run(src_vals) 18 | 19 | assert_array_almost_equal(dst_vals, np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0])) 20 | 21 | 22 | def test_some_bad(): 23 | src = Rectilinear([0, 1, 2], [0, 2]) 24 | dst = Rectilinear([0.5, 1.5, 2.5], [0.25, 1.25]) 25 | 26 | mapper = NearestVal() 27 | mapper.initialize(dst, src) 28 | 29 | src_vals = np.arange(src.get_point_count()) 30 | src_vals[2] = -999 31 | dst_vals = np.zeros(dst.get_point_count()) - 1 32 | rv = mapper.run(src_vals, dst_vals=dst_vals) 33 | 34 | assert rv is dst_vals 35 | assert_array_almost_equal(dst_vals, np.array([0.0, 1.0, -1.0, 3.0, 4.0, 5.0])) 36 | 37 | 38 | def test_no_destination(): 39 | src = Rectilinear([0, 1, 2], [0, 2]) 40 | dst = Rectilinear([0.5, 1.5, 2.5], [0.25, 1.25]) 41 | 42 | mapper = NearestVal() 43 | mapper.initialize(dst, src) 44 | 45 | src_vals = np.arange(src.get_point_count()) 46 | dst_vals = mapper.run(src_vals) 47 | assert_array_almost_equal(dst_vals, np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0])) 48 | 49 | 50 | def test_destination_init_zero(): 51 | src = Rectilinear([0, 1, 2], [0, 2]) 52 | dst = Rectilinear([0.5, 1.5, 2.5], [0.25, 1.25]) 53 | 54 | mapper = NearestVal() 55 | mapper.initialize(dst, src) 56 | 57 | src_vals = np.arange(src.get_point_count()) 58 | src_vals[2] = -999 59 | dst_vals = mapper.run(src_vals) 60 | assert_array_almost_equal(dst_vals, np.array([0.0, 1.0, 0.0, 3.0, 4.0, 5.0])) 61 | 62 | 63 | def test_bad_destination(): 64 | src = Rectilinear([0, 1, 2], [0, 2]) 65 | dst = Rectilinear([0.5, 1.5, 2.5], [0.25, 1.25]) 66 | 67 | mapper = NearestVal() 68 | mapper.initialize(dst, src) 69 | 70 | src_vals = np.arange(src.get_point_count()) 71 | dst_vals = [0.0] * src.get_point_count() 72 | with pytest.raises(TypeError): 73 | mapper.run(src_vals, dst_vals) 74 | 75 | 76 | def test_find_mapper(): 77 | src = Rectilinear([0, 1, 2], [0, 2]) 78 | dst = Rectilinear([0.5, 1.5, 2.5], [0.25, 1.25]) 79 | 80 | mappers = find_mapper(dst, src) 81 | 82 | assert len(mappers) == 3 83 | assert mappers[0].name == "PointToPoint" 84 | assert isinstance(mappers[0], NearestVal) 85 | 86 | 87 | def test_incompatible_grids(): 88 | grid = Rectilinear([0, 1, 2], [0, 2]) 89 | 90 | mapper = NearestVal() 91 | 92 | assert not mapper.test(grid, None) 93 | assert not mapper.test(None, grid) 94 | assert mapper.test(grid, grid) 95 | -------------------------------------------------------------------------------- /tests/portprinter/__init__.py: -------------------------------------------------------------------------------- 1 | def setup(): 2 | from pymt.framework.services import ( 3 | del_services, 4 | instantiate_component, 5 | register_component_classes, 6 | ) 7 | 8 | del_services() 9 | 10 | register_component_classes( 11 | ["pymt.testing.services.AirPort", "pymt.testing.services.EarthPort"] 12 | ) 13 | instantiate_component("AirPort", "air_port") 14 | instantiate_component("EarthPort", "earth_port") 15 | -------------------------------------------------------------------------------- /tests/portprinter/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="function") 5 | def with_two_components(): 6 | from pymt.framework.services import ( 7 | del_services, 8 | instantiate_component, 9 | register_component_classes, 10 | ) 11 | 12 | del_services() 13 | 14 | register_component_classes( 15 | [ 16 | "pymt.testing.services.AirPort", 17 | "pymt.testing.services.EarthPort", 18 | "pymt.testing.services.WaterPort", 19 | ] 20 | ) 21 | instantiate_component("AirPort", "air_port") 22 | instantiate_component("EarthPort", "earth_port") 23 | instantiate_component("WaterPort", "water_port") 24 | -------------------------------------------------------------------------------- /tests/portprinter/test_nc_port_printer.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymt.portprinter.port_printer import NcPortPrinter 4 | from pymt.testing.ports import UniformRectilinearGridPort 5 | 6 | 7 | def test_default(tmpdir): 8 | port = UniformRectilinearGridPort() 9 | with tmpdir.as_cwd(): 10 | printer = NcPortPrinter(port, "landscape_surface__elevation") 11 | printer.open() 12 | printer.write() 13 | 14 | assert os.path.isfile("landscape_surface__elevation.nc") 15 | 16 | 17 | def test_multiple_files(tmpdir): 18 | expected_files = [ 19 | "sea_surface__temperature.nc", 20 | "sea_surface__temperature.0.nc", 21 | "sea_surface__temperature.1.nc", 22 | "sea_surface__temperature.2.nc", 23 | "sea_surface__temperature.3.nc", 24 | ] 25 | 26 | port = UniformRectilinearGridPort() 27 | with tmpdir.as_cwd(): 28 | for _ in range(5): 29 | printer = NcPortPrinter(port, "sea_surface__temperature") 30 | printer.open() 31 | printer.write() 32 | 33 | for filename in expected_files: 34 | assert os.path.isfile(filename) 35 | 36 | 37 | def test_time_series(tmpdir): 38 | port = UniformRectilinearGridPort() 39 | with tmpdir.as_cwd(): 40 | printer = NcPortPrinter(port, "sea_floor_surface_sediment__mean_of_grain_size") 41 | printer.open() 42 | for _ in range(5): 43 | printer.write() 44 | printer.close() 45 | 46 | assert os.path.isfile("sea_floor_surface_sediment__mean_of_grain_size.nc") 47 | -------------------------------------------------------------------------------- /tests/portprinter/test_port_printer_queue.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import xarray 4 | 5 | from pymt.portprinter.port_printer import PortPrinter 6 | 7 | 8 | def test_one_printer(tmpdir, with_two_components): 9 | with tmpdir.as_cwd(): 10 | queue = PortPrinter.from_string( 11 | """ 12 | [print.ocean_surface__temperature] 13 | format=nc 14 | port=water_port 15 | """ 16 | ) 17 | assert isinstance(queue, PortPrinter) 18 | 19 | queue.open() 20 | queue.write() 21 | 22 | assert os.path.isfile("ocean_surface__temperature.nc") 23 | 24 | 25 | def test_multiple_printers(tmpdir, with_two_components): 26 | with tmpdir.as_cwd(): 27 | queue = PortPrinter.from_string( 28 | """ 29 | [print.air__density] 30 | format=nc 31 | port=air_port 32 | 33 | [print.glacier_top_surface__slope] 34 | format=nc 35 | port=earth_port 36 | """ 37 | ) 38 | 39 | assert isinstance(queue, list) 40 | 41 | for printer in queue: 42 | printer.open() 43 | 44 | for _ in range(5): 45 | for printer in queue: 46 | printer.write() 47 | 48 | assert os.path.isfile("air__density.nc") 49 | ds = xarray.open_dataset("air__density.nc", engine="h5netcdf") 50 | assert "air__density" in ds.variables 51 | assert ds.sizes["time"] == 5 52 | 53 | assert os.path.isfile("glacier_top_surface__slope.nc") 54 | ds = xarray.open_dataset("glacier_top_surface__slope.nc", engine="h5netcdf") 55 | assert "glacier_top_surface__slope" in ds.variables 56 | assert ds.sizes["time"] == 5 57 | -------------------------------------------------------------------------------- /tests/portprinter/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import pymt.portprinter.utils as pp 6 | 7 | 8 | class TestFixArrayShape(unittest.TestCase): 9 | def test_no_unknown_dimension(self): 10 | shape = pp.fix_unknown_shape((4, 5), 20) 11 | self.assertTupleEqual((4, 5), shape) 12 | 13 | shape = pp.fix_unknown_shape((), 0) 14 | self.assertTupleEqual((0,), shape) 15 | 16 | def test_one_unknown_dimension(self): 17 | shape = pp.fix_unknown_shape((4, -1), 20) 18 | self.assertTupleEqual((4, 5), shape) 19 | 20 | shape = pp.fix_unknown_shape((-1, 5), 20) 21 | self.assertTupleEqual((4, 5), shape) 22 | 23 | shape = pp.fix_unknown_shape((-1,), 20) 24 | self.assertTupleEqual((20,), shape) 25 | 26 | def test_multiple_unknown_dimension(self): 27 | with self.assertRaises(pp.DimensionError): 28 | pp.fix_unknown_shape((-1, -1), 20) 29 | 30 | def test_shape_mismatch(self): 31 | with self.assertRaises(pp.DimensionError): 32 | pp.fix_unknown_shape((20, -1), 21) 33 | 34 | with self.assertRaises(pp.DimensionError): 35 | pp.fix_unknown_shape((), 20) 36 | 37 | def test_0d(self): 38 | shape = pp.fix_unknown_shape((-1, 4, 0), 20) 39 | self.assertTupleEqual((5, 4, 0), shape) 40 | 41 | 42 | class TestFindUnknownDimension(unittest.TestCase): 43 | def test_no_unknown_dimension(self): 44 | dimen = pp.find_unknown_dimension((4, 5)) 45 | self.assertEqual(None, dimen) 46 | 47 | dimen = pp.find_unknown_dimension((4,)) 48 | self.assertEqual(None, dimen) 49 | 50 | dimen = pp.find_unknown_dimension(()) 51 | self.assertEqual(None, dimen) 52 | 53 | def test_one_unknown_dimension(self): 54 | dimen = pp.find_unknown_dimension((-1, 5)) 55 | self.assertEqual(0, dimen) 56 | dimen = pp.find_unknown_dimension((4, -1)) 57 | self.assertEqual(1, dimen) 58 | dimen = pp.find_unknown_dimension((-1,)) 59 | self.assertEqual(0, dimen) 60 | 61 | def test_multiple_unknown_dimensions(self): 62 | with self.assertRaises(pp.DimensionError): 63 | pp.find_unknown_dimension((-1, -1)) 64 | with self.assertRaises(pp.DimensionError): 65 | pp.find_unknown_dimension((-1, 4, -1)) 66 | 67 | def test_shape_as_int_array(self): 68 | dimen = pp.find_unknown_dimension(np.array((4, -1, 5), dtype=np.int64)) 69 | self.assertEqual(1, dimen) 70 | 71 | def test_shape_as_list(self): 72 | dimen = pp.find_unknown_dimension([4, 5, -1]) 73 | self.assertEqual(2, dimen) 74 | 75 | def test_shape_as_tuple(self): 76 | dimen = pp.find_unknown_dimension((4, 5, 10, -1)) 77 | self.assertEqual(3, dimen) 78 | 79 | def test_0d(self): 80 | dimen = pp.find_unknown_dimension((0, -1)) 81 | self.assertEqual(1, dimen) 82 | -------------------------------------------------------------------------------- /tests/printers/nc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/printers/nc/__init__.py -------------------------------------------------------------------------------- /tests/printers/nc/test_nc_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | 5 | from pymt.grids import RectilinearField 6 | from pymt.printers.nc.write import field_tofile 7 | 8 | 9 | def sample_surface(x, y, alpha, eta=1.0, purity=1.0): 10 | return ( 11 | 1.0 12 | + eta 13 | * ( 14 | np.exp(-(x**2) - (y - alpha) ** 2) 15 | + np.exp(-(x**2) - (y + alpha) ** 2) 16 | + 2 * purity * np.exp(-(x**2) - y**2) * np.cos(2 * alpha * x) 17 | ) 18 | / (2 * (1 + np.exp(-(alpha**2)))) 19 | ) / 2.0 20 | 21 | 22 | def sample_field(): 23 | x = np.arange(100.0) 24 | y = np.arange(100.0) 25 | field = RectilinearField(y, x) 26 | field.add_field("probability", sample_surface(x, y, 1.0)) 27 | return field 28 | 29 | 30 | def rectilinear_1d_field(): 31 | field = RectilinearField([0, 1, 2, 3, 4]) 32 | data = np.arange(field.get_point_count()) 33 | field.add_field("air__temperature", data, centering="point", units="F") 34 | return field 35 | 36 | 37 | def rectilinear_2d_field(): 38 | field = RectilinearField([-1, 0, 1], [2, 4, 6]) 39 | data = np.arange(field.get_point_count()) 40 | field.add_field("air__temperature", data, centering="point", units="F") 41 | return field 42 | 43 | 44 | def rectilinear_3d_field(): 45 | field = RectilinearField( 46 | [-1, 0, 1], 47 | [2, 4, 6], 48 | [10, 20, 30], 49 | coordinate_names=("elevation", "latitude", "longitude"), 50 | units=("m", "degrees_north", "degrees_east"), 51 | ) 52 | data = np.arange(field.get_point_count(), dtype=float) 53 | field.add_field("air__temperature", data, centering="point", units="F") 54 | return field 55 | 56 | 57 | def test_rectilinear_1d_netcdf(tmpdir): 58 | with tmpdir.as_cwd(): 59 | field_tofile(rectilinear_1d_field(), "rectilinear.1d.nc") 60 | assert os.path.isfile("rectilinear.1d.nc") 61 | 62 | 63 | def test_rectilinear_2d_netcdf(tmpdir): 64 | with tmpdir.as_cwd(): 65 | field_tofile(rectilinear_2d_field(), "rectilinear.2d.nc") 66 | assert os.path.isfile("rectilinear.2d.nc") 67 | 68 | 69 | def test_rectilinear_3d_netcdf(tmpdir): 70 | with tmpdir.as_cwd(): 71 | field_tofile(rectilinear_3d_field(), "rectilinear.3d.nc") 72 | assert os.path.isfile("rectilinear.3d.nc") 73 | -------------------------------------------------------------------------------- /tests/printers/nc/test_ugrid.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | import pytest 5 | 6 | from pymt.printers.nc.ugrid import ( 7 | NetcdfRectilinearField, 8 | NetcdfStructuredField, 9 | NetcdfUnstructuredField, 10 | ) 11 | 12 | 13 | def new_rectilinear(**kwds): 14 | from pymt.grids import RectilinearField 15 | 16 | ndims = kwds.pop("ndims", 1) 17 | shape = np.random.randint(3, 101 + 1, ndims) 18 | args = [] 19 | for size in shape: 20 | args.append(np.cumsum(1.0 - np.random.random(size))) 21 | 22 | return RectilinearField(*args, **kwds) 23 | 24 | 25 | _GRID_TYPE = { 26 | "rectilinear": NetcdfRectilinearField, 27 | "structured": NetcdfStructuredField, 28 | "unstructured": NetcdfUnstructuredField, 29 | } 30 | 31 | 32 | @pytest.mark.parametrize("ndims", (1, 2, 3)) 33 | @pytest.mark.parametrize("grid", ("rectilinear", "structured", "unstructured")) 34 | def test_rectilinear_points(tmpdir, grid, ndims): 35 | field = new_rectilinear( 36 | ndims=ndims, 37 | coordinate_names=("elevation", "latitude", "longitude"), 38 | units=("m", "degrees_north", "degrees_east"), 39 | ) 40 | data = np.arange(field.get_point_count()) 41 | field.add_field("air_temperature", data, centering="point", units="F") 42 | 43 | with tmpdir.as_cwd(): 44 | _GRID_TYPE[grid]("rectilinear.nc", field) 45 | assert os.path.isfile("rectilinear.nc") 46 | -------------------------------------------------------------------------------- /tests/printers/nc/test_ugrid_read.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pymt.printers.nc.read import field_fromfile 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "filename", 8 | [ 9 | "unstructured.2d.nc", 10 | "rectilinear.1d.nc", 11 | "rectilinear.2d.nc", 12 | "rectilinear.3d.nc", 13 | ], 14 | ) 15 | def test_read_netcdf(datadir, filename): 16 | field_fromfile(datadir / filename, fmt="NETCDF4") 17 | -------------------------------------------------------------------------------- /tests/printers/nc/test_ugrid_read/rectilinear.1d.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/printers/nc/test_ugrid_read/rectilinear.1d.nc -------------------------------------------------------------------------------- /tests/printers/nc/test_ugrid_read/rectilinear.2d.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/printers/nc/test_ugrid_read/rectilinear.2d.nc -------------------------------------------------------------------------------- /tests/printers/nc/test_ugrid_read/rectilinear.3d.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/printers/nc/test_ugrid_read/rectilinear.3d.nc -------------------------------------------------------------------------------- /tests/printers/nc/test_ugrid_read/unstructured.2d.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/printers/nc/test_ugrid_read/unstructured.2d.nc -------------------------------------------------------------------------------- /tests/services/constant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csdms/pymt/c0544b6874bd800e5f476a7e89de1139f18fcfe0/tests/services/constant/__init__.py -------------------------------------------------------------------------------- /tests/services/constant/test_constant_scalar.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | import yaml 4 | from pytest import approx 5 | 6 | from pymt.services.constant.constant import ConstantScalars 7 | 8 | 9 | def test_initialize(tmpdir): 10 | with tmpdir.as_cwd(): 11 | with open("params.yml", "w") as fp: 12 | yaml.dump({"foo": 2, "bar": 3.0}, fp) 13 | c = ConstantScalars() 14 | c.initialize("params.yml") 15 | 16 | names = c.get_output_var_names() 17 | names.sort() 18 | assert names == ["bar", "foo"] 19 | assert c.get_input_var_names() == [] 20 | 21 | assert c.get_value("foo") == approx(np.array(2.0)) 22 | assert c.get_value("bar") == approx(np.array(3.0)) 23 | with pytest.raises(KeyError): 24 | c.get_value("baz") 25 | 26 | 27 | def test_time_funcs(tmpdir): 28 | with tmpdir.as_cwd(): 29 | with open("params.yml", "w") as fp: 30 | yaml.dump({"foo": 2, "bar": 3.0}, fp) 31 | c = ConstantScalars() 32 | c.initialize("params.yml") 33 | 34 | assert c.get_start_time() == approx(0.0) 35 | assert c.get_current_time() == approx(0.0) 36 | 37 | c.update_until(12.5) 38 | assert c.get_current_time() == approx(12.5) 39 | 40 | 41 | def test_grid_funcs(tmpdir): 42 | with tmpdir.as_cwd(): 43 | with open("params.yml", "w") as fp: 44 | yaml.dump({"foo": 2, "bar": 3.0}, fp) 45 | c = ConstantScalars() 46 | c.initialize("params.yml") 47 | 48 | assert c.get_var_grid("foo") == 0 49 | 50 | assert c.get_grid_shape(0) == (1,) 51 | assert c.get_grid_spacing(0) == (1,) 52 | assert c.get_grid_origin(0) == (0,) 53 | 54 | with pytest.raises(KeyError): 55 | c.get_var_grid("baz") 56 | -------------------------------------------------------------------------------- /tests/test_models.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from pymt import models 6 | 7 | MODELS = [models.__dict__[name] for name in models.__all__] 8 | MODELS.sort(key=lambda item: item.__name__) 9 | 10 | 11 | @pytest.mark.parametrize("cls", MODELS) 12 | def test_model_setup(cls): 13 | model = cls() 14 | args = model.setup() 15 | assert os.path.isfile(os.path.join(args[1], args[0])) 16 | 17 | 18 | @pytest.mark.parametrize("cls", MODELS) 19 | def test_model_initialize(cls): 20 | model = cls() 21 | args = model.setup() 22 | model.initialize(*args) 23 | 24 | assert model.initdir == args[1] 25 | assert model._initialized 26 | 27 | 28 | @pytest.mark.parametrize("cls", MODELS) 29 | def test_model_update(cls): 30 | model = cls() 31 | model.initialize(*model.setup()) 32 | model.update() 33 | # assert model.time > model.start_time 34 | 35 | 36 | @pytest.mark.parametrize("cls", MODELS) 37 | def test_model_finalize(cls): 38 | model = cls() 39 | args = model.setup() 40 | assert os.path.isfile(os.path.join(args[1], args[0])) 41 | 42 | model.initialize(*args) 43 | assert model.initdir == args[1] 44 | assert model._initialized 45 | 46 | model.update() 47 | model.finalize() 48 | assert not model._initialized 49 | -------------------------------------------------------------------------------- /tests/test_units.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from pymt.units import transform_azimuth_to_math, transform_math_to_azimuth 5 | 6 | 7 | def test_math_to_azimuth(): 8 | angle = transform_math_to_azimuth(np.pi * 0.5, "rad") 9 | assert angle == pytest.approx(0.0) 10 | 11 | 12 | def test_math_to_azimuth_inplace(): 13 | x = np.array([0.0, 0.5, 1.0, 1.5]) * np.pi 14 | angle = transform_math_to_azimuth(x, "rad") 15 | assert angle is x 16 | assert angle == pytest.approx([np.pi * 0.5, 0.0, -np.pi * 0.5, -np.pi]) 17 | 18 | 19 | def test_azimuth_to_math(): 20 | angle = transform_azimuth_to_math(0.0, "rad") 21 | assert angle == pytest.approx(np.pi * 0.5) 22 | 23 | 24 | def test_azimuth_to_math_inplace(): 25 | x = np.array([np.pi * 0.5, 0.0, -np.pi * 0.5, -np.pi]) 26 | angle = transform_azimuth_to_math(x, "rad") 27 | assert angle is x 28 | assert angle == pytest.approx([0.0, np.pi * 0.5, np.pi, np.pi * 1.5]) 29 | --------------------------------------------------------------------------------