├── .flake8 ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── task.md └── workflows │ ├── build-and-test-feature-main.yml │ ├── build-and-test-release.yml │ ├── build-and-test-workflow.yml │ ├── docs.yml │ └── spellcheck.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── Makefile ├── api │ ├── meshkernel.c_structures.rst │ ├── meshkernel.errors.rst │ ├── meshkernel.meshkernel.rst │ ├── meshkernel.py_structures.rst │ ├── meshkernel.rst │ ├── meshkernel.utils.rst │ ├── meshkernel.version.rst │ └── modules.rst ├── conf.py ├── examples │ ├── 01_mesh2d_basics.ipynb │ ├── 02_mesh1d_basics.ipynb │ ├── 03_tri_mesh2d_pol.ipynb │ ├── 04_curvilineargrid_basics.ipynb │ ├── 05_mesh2d_refinement_gridded_samples.ipynb │ ├── 06_mesh2d_refinement_gridded_samples_gebco.ipynb │ ├── 07_curvilineargrid_with_defined_extension.ipynb │ ├── 08_mesh2d_orthogonalization.ipynb │ ├── 09_mesh2d_deletion.ipynb │ ├── 10_mesh2d_global_grid.ipynb │ ├── 11_mesh2d_refine_ridges_gridded_samples.ipynb │ ├── 12_mesh2d_refine_gridded_samples_coastlines.ipynb │ ├── 13_mesh2d_refine_gridded_samples_strided_arrays.ipynb │ ├── 14_contacts_generation.ipynb │ ├── 15_mesh2d_refinement_casulli_based_on_depths.ipynb │ ├── data_examples │ │ ├── gaussian_bump.asc │ │ ├── gebco.asc │ │ ├── global_coastline.pol │ │ ├── stpete.xyz │ │ └── test.pol │ └── index.rst ├── images │ ├── GridRefinement.jpg │ ├── MeshOrthogonalization.jpg │ └── TriangularMeshInPolygon.jpg ├── index.md └── make.bat ├── guide-to-publish.md ├── meshkernel ├── __init__.py ├── c_structures.py ├── errors.py ├── meshkernel.py ├── py_structures.py ├── utils.py └── version.py ├── pyproject.toml ├── requirements.txt ├── scripts ├── Dockerfile ├── build_wheel.sh ├── download_sonar_tools.py ├── install_deps.sh └── module_load.sh ├── setup.py └── tests ├── conftest.py ├── mesh2d_factory.py ├── test_c_structures.py ├── test_curvilinear_basics.py ├── test_interpolation.py ├── test_mesh1d_basics.py ├── test_mesh2d_basics.py ├── test_mesh2d_factory.py ├── test_mesh2d_refinement.py ├── test_miscellaneous.py ├── test_orthogonalization.py ├── test_py_structures.py ├── test_py_structures_inputs.py ├── test_transformation_utils.py ├── test_versions.py └── transformation_utils.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | .git, 4 | __pycache__, 5 | autotest 6 | ignore = 7 | # https://flake8.pycqa.org/en/latest/user/error-codes.html 8 | # 'module' imported but unused 9 | F401, 10 | # https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes 11 | # module level import not at top of file 12 | E402, 13 | # no newline at end of file 14 | W292, 15 | # blank line contains whitespace 16 | W293, 17 | # blank line at end of file 18 | W391, 19 | # line break before binary operator 20 | W503, 21 | # line break after binary operator 22 | W504, 23 | # whitespace before ':' 24 | E203 25 | statistics = True 26 | max-line-length = 140 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ./scripts/compile_deps.sh eol=lf 2 | ./scripts/build_deps.sh eol=lf 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Version info (please complete the following information):** 27 | - OS: [e.g. Windows] 28 | - Version [e.g. 0.1.5] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task 3 | about: Describe a piece of work (typically non-coding) that should be done. 4 | title: '' 5 | labels: chore 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What is the need for this task.** 11 | Why should this work be done? 12 | 13 | **What is the task?** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test-feature-main.yml: -------------------------------------------------------------------------------- 1 | name: Build and test (feature/main) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - "feature/**" 8 | pull_request: 9 | types: 10 | - opened # triggers build when opened 11 | - synchronize # triggers build when commits are pushed to HEAD 12 | branches: 13 | - "feature/**" 14 | # Manual trigger 15 | workflow_dispatch: 16 | 17 | jobs: 18 | build: 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | platform: 23 | - macos-13 # x86_64 (free) 24 | - macos-14 # arm64 (free) 25 | build_type: 26 | - Release 27 | 28 | uses: ./.github/workflows/build-and-test-workflow.yml 29 | with: 30 | platform: ${{ matrix.platform }} 31 | build_type: ${{ matrix.build_type }} 32 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and test (release) 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'release/v[0-9]+\.[0-9]+\.[0-9]+' 7 | pull_request: 8 | types: 9 | - opened # triggers build when opened 10 | - synchronize # triggers build when commits are pushed to HEAD 11 | branches: 12 | - 'release/v[0-9]+\.[0-9]+\.[0-9]+' 13 | # Manual trigger 14 | workflow_dispatch: 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | platform: 22 | - macos-13 # x86_64 (free) 23 | - macos-13-xlarge # arm64 (billable) 24 | - macos-14 # arm64 (free) 25 | build_type: 26 | - Release 27 | 28 | uses: ./.github/workflows/build-and-test-workflow.yml 29 | with: 30 | platform: ${{ matrix.platform }} 31 | build_type: ${{ matrix.build_type }} 32 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Build and test workflow 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | platform: 7 | description: "Platform" 8 | required: true 9 | type: string 10 | build_type: 11 | description: "Build type" 12 | required: true 13 | type: string 14 | 15 | jobs: 16 | build: 17 | # Build platform 18 | runs-on: ${{ inputs.platform }} 19 | 20 | name: ${{ inputs.platform }}-${{ inputs.build_type }} 21 | 22 | # Build steps 23 | steps: 24 | # Step: Checkout 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | # Workaround for getting "git describe --tags" to work in cmake/get_version_from_git.cmake (Build step) 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Set Xcode version 32 | if: inputs.platform == 'macos-13' || inputs.platform == 'macos-13-xlarge' 33 | run: sudo xcode-select -s /Applications/Xcode_14.1.app/Contents/Developer 34 | 35 | # Step: Set up Python 36 | - name: Set up Python 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: "3.10" 40 | 41 | # Step: Install Python dependencies 42 | - name: Install Python dependencies 43 | run: python -m pip install -r requirements.txt 44 | 45 | # Step: Install system-provided dependencies 46 | # macOS 47 | - name: Install backend dependencies 48 | run: | # brew update 49 | brew install boost 50 | brew install doxygen 51 | 52 | - name: Get branch name 53 | id: get_branch_name 54 | uses: tj-actions/branch-names@v8 55 | 56 | - name: Process branch name 57 | id: process_branch_name 58 | run: | 59 | str=${{ steps.get_branch_name.outputs.current_branch }} 60 | if [[ "$str" = "main" ]]; then 61 | # MeshKernelPy default barnch is called main, but MeshKernel default branch is called master 62 | str="master" 63 | elif [[ "$str" =~ ^release/ ]]; then 64 | # branch name starts with "release/", get the semantic version from MeshKernelPy 65 | str="release" 66 | elif [[ ! "$str" =~ ^feature/ ]]; then 67 | # last possibility is being on a feature branch, which starts with "feature/" 68 | # if not, it is not possile to continue 69 | exit 1 70 | fi 71 | echo "BACK_END_BRANCH=$str" >> $GITHUB_ENV 72 | 73 | # Step: Build Wheel 74 | # The default compiler on macos is clang, switch to gcc 11. Specifying the version is necessary. 75 | # It seems like gcc and g++ are symbolic links to the default clang and clang++ compilers, respectively. 76 | # CMAKE_CXX_COMPILER_ID will evaluate to AppleClang rather than GNU on macos. 77 | - name: Build Wheel 78 | env: 79 | CC: gcc-12 80 | CXX: g++-12 81 | run: | 82 | export MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion) 83 | python setup.py build_ext 84 | python setup.py sdist 85 | python setup.py bdist_wheel 86 | 87 | # Step: Audit Wheel 88 | - name: Repair platform tag 89 | run: | 90 | wheel_name=$(find ./dist -name "meshkernel-*-macosx_*.whl") 91 | echo Found $wheel_name 92 | # generate the tag: in macosx_[MAJOR_VERSION]_[MINOR_VERSION]_[ARCH], set MINOR_VERSION to 0 otherwise pip install will fail 93 | platform_tag=$(python -c 'import platform; tag = "macosx_" + platform.mac_ver()[0].rsplit(".", 2)[0] + "_0_" + platform.machine().lower(); print(tag)') 94 | # apply new tag only if it is incorrect 95 | if [[ "$wheel_name" != *"$platform_tag"* ]]; then 96 | echo Apply the platform tag $platform_tag to $wheel_name 97 | wheel tags --platform-tag $platform_tag $wheel_name 98 | # remove incorrectly tagged wheel 99 | rm $wheel_name 100 | else 101 | echo Platform tag is correct. No action needed. 102 | fi 103 | 104 | # Step: Test 105 | - name: Test 106 | run: | 107 | wheel_name=$(find ./dist -name "meshkernel-*-macosx_*.whl") 108 | python -m pip install $wheel_name 109 | pytest ./tests 110 | 111 | # Step: Upload artifact 112 | - name: Upload artifact 113 | uses: actions/upload-artifact@v4 114 | if: always() 115 | with: 116 | name: meshkernel-${{ inputs.platform }}-${{ inputs.build_type }} 117 | path: ./dist 118 | if-no-files-found: error 119 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - "release/v[0-9].[0-9].[0-9]" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: 3.9 19 | 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install sphinx myst-nb sphinx-rtd-theme numpy matplotlib 23 | 24 | - name: Run sphinx 25 | working-directory: docs 26 | run: | 27 | sphinx-apidoc --force --separate --tocfile modules -H MeshKernelPy -o ./api ../meshkernel 28 | make clean 29 | make html 30 | 31 | - name: Deploy to github pages 32 | uses: peaceiris/actions-gh-pages@v3 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_dir: ./docs/_build/html 36 | -------------------------------------------------------------------------------- /.github/workflows/spellcheck.yml: -------------------------------------------------------------------------------- 1 | name: Codespell 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | name: Codespell Check 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: codespell-project/actions-codespell@master 15 | with: 16 | check_filenames: true 17 | skip: tests,extern,build,.git,.gitignore,*.tif,*.ppt,*.pdf,*.jpg,*.cd,*.ipynb 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | *.conda 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | MANIFEST 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # shell stuff 53 | #*.sh 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | docs/api/stubs 58 | 59 | # PyBuilder 60 | .idea/ 61 | 62 | # ignore all .ipynb_checkpoints 63 | .ipynb_checkpoints 64 | 65 | # Visual Studio Code 66 | .vscode/ 67 | 68 | # Ignore mypy cache 69 | .mypy_cache 70 | 71 | # Test artifacts 72 | tests/bin 73 | 74 | # DLLs 75 | *.dll 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Deltares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MeshKernelPy 2 | 3 | [![PyPI version](https://img.shields.io/pypi/v/meshkernel.svg)](https://pypi.python.org/pypi/meshkernel) 4 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Deltares_MeshKernelPy&metric=alert_status)](https://sonarcloud.io/dashboard?id=Deltares_MeshKernelPy) 5 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Deltares_MeshKernelPy&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Deltares_MeshKernelPy) 6 | [![Downloads](https://img.shields.io/pypi/dm/meshkernel.svg)](https://pypistats.org/packages/meshkernel) 7 | 8 | `MeshKernelPy` is a library for creating and editing meshes. 9 | It supports 1D and 2D unstructured meshes. 10 | The underlying C++ library `MeshKernel` can be found [here](https://github.com/Deltares/MeshKernel). 11 | 12 | # Installation 13 | 14 | The library can be installed from [PyPI](https://pypi.org/project/meshkernel/) by executing 15 | 16 | ```bash 17 | pip install meshkernel 18 | ``` 19 | 20 | Under Windows, If you encounter any issues importing the pip wheels, you may need to install the [Visual C++ Redistributable for Visual Studio 2019](https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170). 21 | 22 | # Examples 23 | 24 | ## Creating a triangular mesh inside a polygon 25 | 26 | In this example a mesh is created by discretizing the polygon perimeter with the desired edge length. 27 | 28 | ![](https://raw.githubusercontent.com/Deltares/MeshKernelPy/main/docs/images/TriangularMeshInPolygon.jpg) 29 | 30 | ## Mesh orthogonalization 31 | 32 | Finite volume staggered flow solvers require the mesh to be as orthogonal as possible. 33 | MeshKernel provides an algorithm to adapt the mesh and achieve a good balance between mesh orthogonality and smoothness. 34 | 35 | ![](https://raw.githubusercontent.com/Deltares/MeshKernelPy/main/docs/images/MeshOrthogonalization.jpg) 36 | 37 | ## Mesh refinement 38 | 39 | A mesh can be refined in areas based on samples or polygon selections. 40 | 41 | ![](https://raw.githubusercontent.com/Deltares/MeshKernelPy/main/docs/images/GridRefinement.jpg) 42 | 43 | # Contributing 44 | 45 | In order to install `MeshKernelPy` locally, please execute the following line inside your virtual environment 46 | 47 | ```bash 48 | pip install -e ".[tests, lint, docs]" 49 | ``` 50 | 51 | Then add a compiled `MeshKernelApi.dll` into your `src` folder. 52 | 53 | Also make sure that your editor is configured to format the code with [`black`](https://black.readthedocs.io/en/stable/) and [`isort`](https://pycqa.github.io/isort/). 54 | When modifying `Jupyter` notebooks, the [`jupyterlab-code-formatter`](https://jupyterlab-code-formatter.readthedocs.io/en/latest/installation.html) can be used. 55 | 56 | # Building and installing the wheel 57 | 58 | ## Platform-specific build 59 | 60 | A setup script is provided for building the wheel. The script is known to work under Windows, Linux and macOS. 61 | 62 | To install the dependencies, use 63 | 64 | ```powershell 65 | python -m pip install --upgrade pip 66 | python -m pip install wheel numpy matplotlib pytest 67 | 68 | ``` 69 | 70 | The environment variable `BACK_END_BRANCH` must be set prior to building the wheel. It specifies which [MeshKernel](https://github.com/Deltares/MeshKernel) branch should be built during the generation of the wheel. If one is on the `main` branch of MeshKernelPy, `BACK_END_BRANCH` must be either set to `master`. If one is an a release branch, `BACK_END_BRANCH` should be set to `release`. The version of the MeshKernel release branch is hardcoded in `meshkernel/version.py`. 71 | 72 | While in the project's root directory, to build the wheel use 73 | 74 | ```powershell 75 | python setup.py build_ext 76 | python setup.py sdist bdist_wheel 77 | ``` 78 | 79 | To install use: 80 | The wheel is installed 81 | 82 | ```powershell 83 | python -m pip install 84 | ``` 85 | 86 | where `` is the name of the generated wheel. 87 | 88 | To test, simply run `pytest`. 89 | 90 | ## Manylinux Docker image 91 | 92 | To deploy Linux wheels to PyPI, we provide a Docker image that is based on manylinux_2_28_x86_64. 93 | This image includes cmake and boost, which are necessary for compiling the native MeshKernel library (written in C++). 94 | To build the Docker image, use: 95 | 96 | ```powershell 97 | docker build --progress=plain ./scripts -t build_linux_library 98 | ``` 99 | 100 | Once the Docker image has been built, build the manylinux wheel using the following command: 101 | 102 | ```powershell 103 | docker run -e BACK_END_BRANCH= -v $(pwd):/root --rm build_linux_library 104 | ``` 105 | 106 | where `` is either `master` or `release`, as described in [Platform-specific build](#platform-specific-build). 107 | 108 | The deployable manylinux wheels will be located in dist/wheelhouse. 109 | 110 | # License 111 | 112 | `MeshKernelPy` uses the MIT license. 113 | However, the wheels on PyPI bundle the LGPL licensed [MeshKernel](https://github.com/Deltares/MeshKernel). 114 | Please make sure that this fits your needs before depending on it. 115 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 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/api/meshkernel.c_structures.rst: -------------------------------------------------------------------------------- 1 | meshkernel.c\_structures module 2 | =============================== 3 | 4 | .. automodule:: meshkernel.c_structures 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/meshkernel.errors.rst: -------------------------------------------------------------------------------- 1 | meshkernel.errors module 2 | ======================== 3 | 4 | .. automodule:: meshkernel.errors 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/meshkernel.meshkernel.rst: -------------------------------------------------------------------------------- 1 | meshkernel.meshkernel module 2 | ============================ 3 | 4 | .. automodule:: meshkernel.meshkernel 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/meshkernel.py_structures.rst: -------------------------------------------------------------------------------- 1 | meshkernel.py\_structures module 2 | ================================ 3 | 4 | .. automodule:: meshkernel.py_structures 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/meshkernel.rst: -------------------------------------------------------------------------------- 1 | meshkernel package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | meshkernel.c_structures 11 | meshkernel.errors 12 | meshkernel.meshkernel 13 | meshkernel.py_structures 14 | meshkernel.utils 15 | meshkernel.version 16 | 17 | Module contents 18 | --------------- 19 | 20 | .. automodule:: meshkernel 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | -------------------------------------------------------------------------------- /docs/api/meshkernel.utils.rst: -------------------------------------------------------------------------------- 1 | meshkernel.utils module 2 | ======================= 3 | 4 | .. automodule:: meshkernel.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/meshkernel.version.rst: -------------------------------------------------------------------------------- 1 | meshkernel.version module 2 | ========================= 3 | 4 | .. automodule:: meshkernel.version 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/modules.rst: -------------------------------------------------------------------------------- 1 | MeshKernelPy 2 | ============ 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | meshkernel 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("../")) 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = "MeshKernelPy" 21 | copyright = "2025, Deltares" 22 | author = "Deltares" 23 | release = "2.1.0" 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | "myst_nb", 32 | "sphinx.ext.autodoc", 33 | "sphinx.ext.napoleon", 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ["_templates"] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 43 | 44 | 45 | # -- Options for HTML output ------------------------------------------------- 46 | 47 | # The theme to use for HTML and HTML Help pages. See the documentation for 48 | # a list of builtin themes. 49 | # 50 | html_theme = "sphinx_rtd_theme" 51 | 52 | # Add any paths that contain custom static files (such as style sheets) here, 53 | # relative to this directory. They are copied after the builtin static files, 54 | # so a file named "default.css" will overwrite the builtin "default.css". 55 | # html_static_path = ["_static"] 56 | 57 | # Don't actually execute notebooks 58 | jupyter_execute_notebooks = "off" 59 | 60 | # If true, the current module name will be prepended to all description 61 | # unit titles (such as .. function::). 62 | add_module_names = False 63 | -------------------------------------------------------------------------------- /docs/examples/07_curvilineargrid_with_defined_extension.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "9cd952e4", 6 | "metadata": {}, 7 | "source": [ 8 | "# Curvilinear grid generation with defined extension\n", 9 | "\n", 10 | "This is a brief introduction to the process of generating curvilinear grid with a defined extension." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "bb910e9f", 16 | "metadata": {}, 17 | "source": [ 18 | "At the very beginning, the necessary libraries have to be imported." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "69288369", 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import matplotlib.pyplot as plt\n", 29 | "import numpy as np\n", 30 | "from meshkernel import (\n", 31 | " GeometryList,\n", 32 | " GriddedSamples,\n", 33 | " MakeGridParameters,\n", 34 | " MeshKernel,\n", 35 | " MeshRefinementParameters,\n", 36 | " ProjectionType,\n", 37 | " RefinementType,\n", 38 | ")" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "id": "04691c04", 44 | "metadata": {}, 45 | "source": [ 46 | "## Create a curvilinear grid in a spherical system" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "id": "aa11e457", 52 | "metadata": {}, 53 | "source": [ 54 | "The grid will extend from the origin to the upper right corner, automatically computing the number of rows and columns while adjusting the latitude to preserve an aspect ratio close to one in real-world distances." 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 2, 60 | "id": "27f2ab52", 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "lon_min, lon_max = -1, -0.2\n", 65 | "lat_min, lat_max = 49.1, 49.6\n", 66 | "lon_res, lat_res = 0.1, 0.1\n", 67 | "\n", 68 | "make_grid_parameters = MakeGridParameters()\n", 69 | "make_grid_parameters.origin_x = lon_min\n", 70 | "make_grid_parameters.origin_y = lat_min\n", 71 | "make_grid_parameters.upper_right_x = lon_max\n", 72 | "make_grid_parameters.upper_right_y = lat_max\n", 73 | "make_grid_parameters.block_size_x = lon_res\n", 74 | "make_grid_parameters.block_size_y = lat_res\n", 75 | "\n", 76 | "mk = MeshKernel(projection=ProjectionType.SPHERICAL)\n", 77 | "mk.curvilinear_compute_rectangular_grid_on_extension(make_grid_parameters)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "id": "986582fa", 83 | "metadata": {}, 84 | "source": [ 85 | "Convert the curvilinear mesh to an unstructured mesh" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 3, 91 | "id": "8a033d6d", 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "mk.curvilinear_convert_to_mesh2d()" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "id": "cce139e9", 101 | "metadata": {}, 102 | "source": [ 103 | "Plot the mesh" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 4, 109 | "id": "72cc3387", 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "data": { 114 | "image/png": "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WIsWLdKVV16p3NzcEd3GGKPvfOc7uuqqqzRhwgSVlZXp6NGjYztonHp7e1VVVSWfz6fc3Fx99atfVTgcHvY2HR0dqqysVF5ennw+n5YvX65gMDhOE8cunownTpzQF7/4RRUUFMjr9aqkpES//vWvx2ni2MSar7OzUx6P50PXjh07xnHykYnn8ZOklpYW3XHHHfJ6vfL5fLrtttv07rvvjsPEsYsn4+23337J43fvvfeO08SxifcxlN7/7+mSJUvk8Xj09NNPj+2gcYon39e+9jV94hOf0IQJE5SXl6c777xTb7zxxjhNHLtYM/b29uob3/iGZs+erQkTJujqq6/WN7/5TYVCocQMZHCJ73znO+ZHP/qRWbdunfH7/SO6TUNDg/H7/ebpp582hw8fNsuWLTMzZ84077777tgOG4fPfvaz5vrrrzevvPKKefnll80111xjVqxYMeT1w+GwKSoqMpWVlebVV181r776qrnzzjvNTTfdZPr7+8dx8pGLNaMxxnz60582N910k9m3b5/p6Ogw3/ve90xaWpo5ePDgOE09crHmO3/+vOnu7h60HnzwQZOdnW1Onz49jpOPTDyP3//8z/8Yn89n6uvrzWuvvWbeeOMN86tf/cqcPXt2nKaOTTwZFy9ebO65555Bj2MoFBqniWMTT74LfvSjH5klS5YYSaapqWlsB41TPPk2bdpkXnrpJXPs2DFz4MABU1FRYQoLC8358+fHaerYxJqxra3NfO5znzPPPvusaW9vN7/5zW/MrFmzzOc///mEzENhGUZjY+OICsvAwIApKCgw//iP/xjdO3XqlMnMzDS//OUvx3DC2L3++utGktm/f390b+fOncbj8Zi33nrrQ2+za9cuk5aWNug/jKdOnTIej8e88MILYz5zrOLJaIwxXq/X/PznPx+0N2nSJLN58+YxmzUe8eb7oBtuuMH87d/+7ViMOCrx5luwYIH51re+NR4jjlq8GRcvXmyqq6vHYcLRGc1z9NChQ+Yv/uIvTHd3t7WFJVH/Bg8fPmwkmfb29rEYc1QSlfGpp54yH/vYx8x777036pk4JZQAx44d04kTJ1RWVhbd8/v9WrBggVpaWpI42aVaWlqUm5urT33qU9G9srIypaWlad++fR96m0gkIo/HM+gXBmVlZSktLU179+4d85ljFU9GSVq0aJF+9atfqbe3VwMD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", 115 | "text/plain": [ 116 | "
" 117 | ] 118 | }, 119 | "metadata": {}, 120 | "output_type": "display_data" 121 | } 122 | ], 123 | "source": [ 124 | "mesh2d = mk.mesh2d_get()\n", 125 | "fig, ax = plt.subplots()\n", 126 | "mesh2d.plot_edges(ax, color=\"black\")" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "id": "e0e4db1d", 132 | "metadata": {}, 133 | "source": [ 134 | "## Create a curvilinear grid in a cartesian system" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "5876a99c", 140 | "metadata": {}, 141 | "source": [ 142 | "In the cartesian case no adjustment of the y coordinate is required" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 5, 148 | "id": "bbd43208", 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "min_x, min_y = 0, 0\n", 153 | "max_x, max_y = 10.0, 10.0\n", 154 | "block_size_x, block_size_y = 1, 2\n", 155 | "\n", 156 | "make_grid_parameters = MakeGridParameters()\n", 157 | "make_grid_parameters.origin_x = min_x\n", 158 | "make_grid_parameters.origin_y = min_y\n", 159 | "make_grid_parameters.upper_right_x = max_x\n", 160 | "make_grid_parameters.upper_right_y = max_y\n", 161 | "make_grid_parameters.block_size_x = block_size_x\n", 162 | "make_grid_parameters.block_size_y = block_size_y\n", 163 | "\n", 164 | "mk = MeshKernel(projection=ProjectionType.CARTESIAN)\n", 165 | "mk.curvilinear_compute_rectangular_grid_on_extension(make_grid_parameters)" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "id": "b536181a", 171 | "metadata": {}, 172 | "source": [ 173 | "Convert the curvilinear mesh to an unstructured mesh" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 6, 179 | "id": "ae8640a1", 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "mk.curvilinear_convert_to_mesh2d()" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "id": "047abe1d", 189 | "metadata": {}, 190 | "source": [ 191 | "Plot the mesh" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 7, 197 | "id": "02def304", 198 | "metadata": {}, 199 | "outputs": [ 200 | { 201 | "data": { 202 | "image/png": "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", 203 | "text/plain": [ 204 | "
" 205 | ] 206 | }, 207 | "metadata": {}, 208 | "output_type": "display_data" 209 | } 210 | ], 211 | "source": [ 212 | "mesh2d = mk.mesh2d_get()\n", 213 | "fig, ax = plt.subplots()\n", 214 | "mesh2d.plot_edges(ax, color=\"black\")" 215 | ] 216 | } 217 | ], 218 | "metadata": { 219 | "kernelspec": { 220 | "display_name": "Python 3 (ipykernel)", 221 | "language": "python", 222 | "name": "python3" 223 | }, 224 | "language_info": { 225 | "codemirror_mode": { 226 | "name": "ipython", 227 | "version": 3 228 | }, 229 | "file_extension": ".py", 230 | "mimetype": "text/x-python", 231 | "name": "python", 232 | "nbconvert_exporter": "python", 233 | "pygments_lexer": "ipython3", 234 | "version": "3.10.11" 235 | } 236 | }, 237 | "nbformat": 4, 238 | "nbformat_minor": 5 239 | } 240 | -------------------------------------------------------------------------------- /docs/examples/data_examples/test.pol: -------------------------------------------------------------------------------- 1 | * 2 | * Deltares, RGFGRID Version 6.05.00.70663 (Win64), Mar 04 2021, 08:25:21 3 | * File creation date: 2021-05-27, 16:21:48 4 | * 5 | * Coordinate System = Cartesian 6 | * 7 | L000001 8 | 106 2 9 | 2.0344000E+03 6.3593333E+02 10 | 1.9923861E+03 7.2265000E+02 11 | 1.9503722E+03 8.0936667E+02 12 | 1.9083583E+03 8.9608333E+02 13 | 1.8663444E+03 9.8280000E+02 14 | 1.8243306E+03 1.0695167E+03 15 | 1.7823167E+03 1.1562333E+03 16 | 1.7403028E+03 1.2429500E+03 17 | 1.6982889E+03 1.3296667E+03 18 | 1.6562750E+03 1.4163833E+03 19 | 1.6142611E+03 1.5031000E+03 20 | 1.5722472E+03 1.5898167E+03 21 | 1.5302333E+03 1.6765333E+03 22 | 1.5769528E+03 1.7676194E+03 23 | 1.6236722E+03 1.8587056E+03 24 | 1.6703917E+03 1.9497917E+03 25 | 1.7171111E+03 2.0408778E+03 26 | 1.7638306E+03 2.1319639E+03 27 | 1.8105500E+03 2.2230500E+03 28 | 1.8572694E+03 2.3141361E+03 29 | 1.9039889E+03 2.4052222E+03 30 | 1.9507083E+03 2.4963083E+03 31 | 1.9974278E+03 2.5873944E+03 32 | 2.0441472E+03 2.6784806E+03 33 | 2.0908667E+03 2.7695667E+03 34 | 2.1885630E+03 2.7718074E+03 35 | 2.2862593E+03 2.7740481E+03 36 | 2.3839556E+03 2.7762889E+03 37 | 2.4816519E+03 2.7785296E+03 38 | 2.5793481E+03 2.7807704E+03 39 | 2.6770444E+03 2.7830111E+03 40 | 2.7747407E+03 2.7852519E+03 41 | 2.8724370E+03 2.7874926E+03 42 | 2.9701333E+03 2.7897333E+03 43 | 3.0678296E+03 2.7919741E+03 44 | 3.1655259E+03 2.7942148E+03 45 | 3.2632222E+03 2.7964556E+03 46 | 3.3609185E+03 2.7986963E+03 47 | 3.4586148E+03 2.8009370E+03 48 | 3.5563111E+03 2.8031778E+03 49 | 3.6540074E+03 2.8054185E+03 50 | 3.7517037E+03 2.8076593E+03 51 | 3.8494000E+03 2.8099000E+03 52 | 3.9465667E+03 2.7732333E+03 53 | 4.0437333E+03 2.7365667E+03 54 | 4.1409000E+03 2.6999000E+03 55 | 4.2380667E+03 2.6632333E+03 56 | 4.3352333E+03 2.6265667E+03 57 | 4.4324000E+03 2.5899000E+03 58 | 4.5295667E+03 2.5532333E+03 59 | 4.6267333E+03 2.5165667E+03 60 | 4.7239000E+03 2.4799000E+03 61 | 4.8210667E+03 2.4432333E+03 62 | 4.9182333E+03 2.4065667E+03 63 | 4.9648407E+03 2.3133519E+03 64 | 5.0114481E+03 2.2201370E+03 65 | 5.0580556E+03 2.1269222E+03 66 | 5.1046630E+03 2.0337074E+03 67 | 5.1512704E+03 1.9404926E+03 68 | 5.1978778E+03 1.8472778E+03 69 | 5.2444852E+03 1.7540630E+03 70 | 5.2910926E+03 1.6608481E+03 71 | 5.3377000E+03 1.5676333E+03 72 | 5.3287370E+03 1.4712815E+03 73 | 5.3197741E+03 1.3749296E+03 74 | 5.3108111E+03 1.2785778E+03 75 | 5.3018481E+03 1.1822259E+03 76 | 5.2928852E+03 1.0858741E+03 77 | 5.2839222E+03 9.8952222E+02 78 | 5.2749593E+03 8.9317037E+02 79 | 5.2659963E+03 7.9681852E+02 80 | 5.2570333E+03 7.0046667E+02 81 | 5.1642667E+03 6.5690667E+02 82 | 5.0715000E+03 6.1334667E+02 83 | 4.9787333E+03 5.6978667E+02 84 | 4.8859667E+03 5.2622667E+02 85 | 4.7932000E+03 4.8266667E+02 86 | 4.7004333E+03 4.3910667E+02 87 | 4.6076667E+03 3.9554667E+02 88 | 4.5149000E+03 3.5198667E+02 89 | 4.4221333E+03 3.0842667E+02 90 | 4.3293667E+03 2.6486667E+02 91 | 4.2300458E+03 2.6133750E+02 92 | 4.1307250E+03 2.5780833E+02 93 | 4.0314042E+03 2.5427917E+02 94 | 3.9320833E+03 2.5075000E+02 95 | 3.8327625E+03 2.4722083E+02 96 | 3.7334417E+03 2.4369167E+02 97 | 3.6341208E+03 2.4016250E+02 98 | 3.5348000E+03 2.3663333E+02 99 | 3.4354792E+03 2.3310417E+02 100 | 3.3361583E+03 2.2957500E+02 101 | 3.2368375E+03 2.2604583E+02 102 | 3.1375167E+03 2.2251667E+02 103 | 3.0381958E+03 2.1898750E+02 104 | 2.9388750E+03 2.1545833E+02 105 | 2.8395542E+03 2.1192917E+02 106 | 2.7402333E+03 2.0840000E+02 107 | 2.6520042E+03 2.6184167E+02 108 | 2.5637750E+03 3.1528333E+02 109 | 2.4755458E+03 3.6872500E+02 110 | 2.3873167E+03 4.2216667E+02 111 | 2.2990875E+03 4.7560833E+02 112 | 2.2108583E+03 5.2905000E+02 113 | 2.1226292E+03 5.8249167E+02 114 | 2.0344000E+03 6.3593333E+02 115 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples/main: 2 | 3 | Examples 4 | ========== 5 | 6 | These are Jupyter notebooks embedded via `MyST-NB 7 | `_. 8 | If you want to play with them on your own machine, you can download them from `here 9 | `_. 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 01_mesh2d_basics.ipynb 15 | 02_mesh1d_basics.ipynb 16 | 03_tri_mesh2d_pol.ipynb 17 | 04_curvilineargrid_basics.ipynb 18 | 05_mesh2d_refinement_gridded_samples.ipynb 19 | 06_mesh2d_refinement_gridded_samples_gebco.ipynb 20 | 07_curvilineargrid_with_defined_extension.ipynb 21 | -------------------------------------------------------------------------------- /docs/images/GridRefinement.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/MeshKernelPy/23c479efd8e5228fe28de5101c45bd862b046649/docs/images/GridRefinement.jpg -------------------------------------------------------------------------------- /docs/images/MeshOrthogonalization.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/MeshKernelPy/23c479efd8e5228fe28de5101c45bd862b046649/docs/images/MeshOrthogonalization.jpg -------------------------------------------------------------------------------- /docs/images/TriangularMeshInPolygon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/MeshKernelPy/23c479efd8e5228fe28de5101c45bd862b046649/docs/images/TriangularMeshInPolygon.jpg -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # MeshKernelPy 2 | 3 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Deltares_MeshKernelPy&metric=alert_status)](https://sonarcloud.io/dashboard?id=Deltares_MeshKernelPy) 4 | [![PyPI version](https://badge.fury.io/py/meshkernel.svg)](https://badge.fury.io/py/meshkernel) 5 | 6 | `MeshKernelPy` is a library for creating and editing meshes. 7 | It supports 1D and 2D unstructured meshes. 8 | The underlying C++ library `MeshKernel` can be found [here](https://github.com/Deltares/MeshKernel). 9 | 10 | # Installation 11 | 12 | The library can be installed from [PyPI](https://pypi.org/project/meshkernel/) by executing 13 | 14 | ```bash 15 | pip install meshkernel 16 | ``` 17 | 18 | Under Windows, If you encounter any issues importing the pip wheels, you may need to install the [Visual C++ Redistributable for Visual Studio 2019](https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170). 19 | 20 | # Examples 21 | 22 | ## Creating a triangular mesh inside a polygon 23 | 24 | In this example a mesh is created by discretizing the polygon perimeter with the desired edge length. 25 | 26 | ![](https://raw.githubusercontent.com/Deltares/MeshKernelPy/main/docs/images/TriangularMeshInPolygon.jpg) 27 | 28 | ## Mesh orthogonalization 29 | 30 | Finite volume staggered flow solvers require the mesh to be as orthogonal as possible. 31 | MeshKernel provides an algorithm to adapt the mesh and achieve a good balance between mesh orthogonality and smoothness. 32 | 33 | ![](https://raw.githubusercontent.com/Deltares/MeshKernelPy/main/docs/images/MeshOrthogonalization.jpg) 34 | 35 | ## Mesh refinement 36 | 37 | A mesh can be refined in areas based on samples or polygon selections. 38 | 39 | ![](https://raw.githubusercontent.com/Deltares/MeshKernelPy/main/docs/images/GridRefinement.jpg) 40 | 41 | # Contributing 42 | 43 | In order to install `MeshKernelPy` locally, please execute the following line inside your virtual environment 44 | 45 | ```bash 46 | pip install -e ".[tests, lint, docs]" 47 | ``` 48 | 49 | Then add a compiled `MeshKernelApi.dll` into your `src` folder. 50 | 51 | Also make sure that your editor is configured to format the code with [`black`](https://black.readthedocs.io/en/stable/) and [`isort`](https://pycqa.github.io/isort/). 52 | When modifying `Jupyter` notebooks, the [`jupyterlab-code-formatter`](https://jupyterlab-code-formatter.readthedocs.io/en/latest/installation.html) can be used. 53 | 54 | # Building and installing the wheel 55 | 56 | ## Platform-specific build 57 | 58 | A setup script is provided for building the wheel. The script is known to work under Windows, Linux and macOS. 59 | 60 | To install the dependencies, use 61 | 62 | ```powershell 63 | python -m pip install --upgrade pip 64 | python -m pip install wheel numpy matplotlib pytest 65 | 66 | ``` 67 | 68 | The environment variable `BACK_END_BRANCH` must be set prior to building the wheel. It specifies which [MeshKernel](https://github.com/Deltares/MeshKernel) branch should be built during the generation of the wheel. If one is on the `main` branch of MeshKernelPy, `BACK_END_BRANCH` must be either set to `master`. If one is an a release branch, `BACK_END_BRANCH` should be set to `release`. The version of the MeshKernel release branch is hardcoded in `meshkernel/version.py`. 69 | 70 | While in the project's root directory, to build the wheel use 71 | 72 | ```powershell 73 | python setup.py build_ext 74 | python setup.py sdist bdist_wheel 75 | ``` 76 | 77 | To install use: 78 | The wheel is installed 79 | 80 | ```powershell 81 | python -m pip install 82 | ``` 83 | 84 | where `` is the name of the generated wheel. 85 | 86 | To test, simply run `pytest`. 87 | 88 | ## Manylinux Docker image 89 | 90 | To deploy Linux wheels to PyPI, we provide a Docker image that is based on manylinux_2_28_x86_64. 91 | This image includes cmake and boost, which are necessary for compiling the native MeshKernel library (written in C++). 92 | To build the Docker image, please follow these steps: 93 | 94 | ```powershell 95 | chmod +x scripts/compile_deps.sh 96 | chmod +x scripts/build_deps.sh 97 | docker build --progress=plain ./scripts -t build_linux_library 98 | ``` 99 | 100 | Once the Docker image has been built, build the linux wheels using the following command: 101 | 102 | ```powershell 103 | docker run -e BACK_END_BRANCH= -v $(pwd):/root --rm build_linux_library 104 | ``` 105 | 106 | where `` is either `master` or `release`, as described in [Platform-specific build](#platform-specific-build). 107 | 108 | The deployable linux wheels will be located in dist/wheelhouse 109 | 110 | # License 111 | 112 | `MeshKernelPy` uses the MIT license. 113 | However, the wheels on PyPI bundle the LGPL licensed [MeshKernel](https://github.com/Deltares/MeshKernel). 114 | Please make sure that this fits your needs before depending on it. 115 | 116 | # Documentation 117 | 118 | ```{toctree} 119 | :titlesonly: 120 | :maxdepth: 1 121 | examples/index 122 | api/modules 123 | ``` 124 | -------------------------------------------------------------------------------- /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=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /guide-to-publish.md: -------------------------------------------------------------------------------- 1 | # How to publish to PyPi 2 | 3 | 1) If present remove build and dist folder 4 | 5 | 2) Recursively remove all .egg-info files 6 | On powershell you can do this with 7 | ``` 8 | rm -r *.egg-info 9 | ``` 10 | 11 | 3) If not done yet, install twine via 12 | ``` 13 | pip install twine 14 | ``` 15 | 4) Update the version number in the setup.py file. 16 | 17 | 5) Re-create the wheels: 18 | ``` 19 | python setup.py sdist bdist_wheel 20 | ``` 21 | 6) Re-upload the new files: 22 | ``` 23 | twine upload dist/* 24 | ``` -------------------------------------------------------------------------------- /meshkernel/__init__.py: -------------------------------------------------------------------------------- 1 | # If you change these imports, 2 | # do not forget to sync the docs at "docs/api" 3 | from meshkernel.errors import InputError, MeshKernelError 4 | from meshkernel.meshkernel import MeshKernel 5 | from meshkernel.py_structures import ( 6 | AveragingMethod, 7 | Contacts, 8 | CurvilinearGrid, 9 | CurvilinearParameters, 10 | DeleteMeshOption, 11 | GeometryList, 12 | GriddedSamples, 13 | InterpolationParameters, 14 | InterpolationType, 15 | InterpolationValues, 16 | MakeGridParameters, 17 | Mesh1d, 18 | Mesh2d, 19 | Mesh2dLocation, 20 | MeshRefinementParameters, 21 | OrthogonalizationParameters, 22 | ProjectionType, 23 | ProjectToLandBoundaryOption, 24 | RefinementType, 25 | SplinesToCurvilinearParameters, 26 | ) 27 | from meshkernel.version import __version__ 28 | -------------------------------------------------------------------------------- /meshkernel/errors.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | import meshkernel.py_structures as mps 4 | 5 | 6 | class Error(Exception): 7 | """Base class for exceptions in this module.""" 8 | 9 | 10 | class InputError(Error): 11 | """Exception raised for errors in the input.""" 12 | 13 | 14 | class MeshKernelError(Error): 15 | """Exception raised for errors occurring in the MeshKernel library.""" 16 | 17 | def __init__(self, category: str, message: str): 18 | super().__init__(category + ": " + message) 19 | 20 | 21 | class MeshGeometryError(MeshKernelError): 22 | """Exception raised for mesh geometry errors occurring in the MeshKernel library.""" 23 | 24 | def __init__(self, message: str, info: Tuple[int, mps.Mesh2dLocation]): 25 | super().__init__("MeshGeometryError", message) 26 | self.info = info 27 | 28 | def index(self) -> int: 29 | return self.info[0] 30 | 31 | def location(self) -> mps.Mesh2dLocation: 32 | return self.info[1] 33 | -------------------------------------------------------------------------------- /meshkernel/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import numpy as np 4 | from matplotlib.collections import LineCollection 5 | 6 | 7 | def to_contiguous_numpy_array(vec) -> np.ndarray: 8 | """Ensures the input vector is contiguous before passing it to a MeshKernel C API function, 9 | if the vector has been created with slicing operations. 10 | 11 | Args: 12 | vec (np.ndarray): The input vector. 13 | 14 | Returns: 15 | np.ndarray: The contiguous vector. 16 | 17 | Raises: 18 | TypeError: If `vec` is not a NumPy array. 19 | """ 20 | if not isinstance(vec, np.ndarray): 21 | raise TypeError( 22 | "Input must be a NumPy array in to_contiguous_numpy_array function." 23 | ) 24 | 25 | return np.ascontiguousarray(vec) 26 | 27 | 28 | def plot_edges(node_x, node_y, edge_nodes, ax, *args, **kwargs): 29 | """Plots the edges at a given axes. 30 | `args` and `kwargs` will be used as parameters of the `plot` method. 31 | 32 | 33 | Args: 34 | node_x (ndarray): A 1D double array describing the x-coordinates of the nodes. 35 | node_y (ndarray): A 1D double array describing the y-coordinates of the nodes. 36 | edge_nodes (ndarray, optional): A 1D integer array describing the nodes composing each mesh 2d edge. 37 | ax (matplotlib.axes.Axes): The axes where to plot the edges 38 | """ 39 | n_edge = int(edge_nodes.size / 2) 40 | edge_coords = np.empty((n_edge, 2, 2), dtype=np.float64) 41 | node_0 = edge_nodes[0::2] 42 | node_1 = edge_nodes[1::2] 43 | edge_coords[:, 0, 0] = node_x[node_0] 44 | edge_coords[:, 0, 1] = node_y[node_0] 45 | edge_coords[:, 1, 0] = node_x[node_1] 46 | edge_coords[:, 1, 1] = node_y[node_1] 47 | line_segments = LineCollection(edge_coords, *args, **kwargs) 48 | ax.add_collection(line_segments) 49 | ax.autoscale(enable=True) 50 | 51 | 52 | def get_maximum_bounding_box_coordinates(): 53 | """Get the maximum coordinate values for a bounding box defined by floating point coordinates""" 54 | 55 | x_lower_left = -sys.float_info.max 56 | y_lower_left = -sys.float_info.max 57 | 58 | x_upper_right = sys.float_info.max 59 | y_upper_right = sys.float_info.max 60 | 61 | return x_lower_left, y_lower_left, x_upper_right, y_upper_right 62 | -------------------------------------------------------------------------------- /meshkernel/version.py: -------------------------------------------------------------------------------- 1 | # MeshKernelPy version 2 | __version__ = "8.0.0" 3 | 4 | # MeshKernel version 5 | __backend_version__ = "8.0.0" 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.isort] 2 | profile = "black" 3 | multi_line_output = 3 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools 2 | wheel 3 | auditwheel 4 | pytest 5 | numpy>=1.22 6 | matplotlib>=3.6 7 | -------------------------------------------------------------------------------- /scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | # Container for compiling MeshKernelPy 2 | # By using an old linux operating system, dynamic libraries should be compatible with most linux os 3 | 4 | FROM quay.io/pypa/manylinux_2_28_x86_64:2024-07-01-8dac23b 5 | 6 | WORKDIR /root 7 | 8 | COPY . . 9 | 10 | # versions of deps (must be set pripor to running install_deps.sh) 11 | ENV DEVTOOLSET=gcc-toolset-12 12 | ENV CMAKE_VERSION="3.26.4" 13 | ENV BOOST_VERSION="1.83.0" 14 | ENV MINICONDA3_VERSION="py310_23.11.0-2" 15 | ENV PYTHON_VERSION="3.10" 16 | 17 | # install the dependencies 18 | RUN ./install_deps.sh 19 | 20 | # Append/define necessary paths/vars 21 | ENV PATH="/opt/rh/${DEVTOOLSET}/root/usr/bin:$PATH" 22 | ENV PATH="/opt/conda/bin:$PATH" 23 | ENV Boost_INCLUDE_DIR=/opt/${BOOST_VERSION}/include 24 | ARG GNU_COMPILER_DIRECTORY=/opt/rh/${DEVTOOLSET}/root/usr/bin 25 | ENV CC=${GNU_COMPILER_DIRECTORY}/gcc 26 | ENV CXX=${GNU_COMPILER_DIRECTORY}/g++ 27 | 28 | # build the wheel 29 | CMD ["bash", "./scripts/build_wheel.sh"] 30 | -------------------------------------------------------------------------------- /scripts/build_wheel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DOCKER_CONDA_ENV="docker_conda_env" 4 | 5 | # set default channel 6 | conda config --add channels conda-forge 7 | conda config --remove channels defaults 8 | 9 | # create conda env and activate it 10 | conda create -y --force -n "${DOCKER_CONDA_ENV}" python="${PYTHON_VERSION}" pip 11 | . activate "${DOCKER_CONDA_ENV}" 12 | 13 | remove_conda_env() { 14 | # deactivate and remove conda env 15 | conda deactivate 16 | conda env remove -n "${DOCKER_CONDA_ENV}" 17 | } 18 | 19 | error() { 20 | # store last exit code before invoking any other command 21 | local EXIT_CODE="$?" 22 | # print error message 23 | echo "$(basename "$0")": "$1" 24 | remove_conda_env 25 | exit $EXIT_CODE 26 | } 27 | 28 | # clean up residual data from previous run 29 | rm -rf ./build ./*.egg-info #./dist 30 | 31 | python -m pip install -r requirements.txt || error "pip install failed" 32 | 33 | python setup.py build_ext || error "[setup] building C/C++ extension modules failed" 34 | python setup.py sdist || error "[setup] Creation of source distribution failed" 35 | python setup.py bdist_wheel || error "[setup] Building the wheel failed" 36 | 37 | ( 38 | cd dist || error "Could not change the directory to dist" 39 | list=() 40 | for file in *linux_x86_64.whl; do 41 | list+=("$file") 42 | done 43 | auditwheel show "${list[0]}" 44 | auditwheel repair "${list[0]}" 45 | ) 46 | 47 | cp ./dist/wheelhouse/*.whl . 48 | 49 | remove_conda_env 50 | -------------------------------------------------------------------------------- /scripts/download_sonar_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | Script used to download sonar tools 3 | """ 4 | 5 | import platform 6 | import shutil 7 | import zipfile 8 | from pathlib import Path 9 | 10 | import requests 11 | 12 | SONAR_SCANNER_VERSION = "5.0.1.3006" 13 | 14 | 15 | def download_file(url: str, save_path: Path, chunk_size=128) -> None: 16 | r = requests.get(url, stream=True) 17 | with save_path.open("wb") as fd: 18 | for chunk in r.iter_content(chunk_size=chunk_size): 19 | fd.write(chunk) 20 | 21 | 22 | def unzip_file(zip_file_path: Path, unzip_directory: Path) -> None: 23 | with zipfile.ZipFile(str(zip_file_path), "r") as zip_ref: 24 | zip_ref.extractall(str(unzip_directory)) 25 | 26 | 27 | def get_build_wrapper(save_dir: Path) -> None: 28 | if platform.system() == "Windows": 29 | url = "https://sonarcloud.io/static/cpp/build-wrapper-win-x86.zip" 30 | elif platform.system() == "Linux": 31 | url = "http://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip" 32 | else: 33 | raise Exception("Unsupported OS used.") 34 | 35 | save_path = save_dir / Path("build-wrapper.zip") 36 | download_file(url, save_path) 37 | unzip_file(save_path, save_dir) 38 | 39 | 40 | def rename_sonar_scanner_folder(save_dir: Path) -> None: 41 | sonar_scanner_folder = next(save_dir.glob("sonar-scanner-*")) 42 | shutil.move( 43 | str(sonar_scanner_folder), str(sonar_scanner_folder.with_name("sonar-scanner")) 44 | ) 45 | 46 | 47 | def get_scanner(save_dir: Path) -> None: 48 | if platform.system() == "Windows": 49 | url = ( 50 | "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/" 51 | f"sonar-scanner-cli-{SONAR_SCANNER_VERSION}-windows.zip" 52 | ) 53 | elif platform.system() == "Linux": 54 | url = ( 55 | "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/" 56 | f"sonar-scanner-cli-{SONAR_SCANNER_VERSION}-linux.zip" 57 | ) 58 | else: 59 | raise Exception("Unsupported OS used.") 60 | 61 | save_path = save_dir / Path("download_sonar_scanner.zip") 62 | download_file(url, save_path) 63 | unzip_file(save_path, save_dir) 64 | rename_sonar_scanner_folder(save_dir) 65 | 66 | 67 | if __name__ == "__main__": 68 | save_dir = Path(".") / Path(".sonar") 69 | save_dir.mkdir(exist_ok=True) 70 | get_build_wrapper(save_dir) 71 | get_scanner(save_dir) 72 | -------------------------------------------------------------------------------- /scripts/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | error() { 4 | # store last exit code before invoking any other command 5 | local EXIT_CODE="$?" 6 | # print error message 7 | echo "$(basename "$0")": "$1" 8 | exit $EXIT_CODE 9 | } 10 | 11 | # add development tools 12 | yum repolist all 13 | 14 | yum clean all 15 | yum makecache 16 | yum update 17 | 18 | dnf clean all 19 | dnf makecache 20 | dnf update 21 | 22 | # NetCDF 23 | dnf -y install epel-release || error "[dnf] Failed to install epel-release" 24 | dnf -y config-manager --set-enabled powertools || error "[dnf] Failed to enable powertools" 25 | dnf -y install netcdf || error "[dnf] Failed to install netcdf" 26 | 27 | # git 28 | yum -y install git || error "[yum] Failed to install git" 29 | 30 | # wget 31 | yum -y install wget || error "[yum] Failed to install wget" 32 | 33 | # devtoolset 34 | yum -y install "${DEVTOOLSET}" || error "[yum] Failed to install ${DEVTOOLSET}" 35 | scl enable "${DEVTOOLSET}" bash || error "[scl] Failed to enable ${DEVTOOLSET}" 36 | 37 | # enter root 38 | ( 39 | cd /root || error "Could not change the directory to root" 40 | 41 | # install CMake 42 | CMAKE_SCRIPT="cmake-${CMAKE_VERSION}-linux-x86_64.sh" 43 | CMAKE_URL="https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_SCRIPT}" 44 | echo "${CMAKE_URL}" 45 | wget "${CMAKE_URL}" || error "[cmake] ${CMAKE_SCRIPT} download failed" 46 | CMAKE_INSTALL_PREFIX=/opt/cmake 47 | mkdir ${CMAKE_INSTALL_PREFIX} || error "[cmake] Creation of ${CMAKE_INSTALL_PREFIX} failed" 48 | chmod +x "${CMAKE_SCRIPT}" || error "[cmake] Changing the permissions of ${CMAKE_SCRIPT} failed" 49 | bash "${CMAKE_SCRIPT}" --skip-license --prefix=${CMAKE_INSTALL_PREFIX} || error "[cmake] Installation failed" 50 | 51 | # install boost 52 | BOOST_LIB=boost_"${BOOST_VERSION//./_}" 53 | # Official mirror 54 | # BOOST_MIRROR=https://boostorg.jfrog.io/artifactory/main/release/"${BOOST_VERSION}"/source/"${BOOST_LIB}".tar.gz 55 | # Alternative mirror to use if the official mirror is down 56 | # BOOST_MIRROR=https://mirror.bazel.build/boostorg.jfrog.io/artifactory/main/release/"${BOOST_VERSION}"/source/"${BOOST_LIB}".tar.gz 57 | # sourceforge 58 | BOOST_MIRROR=https://sourceforge.net/projects/boost/files/boost/"${BOOST_VERSION}"/"${BOOST_LIB}".tar.gz 59 | wget "${BOOST_MIRROR}" || error "[boost] ${BOOST_LIB}.tar.gz download failed" 60 | export LD_LIBRARY_PATH=/usr/local/lib:/usr/lib:/usr/local/lib64:/usr/lib64:"$LD_LIBRARY_PATH" 61 | tar -xzf "${BOOST_LIB}".tar.gz || error "[boost] ${BOOST_LIB}.tar.gz extraction failed" 62 | ( 63 | cd "${BOOST_LIB}" || error "Could not change the directory to ${BOOST_LIB}" 64 | ./bootstrap.sh --with-libraries=filesystem,system || error "[boost] bootstrap failed" 65 | ./b2 -j4 cxxflags="-fPIC" runtime-link=static variant=release link=static --prefix=/opt/boost_"${BOOST_VERSION}" install \ 66 | || error "[boost] Installation failed" 67 | ) 68 | 69 | # install miniconda 70 | MINICONDA3_SCRIPT="Miniconda3-${MINICONDA3_VERSION}-Linux-x86_64.sh" 71 | MINICONDA_URL="https://repo.continuum.io/miniconda/${MINICONDA3_SCRIPT}" 72 | wget "${MINICONDA_URL}" || error "[miniconda] ${MINICONDA3_SCRIPT} download failed" 73 | chmod +x "${MINICONDA3_SCRIPT}" || error "[miniconda] Changing the permissions of ${MINICONDA3_SCRIPT} failed" 74 | bash "${MINICONDA3_SCRIPT}" -b -p /opt/conda || error '[miniconda] Installation failed' 75 | ) 76 | -------------------------------------------------------------------------------- /scripts/module_load.sh: -------------------------------------------------------------------------------- 1 | # Script that is meant to be sourced in the CI 2 | # It includes the necessary module loads to build MeshKernel on Deltares Linux machines 3 | 4 | module load cmake/3.23.1_gcc11.3.0 5 | module load gcc/11.3.0 6 | module load boost/1.81.0_gcc11.3.0 7 | module load netcdf/v4.9.1_gcc11.3.0 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os 3 | import pathlib 4 | import platform 5 | import shutil 6 | from collections import namedtuple 7 | 8 | from setuptools import Extension, find_packages, setup 9 | from setuptools.command.build_ext import build_ext as build_ext_orig 10 | 11 | from meshkernel.version import __backend_version__ 12 | 13 | author_dict = { 14 | "Julian Hofer": "julian.hofer@deltares.nl", 15 | "Prisca van der Sluis": "prisca.vandersluis@deltares.nl", 16 | "Luca Carniato": "luca.carniato@deltares.nl", 17 | } 18 | __author__ = ", ".join(author_dict.keys()) 19 | __author_email__ = ", ".join(s for _, s in author_dict.items()) 20 | 21 | 22 | def read(rel_path: str) -> str: 23 | """Used to read a text file 24 | 25 | Args: 26 | rel_path (str): Relative path to the file 27 | 28 | Returns: 29 | str: File content 30 | """ 31 | here = os.path.abspath(os.path.dirname(__file__)) 32 | with codecs.open(os.path.join(here, rel_path), "r") as fp: 33 | return fp.read() 34 | 35 | 36 | def get_version(rel_path: str) -> str: 37 | """Get the version string 38 | 39 | Args: 40 | rel_path (str): Relative path to the file 41 | 42 | Raises: 43 | RuntimeError: Raised if the version string could not be found 44 | 45 | Returns: 46 | str: The version string 47 | """ 48 | for line in read(rel_path).splitlines(): 49 | if line.startswith("__version__"): 50 | delim = '"' if '"' in line else "'" 51 | return line.split(delim)[1] 52 | 53 | raise RuntimeError("Unable to find version string.") 54 | 55 | 56 | LibraryMeta = namedtuple("LibraryMeta", "system, name") 57 | LibraryMeta.__doc__ = """A namedtuple that contains the library meta. 58 | It has 2 fields: 59 | system - System (OS) name 60 | name - Library file name""" 61 | 62 | 63 | def get_library_meta() -> LibraryMeta: 64 | """Get the filename of the MeshKernel library 65 | 66 | Raises: 67 | OSError: If the operating system is not supported 68 | 69 | Returns: 70 | LibraryMeta: A namedtuple containing the library meta 71 | """ 72 | system = platform.system() 73 | if system == "Windows": 74 | name = "MeshKernelApi.dll" 75 | elif system == "Linux": 76 | name = "libMeshKernelApi.so" 77 | elif system == "Darwin": 78 | name = "libMeshKernelApi.dylib" 79 | else: 80 | if not system: 81 | system = "Unknown OS" 82 | raise OSError("Unsupported operating system: {}".format(system)) 83 | return LibraryMeta(system, name) 84 | 85 | 86 | try: 87 | from wheel.bdist_wheel import bdist_wheel as _bdist_wheel 88 | 89 | class bdist_wheel(_bdist_wheel): 90 | """Class describing our wheel. 91 | Basically it says that it is not a pure Python package, 92 | but it also does not contain any Python source and 93 | therefore works for all Python versions 94 | """ 95 | 96 | def finalize_options(self): 97 | _bdist_wheel.finalize_options(self) 98 | # Mark us as not a pure python package 99 | self.root_is_pure = False 100 | 101 | def get_tag(self): 102 | # We don't contain any python source 103 | return "py3", "none", _bdist_wheel.get_tag(self)[2] 104 | 105 | except ImportError: 106 | bdist_wheel = None 107 | 108 | 109 | class CMakeExtension(Extension): 110 | """Class for building a native cmake extension (C++)""" 111 | 112 | def __init__(self, repository): 113 | """Constructor of CMakeExtension class 114 | 115 | Args: 116 | repository (str): The git repository of the extension to build 117 | """ 118 | 119 | name = repository.split("/")[-1] 120 | super().__init__(name, sources=[]) 121 | self.repository = repository 122 | 123 | 124 | class build_ext(build_ext_orig): 125 | """Class for building an extension using cmake""" 126 | 127 | def run(self): 128 | for ext in self.extensions: 129 | self.build_cmake(ext) 130 | super().run() 131 | 132 | def build_cmake(self, ext): 133 | cwd = str(pathlib.Path().absolute()) 134 | build_temp = pathlib.Path(self.build_temp) 135 | 136 | build_temp.mkdir(parents=True, exist_ok=True) 137 | extdir = pathlib.Path(self.get_ext_fullpath(ext.name)) 138 | extdir.mkdir(parents=True, exist_ok=True) 139 | 140 | os.chdir(str(build_temp)) 141 | if not os.path.isdir(ext.name): 142 | # clone repository 143 | self.spawn(["git", "clone", ext.repository]) 144 | # switch to main, release or feature branch 145 | branch = os.getenv("BACK_END_BRANCH") 146 | try: 147 | if branch == "release": 148 | release_branch = "release/v" + __backend_version__ 149 | self.spawn( 150 | [ 151 | "git", 152 | "-C", 153 | "./MeshKernel", 154 | "switch", 155 | "-C", 156 | release_branch, 157 | "origin/" + release_branch, 158 | ] 159 | ) 160 | elif branch.startswith("feature/"): 161 | self.spawn( 162 | [ 163 | "git", 164 | "-C", 165 | "./MeshKernel", 166 | "switch", 167 | "-C", 168 | branch, 169 | "origin/" + branch, 170 | ] 171 | ) 172 | else: 173 | if branch != "master": 174 | print( 175 | "Invalid reference to branch origin/{}. Remaining on master branch.".format( 176 | branch 177 | ) 178 | ) 179 | else: 180 | print("Remaining on master branch") 181 | except Exception as ex: 182 | # spawn failed because git switch command failed (remote/origin does not exist) 183 | print( 184 | ex, 185 | "(Invalid reference to branch origin/{}). Remaining on master branch.".format( 186 | branch 187 | ), 188 | ) 189 | self.spawn(["git", "-C", "./MeshKernel", "branch"]) 190 | 191 | os.chdir(ext.name) 192 | 193 | if not self.dry_run: 194 | library_meta = get_library_meta() 195 | 196 | # configure 197 | self.spawn( 198 | [ 199 | "cmake", 200 | "-S", 201 | ".", 202 | "-B", 203 | "build", 204 | "-DCMAKE_BUILD_TYPE=Release", 205 | "-DENABLE_UNIT_TESTING=OFF", 206 | ] 207 | ) 208 | 209 | # build in release mode 210 | self.spawn(["cmake", "--build", "build", "--config", "Release", "-j4"]) 211 | 212 | if library_meta.system == "Linux" or library_meta.system == "Darwin": 213 | meshkernel_path = str( 214 | os.path.join( 215 | *[ 216 | pathlib.Path().absolute(), 217 | "build", 218 | "libs", 219 | "MeshKernelApi", 220 | library_meta.name, 221 | ] 222 | ) 223 | ) 224 | elif library_meta.system == "Windows": 225 | meshkernel_path = str( 226 | os.path.join( 227 | *[ 228 | pathlib.Path().absolute(), 229 | "build", 230 | "libs", 231 | "MeshKernelApi", 232 | "Release", 233 | library_meta.name, 234 | ] 235 | ) 236 | ) 237 | 238 | destination = os.path.join(*[cwd, "meshkernel", library_meta.name]) 239 | shutil.copyfile(meshkernel_path, destination) 240 | 241 | os.chdir(cwd) 242 | 243 | 244 | long_description = read("README.md") 245 | 246 | setup( 247 | name="meshkernel", 248 | description="`meshkernel` is a library which can be used to manipulate meshes.", 249 | long_description=long_description, 250 | long_description_content_type="text/markdown", 251 | author=__author__, 252 | author_email=__author_email__, 253 | url="https://github.com/Deltares/MeshKernelPy", 254 | license="MIT", 255 | platforms="Windows, Linux, macOS", 256 | install_requires=[ 257 | "numpy>=1.22", 258 | "matplotlib>=3.6", 259 | ], 260 | extras_require={ 261 | "tests": [ 262 | "pytest", 263 | "pytest-cov", 264 | "nbval", 265 | ], 266 | "lint": [ 267 | "flake8", 268 | "black", 269 | "isort", 270 | ], 271 | "docs": [ 272 | "sphinx", 273 | "sphinx_book_theme", 274 | "myst_nb", 275 | ], 276 | }, 277 | python_requires=">=3.8", 278 | package_data={"meshkernel": [get_library_meta().name]}, 279 | packages=find_packages(), 280 | ext_modules=[CMakeExtension("https://github.com/Deltares/MeshKernel")], 281 | cmdclass={"build_ext": build_ext, "bdist_wheel": bdist_wheel}, 282 | version=get_version("meshkernel/version.py"), 283 | classifiers=["Topic :: Scientific/Engineering :: Mathematics"], 284 | ) 285 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mesh2d_factory import Mesh2dFactory 3 | 4 | from meshkernel import MeshKernel 5 | 6 | 7 | @pytest.fixture(scope="function") 8 | def meshkernel_with_mesh2d(): 9 | """Creates a new instance of 'meshkernel' and sets a Mesh2d with the specified dimensions. 10 | 11 | Args: 12 | rows (int): Number of node rows 13 | columns (int): Number of node columns 14 | 15 | Returns: 16 | MeshKernel: The created instance of `meshkernel` 17 | """ 18 | 19 | def _create(rows: int, columns: int, spacing_x: int = 1.0, spacing_y: int = 1.0): 20 | mesh2d = Mesh2dFactory.create( 21 | rows=rows, columns=columns, spacing_x=spacing_x, spacing_y=spacing_y 22 | ) 23 | mk = MeshKernel() 24 | 25 | mk.mesh2d_set(mesh2d) 26 | 27 | return mk 28 | 29 | return _create 30 | -------------------------------------------------------------------------------- /tests/mesh2d_factory.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from meshkernel import InputError, Mesh2d 4 | 5 | 6 | class Mesh2dFactory: 7 | @staticmethod 8 | def create( 9 | rows: int, 10 | columns: int, 11 | origin_x: float = 0.0, 12 | origin_y: float = 0.0, 13 | spacing_x: float = 1.0, 14 | spacing_y: float = 1.0, 15 | ) -> Mesh2d: 16 | """Create a Mesh2d instance describing a rectilinear mesh. 17 | 18 | Args: 19 | rows (int): The number of rows. 20 | columns (int): The number of columns. 21 | origin_x (float, optional): The x-coordinate of the origin. Defaults to 0.0. 22 | origin_y (float, optional): The y-coordinate of the origin. Defaults to 0.0. 23 | spacing_x (float, optional): The spacing between the columns. Defaults to 1.0. 24 | spacing_y (float, optional): The spacing between the rows. Defaults to 1.0. 25 | 26 | Returns: 27 | Mesh2d: The calculated rectilinear mesh. 28 | """ 29 | 30 | # Validate input 31 | if rows < 1: 32 | raise InputError("There needs to be at least one row.") 33 | if not isinstance(rows, int): 34 | raise InputError("`rows` needs to be an integer.") 35 | if columns < 1: 36 | raise InputError("There needs to be at least one column.") 37 | if not isinstance(columns, int): 38 | raise InputError("`columns` needs to be an integer.") 39 | if spacing_x <= 0: 40 | raise InputError("`spacing_x` needs to be positive.") 41 | if spacing_y <= 0: 42 | raise InputError("`spacing_y` needs to be positive.") 43 | 44 | # Convert to node rows and columns 45 | node_rows = rows + 1 46 | node_columns = columns + 1 47 | 48 | # Initialize helper objects 49 | num_nodes = node_rows * node_columns 50 | indices_values = np.empty((node_rows, node_columns)) 51 | 52 | # Allocate memory for mesh arrays 53 | node_x = np.empty(num_nodes, dtype=np.double) 54 | node_y = np.empty(num_nodes, dtype=np.double) 55 | edge_nodes = np.empty( 56 | 2 * (2 * num_nodes - node_rows - node_columns), dtype=np.int32 57 | ) 58 | 59 | # Calculate node positions 60 | node_index = 0 61 | for row_index in range(node_rows): 62 | for column_index in range(node_columns): 63 | node_x[node_index] = column_index * spacing_x + origin_x 64 | node_y[node_index] = row_index * spacing_y + origin_y 65 | indices_values[row_index, column_index] = ( 66 | row_index * node_columns + column_index 67 | ) 68 | node_index += 1 69 | 70 | # Calculate edge indices 71 | edge_index = 0 72 | for row_index in range(node_rows - 1): 73 | for column_index in range(node_columns): 74 | edge_nodes[edge_index] = indices_values[row_index, column_index] 75 | edge_index += 1 76 | edge_nodes[edge_index] = indices_values[row_index + 1, column_index] 77 | edge_index += 1 78 | for row_index in range(node_rows): 79 | for column_index in range(node_columns - 1): 80 | edge_nodes[edge_index] = indices_values[row_index, column_index + 1] 81 | edge_index += 1 82 | edge_nodes[edge_index] = indices_values[row_index, column_index] 83 | edge_index += 1 84 | 85 | return Mesh2d(node_x, node_y, edge_nodes) 86 | -------------------------------------------------------------------------------- /tests/test_c_structures.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.ctypeslib import as_array 3 | from numpy.testing import assert_array_equal 4 | 5 | from meshkernel import ( 6 | Contacts, 7 | GeometryList, 8 | Mesh1d, 9 | Mesh2d, 10 | MeshRefinementParameters, 11 | OrthogonalizationParameters, 12 | RefinementType, 13 | ) 14 | from meshkernel.c_structures import ( 15 | CContacts, 16 | CGeometryList, 17 | CMesh1d, 18 | CMesh2d, 19 | CMeshRefinementParameters, 20 | COrthogonalizationParameters, 21 | ) 22 | 23 | 24 | def test_cmesh2d_from_mesh2d(): 25 | """Tests `from_mesh2d` of the `CMesh2D` class with a simple mesh.""" 26 | 27 | # 2---3 28 | # | | 29 | # 0---1 30 | node_x = np.array([0.0, 1.0, 1.0, 0.0], dtype=np.double) 31 | node_y = np.array([0.0, 0.0, 1.0, 1.0], dtype=np.double) 32 | edge_nodes = np.array([0, 1, 1, 3, 3, 2, 2, 0], dtype=np.int32) 33 | face_nodes = np.array([0, 1, 2, 3], dtype=np.int32) 34 | nodes_per_face = np.array([4], dtype=np.int32) 35 | edge_x = np.array([0.5, 1.0, 0.5, 0.0], dtype=np.double) 36 | edge_y = np.array([0.0, 0.5, 1.0, 0.5], dtype=np.double) 37 | face_x = np.array([0.5], dtype=np.double) 38 | face_y = np.array([0.5], dtype=np.double) 39 | edge_faces = np.array([0, -1, 0, -1, 0, -1, 0, -1], dtype=np.int32) 40 | face_edges = np.array([0, 1, 2, 3], dtype=np.int32) 41 | 42 | mesh2d = Mesh2d(node_x, node_y, edge_nodes) 43 | mesh2d.face_nodes = face_nodes 44 | mesh2d.nodes_per_face = nodes_per_face 45 | mesh2d.edge_x = edge_x 46 | mesh2d.edge_y = edge_y 47 | mesh2d.face_x = face_x 48 | mesh2d.face_y = face_y 49 | mesh2d.edge_faces = edge_faces 50 | mesh2d.face_edges = face_edges 51 | 52 | c_mesh2d = CMesh2d.from_mesh2d(mesh2d) 53 | 54 | # Get the numpy arrays from the ctypes object 55 | c_mesh2d_node_x = as_array(c_mesh2d.node_x, (4,)) 56 | c_mesh2d_node_y = as_array(c_mesh2d.node_y, (4,)) 57 | c_mesh2d_edge_nodes = as_array(c_mesh2d.edge_nodes, (8,)) 58 | c_mesh2d_face_nodes = as_array(c_mesh2d.face_nodes, (4,)) 59 | c_mesh2d_nodes_per_face = as_array(c_mesh2d.nodes_per_face, (1,)) 60 | c_mesh2d_edge_x = as_array(c_mesh2d.edge_x, (4,)) 61 | c_mesh2d_edge_y = as_array(c_mesh2d.edge_y, (4,)) 62 | c_mesh2d_face_x = as_array(c_mesh2d.face_x, (1,)) 63 | c_mesh2d_face_y = as_array(c_mesh2d.face_y, (1,)) 64 | 65 | # Assert data is correct 66 | assert_array_equal(c_mesh2d_node_x, node_x) 67 | assert_array_equal(c_mesh2d_node_y, node_y) 68 | assert_array_equal(c_mesh2d_edge_nodes, edge_nodes) 69 | assert_array_equal(c_mesh2d_face_nodes, face_nodes) 70 | assert_array_equal(c_mesh2d_nodes_per_face, nodes_per_face) 71 | assert_array_equal(c_mesh2d_edge_x, edge_x) 72 | assert_array_equal(c_mesh2d_edge_y, edge_y) 73 | assert_array_equal(c_mesh2d_face_x, face_x) 74 | assert_array_equal(c_mesh2d_face_y, face_y) 75 | 76 | assert c_mesh2d.num_nodes == 4 77 | assert c_mesh2d.num_edges == 4 78 | assert c_mesh2d.num_faces == 1 79 | assert c_mesh2d.num_face_nodes == 4 80 | 81 | 82 | def test_cmesh2d_allocate_memory(): 83 | """Tests `allocate_memory` of the `CMesh2D` class.""" 84 | 85 | c_mesh2d = CMesh2d() 86 | c_mesh2d.num_nodes = 4 87 | c_mesh2d.num_edges = 4 88 | c_mesh2d.num_faces = 1 89 | c_mesh2d.num_face_nodes = 4 90 | 91 | mesh2d = c_mesh2d.allocate_memory() 92 | 93 | assert mesh2d.node_x.size == 4 94 | assert mesh2d.node_y.size == 4 95 | assert mesh2d.edge_nodes.size == 8 96 | assert mesh2d.face_nodes.size == 4 97 | assert mesh2d.nodes_per_face.size == 1 98 | assert mesh2d.edge_x.size == 4 99 | assert mesh2d.edge_y.size == 4 100 | assert mesh2d.face_x.size == 1 101 | assert mesh2d.face_y.size == 1 102 | 103 | 104 | def test_cgeometrylist_from_geometrylist(): 105 | """Tests `from_geometrylist` of the `CGeometryList` class.""" 106 | 107 | x_coordinates = np.array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=np.double) 108 | y_coordinates = np.array([5.0, 6.0, 7.0, 8.0, 9.0], dtype=np.double) 109 | values = np.array([10.0, 11.0, 12.0, 13.0, 14.0], dtype=np.double) 110 | geometry_separator = 15.0 111 | inner_outer_separator = 16.0 112 | 113 | geometry_list = GeometryList(x_coordinates, y_coordinates) 114 | geometry_list.values = values 115 | geometry_list.geometry_separator = geometry_separator 116 | geometry_list.inner_outer_separator = inner_outer_separator 117 | 118 | c_geometry_list = CGeometryList.from_geometrylist(geometry_list) 119 | 120 | # Get the numpy arrays from the ctypes object 121 | c_geometry_list_x_coordinates = as_array(c_geometry_list.x_coordinates, (5,)) 122 | c_geometry_list_y_coordinates = as_array(c_geometry_list.y_coordinates, (5,)) 123 | c_geometry_list_values = as_array(c_geometry_list.values, (5,)) 124 | 125 | assert_array_equal(c_geometry_list_x_coordinates, x_coordinates) 126 | assert_array_equal(c_geometry_list_y_coordinates, y_coordinates) 127 | assert_array_equal(c_geometry_list_values, values) 128 | 129 | assert c_geometry_list.geometry_separator == geometry_separator 130 | assert c_geometry_list.inner_outer_separator == inner_outer_separator 131 | assert c_geometry_list.n_coordinates == x_coordinates.size 132 | 133 | 134 | def test_corthogonalizationparameters_from_orthogonalizationparameters(): 135 | """Tests `from_orthogonalizationparameters` of the `COrthogonalizationParameters` class.""" 136 | 137 | parameters = OrthogonalizationParameters() 138 | parameters.outer_iterations = 0 139 | parameters.boundary_iterations = 1 140 | parameters.inner_iterations = 2 141 | parameters.orthogonalization_to_smoothing_factor = 3.0 142 | parameters.orthogonalization_to_smoothing_factor_at_boundary = 4.0 143 | parameters.areal_to_angle_smoothing_factor = 5.0 144 | 145 | c_parameters = COrthogonalizationParameters.from_orthogonalizationparameters( 146 | parameters 147 | ) 148 | 149 | assert c_parameters.outer_iterations == 0 150 | assert c_parameters.boundary_iterations == 1 151 | assert c_parameters.inner_iterations == 2 152 | assert c_parameters.orthogonalization_to_smoothing_factor == 3.0 153 | assert c_parameters.orthogonalization_to_smoothing_factor_at_boundary == 4.0 154 | assert c_parameters.areal_to_angle_smoothing_factor == 5.0 155 | 156 | 157 | def test_cmeshrefinementparameters_from_meshrefinementparameters(): 158 | """Tests `from_samplerefinementparameters` of the `CMeshRefinementParameters` class.""" 159 | 160 | parameters = MeshRefinementParameters(False, True, 1.0, 2, False, True, 3) 161 | 162 | c_parameters = CMeshRefinementParameters.from_meshrefinementparameters(parameters) 163 | 164 | assert c_parameters.max_refinement_iterations == 3 165 | assert c_parameters.refine_intersected == 0 166 | assert c_parameters.use_mass_center_when_refining == 1 167 | assert c_parameters.min_edge_size == 1.0 168 | assert c_parameters.refinement_type == RefinementType.REFINEMENT_LEVELS.value 169 | assert c_parameters.connect_hanging_nodes == 0 170 | assert c_parameters.account_for_samples_outside_face == 1 171 | 172 | 173 | def test_cmesh1d_from_mesh1d(): 174 | r"""Tests `from_mesh1d` of the `CMesh1D` class with a simple mesh. 175 | 176 | 1 3 177 | / \ / 178 | 0 2 179 | """ 180 | 181 | node_x = np.array([0.0, 1.0, 2.0, 3.0], dtype=np.double) 182 | node_y = np.array([0.0, 1.0, 0.0, 1.0], dtype=np.double) 183 | edge_nodes = np.array([0, 1, 1, 2, 2, 3], dtype=np.int32) 184 | 185 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 186 | 187 | c_mesh1d = CMesh1d.from_mesh1d(mesh1d) 188 | 189 | # Get the numpy arrays from the ctypes object 190 | c_mesh1d_node_x = as_array(c_mesh1d.node_x, (4,)) 191 | c_mesh1d_node_y = as_array(c_mesh1d.node_y, (4,)) 192 | c_mesh1d_edge_nodes = as_array(c_mesh1d.edge_nodes, (6,)) 193 | 194 | # Assert data is correct 195 | assert_array_equal(c_mesh1d_node_x, node_x) 196 | assert_array_equal(c_mesh1d_node_y, node_y) 197 | assert_array_equal(c_mesh1d_edge_nodes, edge_nodes) 198 | 199 | assert c_mesh1d.num_nodes == 4 200 | assert c_mesh1d.num_edges == 3 201 | 202 | 203 | def test_cmesh1d_allocate_memory(): 204 | """Tests `allocate_memory` of the `CMesh1d` class.""" 205 | 206 | c_mesh1d = CMesh1d() 207 | c_mesh1d.num_nodes = 4 208 | c_mesh1d.num_edges = 3 209 | 210 | mesh1d = c_mesh1d.allocate_memory() 211 | 212 | assert mesh1d.node_x.size == 4 213 | assert mesh1d.node_y.size == 4 214 | assert mesh1d.edge_nodes.size == 6 215 | 216 | 217 | def test_ccontacts_from_contacts(): 218 | """Tests `from_contacts` of the `CContacts` class.""" 219 | 220 | mesh1d_indices = np.array([0, 2, 4], dtype=np.int32) 221 | mesh2d_indices = np.array([1, 3, 5], dtype=np.int32) 222 | 223 | contacts = Contacts(mesh1d_indices, mesh2d_indices) 224 | 225 | c_contacts = CContacts.from_contacts(contacts) 226 | 227 | # Get the numpy arrays from the ctypes object 228 | c_contacts_mesh1d_indices = as_array(c_contacts.mesh1d_indices, (3,)) 229 | c_contacts_mesh2d_indices = as_array(c_contacts.mesh2d_indices, (3,)) 230 | 231 | assert_array_equal(c_contacts_mesh1d_indices, mesh1d_indices) 232 | assert_array_equal(c_contacts_mesh2d_indices, mesh2d_indices) 233 | 234 | assert c_contacts.num_contacts == 3 235 | 236 | 237 | def test_ccontacts_allocate_memory(): 238 | """Tests `allocate_memory` of the `CContacts` class.""" 239 | 240 | c_contacts = CContacts() 241 | c_contacts.num_contacts = 3 242 | 243 | contacts = c_contacts.allocate_memory() 244 | 245 | assert contacts.mesh1d_indices.size == 3 246 | assert contacts.mesh2d_indices.size == 3 247 | -------------------------------------------------------------------------------- /tests/test_curvilinear_basics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pytest import approx 3 | 4 | from meshkernel import ( 5 | CurvilinearParameters, 6 | GeometryList, 7 | MakeGridParameters, 8 | MeshKernel, 9 | OrthogonalizationParameters, 10 | ProjectionType, 11 | SplinesToCurvilinearParameters, 12 | ) 13 | from meshkernel.py_structures import CurvilinearDirection 14 | 15 | 16 | def create_meshkernel_instance_with_curvilinear_grid(): 17 | r"""A function for creating an instance of meshkernel with a uniform curvilinear grid.""" 18 | mk = MeshKernel() 19 | 20 | separator = -999.0 21 | splines_x = np.array( 22 | [ 23 | 2.0, 24 | 4.0, 25 | 7.0, 26 | separator, 27 | -1.0, 28 | 1.0, 29 | 5.0, 30 | separator, 31 | 3.0, 32 | -2.0, 33 | separator, 34 | 7.0, 35 | 4.0, 36 | ], 37 | dtype=np.double, 38 | ) 39 | splines_y = np.array( 40 | [ 41 | 1.0, 42 | 3.0, 43 | 4.0, 44 | separator, 45 | 4.0, 46 | 6.0, 47 | 7.0, 48 | separator, 49 | 1.0, 50 | 6.0, 51 | separator, 52 | 3.0, 53 | 8.0, 54 | ], 55 | dtype=np.double, 56 | ) 57 | splines_values = np.zeros_like(splines_x) 58 | splines = GeometryList(splines_x, splines_y, splines_values) 59 | 60 | curvilinear_parameters = CurvilinearParameters() 61 | curvilinear_parameters.n_refinement = 10 62 | curvilinear_parameters.m_refinement = 10 63 | 64 | mk.curvilinear_compute_transfinite_from_splines(splines, curvilinear_parameters) 65 | 66 | return mk 67 | 68 | 69 | def create_meshkernel_instance_with_skewed_curvilinear_grid(num_columns=3, num_rows=3): 70 | """A function for creating an instance of meshkernel with a non uniform curvilinear grid. 71 | 72 | Args: 73 | num_columns (int): The number of columns to generate. 74 | num_rows (int): The number of rows to generate. 75 | """ 76 | mk = MeshKernel() 77 | 78 | make_grid_parameters = MakeGridParameters() 79 | make_grid_parameters.num_columns = num_columns 80 | make_grid_parameters.num_rows = num_rows 81 | make_grid_parameters.angle = 0.0 82 | make_grid_parameters.origin_x = 0.0 83 | make_grid_parameters.origin_y = 0.0 84 | make_grid_parameters.block_size_x = 10.0 85 | make_grid_parameters.block_size_y = 10.0 86 | 87 | mk.curvilinear_compute_rectangular_grid(make_grid_parameters) 88 | 89 | # Skew the grid by moving a node 90 | mk.curvilinear_move_node(10.0, 20.0, 18.0, 12.0) 91 | 92 | return mk 93 | 94 | 95 | def test_curvilinear_compute_transfinite_from_splines(): 96 | r"""Tests `curvilinear_compute_transfinite_from_splines` generates a curvilinear grid.""" 97 | mk = create_meshkernel_instance_with_curvilinear_grid() 98 | 99 | output_curvilinear = mk.curvilineargrid_get() 100 | 101 | # Test the number of m and n are as expected 102 | assert output_curvilinear.num_m == 11 103 | assert output_curvilinear.num_n == 11 104 | 105 | 106 | def test_curvilinear_compute_orthogonal_from_splines(): 107 | r"""Tests `curvilinear_compute_orthogonal_from_splines` generates a curvilinear grid using 108 | the advancing front algorithm. 109 | """ 110 | mk = MeshKernel() 111 | 112 | separator = -999.0 113 | splines_x = np.array( 114 | [ 115 | 152.001571655273, 116 | 374.752960205078, 117 | 850.255920410156, 118 | separator, 119 | 72.5010681152344, 120 | 462.503479003906, 121 | separator, 122 | ], 123 | dtype=np.double, 124 | ) 125 | splines_y = np.array( 126 | [ 127 | 86.6264953613281, 128 | 336.378997802734, 129 | 499.130676269531, 130 | separator, 131 | 391.129577636719, 132 | 90.3765411376953, 133 | separator, 134 | ], 135 | dtype=np.double, 136 | ) 137 | 138 | splines_values = np.zeros_like(splines_x) 139 | splines = GeometryList(splines_x, splines_y, splines_values) 140 | 141 | curvilinear_parameters = CurvilinearParameters() 142 | curvilinear_parameters.n_refinement = 40 143 | curvilinear_parameters.m_refinement = 20 144 | 145 | splines_to_curvilinear_parameters = SplinesToCurvilinearParameters() 146 | splines_to_curvilinear_parameters.aspect_ratio = 0.1 147 | splines_to_curvilinear_parameters.aspect_ratio_grow_factor = 1.1 148 | splines_to_curvilinear_parameters.average_width = 500.0 149 | splines_to_curvilinear_parameters.nodes_on_top_of_each_other_tolerance = 1e-4 150 | splines_to_curvilinear_parameters.min_cosine_crossing_angles = 0.95 151 | splines_to_curvilinear_parameters.check_front_collisions = 0 152 | splines_to_curvilinear_parameters.curvature_adapted_grid_spacing = 1 153 | splines_to_curvilinear_parameters.remove_skinny_triangles = 0 154 | 155 | mk.curvilinear_compute_orthogonal_from_splines( 156 | splines, curvilinear_parameters, splines_to_curvilinear_parameters 157 | ) 158 | 159 | output_curvilinear = mk.curvilineargrid_get() 160 | 161 | # Test the number of m and n are as expected 162 | assert output_curvilinear.num_m == 9 163 | assert output_curvilinear.num_n == 3 164 | 165 | 166 | def test_curvilinear_convert_to_mesh2d(): 167 | r"""Tests `curvilinear_compute_transfinite_from_splines` converts a curvilinear mesh into an unstructured mesh.""" 168 | mk = create_meshkernel_instance_with_curvilinear_grid() 169 | 170 | mk.curvilinear_convert_to_mesh2d() 171 | 172 | mesh2d = mk.mesh2d_get() 173 | 174 | curvilinear_grid = mk.curvilineargrid_get() 175 | 176 | # Test curvilinear grid is empty and mesh2d is filled 177 | assert curvilinear_grid.num_m == 0 178 | assert curvilinear_grid.num_n == 0 179 | assert len(mesh2d.node_x) == 121 180 | assert len(mesh2d.edge_nodes) == 440 181 | 182 | 183 | def test_curvilinear_compute_rectangular_grid(): 184 | r"""Tests `curvilinear_compute_rectangular_grid` makes a curvilinear grid.""" 185 | mk = MeshKernel() 186 | 187 | make_grid_parameters = MakeGridParameters() 188 | make_grid_parameters.num_columns = 3 189 | make_grid_parameters.num_rows = 3 190 | make_grid_parameters.angle = 0.0 191 | make_grid_parameters.origin_x = 0.0 192 | make_grid_parameters.origin_y = 0.0 193 | make_grid_parameters.block_size_x = 10.0 194 | make_grid_parameters.block_size_y = 10.0 195 | 196 | mk.curvilinear_compute_rectangular_grid(make_grid_parameters) 197 | 198 | curvilinear_grid = mk.curvilineargrid_get() 199 | 200 | # Test the number of m and n 201 | assert curvilinear_grid.num_m == 4 202 | assert curvilinear_grid.num_n == 4 203 | 204 | 205 | def test_curvilinear_compute_rectangular_grid_defined_extension_spherical_coordinates(): 206 | r"""Tests `curvilinear_compute_rectangular_grid` makes a curvilinear grid within 207 | a defined extension in spherical coordinates.""" 208 | mk = MeshKernel(projection=ProjectionType.SPHERICAL) 209 | 210 | make_grid_parameters = MakeGridParameters() 211 | make_grid_parameters.origin_x = -1.0 212 | make_grid_parameters.origin_y = 49.1 213 | make_grid_parameters.upper_right_x = -0.2 214 | make_grid_parameters.upper_right_y = 49.6 215 | make_grid_parameters.block_size_x = 0.01 216 | make_grid_parameters.block_size_y = 0.01 217 | 218 | mk.curvilinear_compute_rectangular_grid_on_extension(make_grid_parameters) 219 | 220 | curvilinear_grid = mk.curvilineargrid_get() 221 | 222 | # Test the number of m and n 223 | assert curvilinear_grid.num_m == 81 224 | assert curvilinear_grid.num_n == 103 225 | 226 | 227 | def test_curvilinear_compute_rectangular_grid_with_polygon(): 228 | r"""Tests `curvilinear_compute_rectangular_grid` makes a curvilinear grid using a polygon.""" 229 | mk = MeshKernel() 230 | 231 | make_grid_parameters = MakeGridParameters() 232 | make_grid_parameters.num_columns = 3 233 | make_grid_parameters.num_rows = 3 234 | make_grid_parameters.angle = 0.0 235 | make_grid_parameters.origin_x = 0.0 236 | make_grid_parameters.origin_y = 0.0 237 | make_grid_parameters.block_size_x = 1.0 238 | make_grid_parameters.block_size_y = 1.0 239 | 240 | node_x = np.array([2.5, 5.5, 3.5, 0.5, 2.5], dtype=np.double) 241 | node_y = np.array([0.5, 3.0, 5.0, 2.5, 0.5], dtype=np.double) 242 | geometry_list = GeometryList(node_x, node_y) 243 | 244 | mk.curvilinear_compute_rectangular_grid_from_polygon( 245 | make_grid_parameters, geometry_list 246 | ) 247 | 248 | curvilinear_grid = mk.curvilineargrid_get() 249 | 250 | # Test the number of m and n 251 | assert curvilinear_grid.num_m == 11 252 | assert curvilinear_grid.num_n == 11 253 | 254 | 255 | def test_curvilinear_refine_derefine(): 256 | r"""Tests `curvilinear_refine` refines a curvilinear grid and 257 | `curvilinear_derefine` de-refines a curvilinear grid . 258 | """ 259 | mk = create_meshkernel_instance_with_curvilinear_grid() 260 | 261 | mk.curvilinear_refine(2.299, 4.612, 3.074, 3.684, 2) 262 | 263 | curvilinear_grid = mk.curvilineargrid_get() 264 | 265 | # After refinement 266 | assert curvilinear_grid.num_m == 11 267 | assert curvilinear_grid.num_n == 14 268 | 269 | mk.curvilinear_refine(2.299, 4.612, 3.074, 3.684, -6) 270 | 271 | curvilinear_grid = mk.curvilineargrid_get() 272 | 273 | # After de-refinement 274 | assert curvilinear_grid.num_m == 11 275 | assert curvilinear_grid.num_n == 9 276 | 277 | 278 | def test_curvilinear_compute_transfinite_from_polygon(): 279 | r"""Tests `curvilinear_compute_transfinite_from_polygon` generates curvilinear grid from a polygon. 280 | 281 | Input polygon: 282 | 6---5---4 283 | | | 284 | 7 3 285 | | | 286 | 0---1---2 287 | 288 | Generated curvilinear grid: 289 | 290 | 6---7---8 291 | | | | 292 | 3---4---5 293 | | | | 294 | 0---1---2 295 | """ 296 | 297 | # The input polygon 298 | node_x = np.array([0, 5, 10, 10, 10, 5, 0, 0, 0], dtype=np.double) 299 | node_y = np.array([0, 0, 0, 5, 10, 10, 10, 5, 0], dtype=np.double) 300 | geometry_list = GeometryList(node_x, node_y) 301 | 302 | mk = MeshKernel() 303 | 304 | mk.curvilinear_compute_transfinite_from_polygon(geometry_list, 0, 2, 4, False) 305 | 306 | curvilinear_grid = mk.curvilineargrid_get() 307 | 308 | assert curvilinear_grid.num_m == 3 309 | assert curvilinear_grid.num_n == 3 310 | 311 | 312 | def test_curvilinear_compute_transfinite_from_triangle(): 313 | r"""Tests `curvilinear_compute_transfinite_from_triangle` computes a curvilinear grid from a polygon 314 | having a triangular shape. 315 | """ 316 | 317 | node_x = np.array( 318 | [ 319 | 444.504791, 320 | 427.731781, 321 | 405.640503, 322 | 381.094666, 323 | 451.050354, 324 | 528.778931, 325 | 593.416260, 326 | 558.643005, 327 | 526.733398, 328 | 444.095703, 329 | 444.504791, 330 | ], 331 | dtype=np.double, 332 | ) 333 | 334 | node_y = np.array( 335 | [ 336 | 437.155945, 337 | 382.745758, 338 | 317.699005, 339 | 262.470612, 340 | 262.879700, 341 | 263.288788, 342 | 266.561584, 343 | 324.653687, 344 | 377.836578, 345 | 436.746857, 346 | 437.155945, 347 | ], 348 | dtype=np.double, 349 | ) 350 | 351 | geometry_list = GeometryList(node_x, node_y) 352 | 353 | mk = MeshKernel() 354 | 355 | mk.curvilinear_compute_transfinite_from_triangle(geometry_list, 0, 3, 6) 356 | 357 | curvilinear_grid = mk.curvilineargrid_get() 358 | 359 | # Test a curvilinear grid was generated 360 | assert curvilinear_grid.num_m == 4 361 | assert curvilinear_grid.num_n == 4 362 | 363 | 364 | def test_curvilinear_grid_orthogonalization(): 365 | r"""Tests `curvilinear_orthogonalize` orthogonalizes a curvilinear grid.""" 366 | 367 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid() 368 | 369 | orthogonalization_parameters = OrthogonalizationParameters() 370 | orthogonalization_parameters.outer_iterations = 1 371 | orthogonalization_parameters.boundary_iterations = 25 372 | orthogonalization_parameters.inner_iterations = 25 373 | orthogonalization_parameters.orthogonalization_to_smoothing_factor = 0.975 374 | 375 | # Orthogonalizes a curvilinear grid 376 | mk.curvilinear_orthogonalize(orthogonalization_parameters, 0.0, 0.0, 30.0, 30.0) 377 | 378 | # Get the new curvilinear grid 379 | curvilinear_grid = mk.curvilineargrid_get() 380 | 381 | # Assert nodal position after orthogonalization is closer to 10.0, 20.0 382 | assert curvilinear_grid.node_x[9] == approx(11.841396536135521, 0.0001) 383 | assert curvilinear_grid.node_y[9] == approx(18.158586078094562, 0.0001) 384 | 385 | 386 | def test_curvilinear_grid_orthogonalization_with_frozen_line(): 387 | r"""Tests `curvilinear_orthogonalize` with a frozen line orthogonalizes a curvilinear grid, 388 | except on the frozen line, whe nodal positions are fixed. 389 | """ 390 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid() 391 | 392 | orthogonalization_parameters = OrthogonalizationParameters() 393 | orthogonalization_parameters.outer_iterations = 1 394 | orthogonalization_parameters.boundary_iterations = 25 395 | orthogonalization_parameters.inner_iterations = 25 396 | orthogonalization_parameters.orthogonalization_to_smoothing_factor = 0.975 397 | 398 | # Add frozen lines 399 | mk.curvilinear_frozen_line_add(10.0, 0.0, 10.0, 30.0) 400 | 401 | # Performs orthogonalization 402 | mk.curvilinear_orthogonalize(orthogonalization_parameters, 10.0, 0.0, 10.0, 30.0) 403 | 404 | # Get the result 405 | curvilinear_grid = mk.curvilineargrid_get() 406 | 407 | # Assert the position of the skewed node after orthogonalization has not changed 408 | assert curvilinear_grid.node_x[9] == approx(18.0, 0.0001) 409 | assert curvilinear_grid.node_y[9] == approx(12.0, 0.0001) 410 | 411 | 412 | def test_curvilinear_smoothing(): 413 | r"""Tests `curvilinear_smoothing` smooths a curvilinear grid.""" 414 | 415 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid() 416 | 417 | # Perform smoothing 418 | mk.curvilinear_smoothing(10, 0.0, 0.0, 30.0, 30.0) 419 | 420 | # Get the result 421 | curvilinear_grid = mk.curvilineargrid_get() 422 | 423 | # Assert the nodal position after smoothing 424 | assert curvilinear_grid.node_x[9] == approx(10.315557749152806, 0.0001) 425 | assert curvilinear_grid.node_y[9] == approx(19.68444225084719, 0.0001) 426 | 427 | 428 | def test_curvilinear_smoothing_directional(): 429 | r"""Tests `curvilinear_smoothing_directional` smooths a curvilinear grid along one direction.""" 430 | 431 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid() 432 | 433 | # Perform directional smoothing 434 | mk.curvilinear_smoothing_directional( 435 | 10, 10.0, 0.0, 10.0, 30.0, 0.0, 0.0, 30.0, 30.0 436 | ) 437 | 438 | # Get the result 439 | curvilinear_grid = mk.curvilineargrid_get() 440 | 441 | # Assert the nodal position after directional smoothing 442 | assert curvilinear_grid.node_x[9] == approx(14.278566438378412, 0.0001) 443 | assert curvilinear_grid.node_y[9] == approx(20.37322551364857, 0.0001) 444 | 445 | 446 | def test_curvilinear_line_shift(): 447 | r"""Tests curvilinear line shift shift workflow, where some nodes on a grid line are shifted, 448 | and the shifting is distributed on a curvilinear grid block. 449 | """ 450 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 451 | 452 | # Initialize line shift algorithm 453 | mk.curvilinear_initialize_line_shift() 454 | 455 | # Sets the line to shift 456 | mk.curvilinear_set_line_line_shift(0.0, 0.0, 0.0, 50.0) 457 | 458 | # Sets the block where the shift will be distributed 459 | mk.curvilinear_set_block_line_shift(0.0, 0.0, 20.0, 50.0) 460 | 461 | # Move left side nodes on a new position 462 | mk.curvilinear_move_node_line_shift(0.0, 0.0, -10.0, 0.0) 463 | mk.curvilinear_move_node_line_shift(0.0, 10.0, -10.0, 10.0) 464 | mk.curvilinear_move_node_line_shift(0.0, 20.0, -10.0, 20.0) 465 | mk.curvilinear_move_node_line_shift(0.0, 30.0, -10.0, 30.0) 466 | mk.curvilinear_move_node_line_shift(0.0, 40.0, -10.0, 40.0) 467 | mk.curvilinear_move_node_line_shift(0.0, 50.0, -10.0, 50.0) 468 | 469 | # Performs curvilinear grid line shift 470 | mk.curvilinear_line_shift() 471 | 472 | # Get the result 473 | curvilinear_grid = mk.curvilineargrid_get() 474 | 475 | # Assert the nodal position after line shift 476 | assert curvilinear_grid.node_x[0] == -10.0 477 | assert curvilinear_grid.node_x[6] == -10.0 478 | assert curvilinear_grid.node_x[12] == -10.0 479 | assert curvilinear_grid.node_x[18] == -10.0 480 | assert curvilinear_grid.node_x[24] == -10.0 481 | assert curvilinear_grid.node_x[30] == -10.0 482 | 483 | assert curvilinear_grid.node_y[0] == 0 484 | assert curvilinear_grid.node_y[6] == 10.0 485 | assert curvilinear_grid.node_y[12] == 20.0 486 | assert curvilinear_grid.node_y[18] == 30.0 487 | assert curvilinear_grid.node_y[24] == 40.0 488 | assert curvilinear_grid.node_y[30] == 50.0 489 | 490 | 491 | def test_curvilinear_insert_face(): 492 | r"""Tests 'curvilinear_insert_face' inserts two new faces.""" 493 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 494 | 495 | # Inserts two faces 496 | mk.curvilinear_insert_face(-10.0, 5.0) 497 | mk.curvilinear_insert_face(-5.0, 10.0) 498 | 499 | # Get the result 500 | curvilinear_grid = mk.curvilineargrid_get() 501 | 502 | # Assert two new faces have been inserted 503 | assert curvilinear_grid.node_x[0] == -10.0 504 | assert curvilinear_grid.node_x[1] == 0.0 505 | assert curvilinear_grid.node_x[8] == 0.0 506 | assert curvilinear_grid.node_x[9] == 10.0 507 | 508 | assert curvilinear_grid.node_y[0] == 0.0 509 | assert curvilinear_grid.node_y[1] == 0.0 510 | assert curvilinear_grid.node_y[8] == 10.0 511 | assert curvilinear_grid.node_y[9] == 10.0 512 | 513 | 514 | def test_curvilinear_delete_node(): 515 | r"""Tests 'curvilinear_delete_node' deletes a node.""" 516 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 517 | 518 | # Inserts two faces 519 | mk.curvilinear_delete_node(0.0, 0.0) 520 | 521 | # Get the result 522 | curvilinear_grid = mk.curvilineargrid_get() 523 | 524 | # Test the bottom right corner is marked as deleted (invalid coordinates) 525 | assert curvilinear_grid.node_x[0] == -999.0 526 | assert curvilinear_grid.node_y[0] == -999.0 527 | 528 | 529 | def test_curvilinear_line_attraction_repulsion(): 530 | r"""Tests 'curvilinear_line_attraction_repulsion' repels lines.""" 531 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 532 | 533 | # Repels lines 534 | mk.curvilinear_line_attraction_repulsion( 535 | 1.0, 30.0, 0.0, 30.0, 50.0, 10.0, 0.0, 50.0, 50.0 536 | ) 537 | 538 | # Get the result 539 | curvilinear_grid = mk.curvilineargrid_get() 540 | 541 | # Test a node on the third grid line was shifted from 20 to 25 542 | assert curvilinear_grid.node_x[2] == 25.0 543 | 544 | 545 | def test_curvilinear_line_mirror(): 546 | r"""Tests 'curvilinear_line_mirror' replicates (mirrors) a boundary grid line.""" 547 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 548 | 549 | # Mirrors the left grid line to left 550 | mk.curvilinear_line_mirror(2.0, 1, 0.0, 0.0, 0.0, 50.0) 551 | 552 | # Get the result 553 | curvilinear_grid = mk.curvilineargrid_get() 554 | 555 | # Test some few nodes have been generated on the left side 556 | assert curvilinear_grid.node_x[0] == -20.0 557 | assert curvilinear_grid.node_x[7] == -20.0 558 | assert curvilinear_grid.node_y[0] == 0.0 559 | assert curvilinear_grid.node_y[7] == 10.0 560 | 561 | 562 | def test_curvilinear_compute_curvature(): 563 | r"""Tests 'test_curvilinear_compute_curvature' gets curvature data.""" 564 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 565 | 566 | # Execute 567 | curvature = mk.curvilinear_compute_curvature(CurvilinearDirection.N) 568 | 569 | # Assert 570 | # Test the curvature values 571 | assert curvature[0] == -999.0 572 | assert curvature[1] == -999.0 573 | assert curvature[2] == -999.0 574 | assert curvature[3] == -999.0 575 | assert curvature[4] == -999.0 576 | assert curvature[5] == -999.0 577 | assert curvature[6] == 0.001000001000001 578 | assert curvature[7] == 90.9090909090909 579 | assert curvature[8] == 0.001000001000001 580 | assert curvature[9] == 0.001000001000001 581 | assert curvature[10] == 0.001000001000001 582 | 583 | 584 | def test_curvilinear_compute_smoothness(): 585 | r"""Tests 'curvilinear_compute_smoothness' gets smoothness data.""" 586 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 587 | 588 | # Execute 589 | smoothness = mk.curvilinear_compute_smoothness(CurvilinearDirection.N) 590 | 591 | # Assert 592 | # Test the smoothness values 593 | assert smoothness[0] == -999.0 594 | assert smoothness[1] == -999.0 595 | assert smoothness[5] == -999.0 596 | assert smoothness[6] == 1.0 597 | assert smoothness[7] == approx(1.2126781251816647, 0.0001) 598 | 599 | 600 | def test_add_frozen_line(): 601 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 602 | frozen_line_id = mk.curvilinear_frozen_line_add(10.0, 20.0, 10.0, 40.0) 603 | 604 | assert frozen_line_id == 0 605 | 606 | 607 | def test_get_frozen_line_ids(): 608 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 609 | frozen_line_id = mk.curvilinear_frozen_line_add(10.0, 20.0, 0.0, 40.0) 610 | frozen_line_ids = mk.curvilinear_frozen_lines_get_ids() 611 | assert len(frozen_line_ids) == 1 612 | assert frozen_line_id == frozen_line_ids[0] 613 | 614 | 615 | def test_delete_frozen_line(): 616 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 617 | frozen_line_id = mk.curvilinear_frozen_line_add(10.0, 20.0, 10.0, 40.0) 618 | mk.curvilinear_frozen_line_delete(frozen_line_id) 619 | 620 | frozen_line_ids = mk.curvilinear_frozen_lines_get_ids() 621 | assert len(frozen_line_ids) == 0 622 | 623 | 624 | def test_delete_frozen_line_twice_does_not_throw_an_exception(): 625 | mk = create_meshkernel_instance_with_skewed_curvilinear_grid(5, 5) 626 | frozen_line_id = mk.curvilinear_frozen_line_add(10.0, 20.0, 10.0, 40.0) 627 | 628 | mk.curvilinear_frozen_line_delete(frozen_line_id) 629 | frozen_line_ids = mk.curvilinear_frozen_lines_get_ids() 630 | assert len(frozen_line_ids) == 0 631 | 632 | mk.curvilinear_frozen_line_delete(frozen_line_id) 633 | frozen_line_ids = mk.curvilinear_frozen_lines_get_ids() 634 | assert len(frozen_line_ids) == 0 635 | -------------------------------------------------------------------------------- /tests/test_interpolation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from numpy.testing import assert_array_equal 4 | from pytest import approx 5 | 6 | from meshkernel import AveragingMethod, GeometryList, Mesh2dLocation, MeshKernel 7 | 8 | 9 | def test_mesh2d_triangulation_interpolation_on_faces( 10 | meshkernel_with_mesh2d: MeshKernel, 11 | ): 12 | """Tests `mesh2d_triangulation_interpolation` on the faces of a 3x3 Mesh2d.""" 13 | 14 | mk = meshkernel_with_mesh2d(3, 3) 15 | 16 | samples_x = np.array([0.4, 1.3, 2.6, 0.6, 1.6, 2.4, 0.4, 1.6, 2.5], dtype=np.double) 17 | samples_y = np.array([0.5, 0.5, 0.5, 1.5, 1.5, 1.5, 2.5, 2.5, 2.5], dtype=np.double) 18 | samples_values = np.array( 19 | [0.9, 1.8, 3.1, 4.1, 5.1, 5.9, 6.9, 8.1, 9], dtype=np.double 20 | ) 21 | samples = GeometryList(samples_x, samples_y, samples_values) 22 | 23 | interpolation = mk.mesh2d_triangulation_interpolation(samples, Mesh2dLocation.FACES) 24 | 25 | assert interpolation.x_coordinates[0] == 0.5 26 | assert interpolation.x_coordinates[1] == 1.5 27 | assert interpolation.x_coordinates[2] == 2.5 28 | assert interpolation.x_coordinates[3] == 0.5 29 | assert interpolation.x_coordinates[4] == 1.5 30 | assert interpolation.x_coordinates[5] == 2.5 31 | assert interpolation.x_coordinates[6] == 0.5 32 | assert interpolation.x_coordinates[7] == 1.5 33 | assert interpolation.x_coordinates[8] == 2.5 34 | 35 | assert interpolation.y_coordinates[0] == 0.5 36 | assert interpolation.y_coordinates[1] == 0.5 37 | assert interpolation.y_coordinates[2] == 0.5 38 | assert interpolation.y_coordinates[3] == 1.5 39 | assert interpolation.y_coordinates[4] == 1.5 40 | assert interpolation.y_coordinates[5] == 1.5 41 | assert interpolation.y_coordinates[6] == 2.5 42 | assert interpolation.y_coordinates[7] == 2.5 43 | assert interpolation.y_coordinates[8] == 2.5 44 | 45 | assert interpolation.values[0] == approx(1, abs=0.00000001) 46 | assert interpolation.values[1] == approx(2, abs=0.00000001) 47 | assert interpolation.values[2] == approx(3, abs=0.00000001) 48 | assert interpolation.values[3] == approx(4, abs=0.00000001) 49 | assert interpolation.values[4] == approx(5, abs=0.00000001) 50 | assert interpolation.values[5] == approx(6, abs=0.00000001) 51 | assert interpolation.values[6] == approx(7, abs=0.00000001) 52 | assert interpolation.values[7] == approx(8, abs=0.00000001) 53 | assert interpolation.values[8] == approx(9, abs=0.00000001) 54 | 55 | 56 | def test_mesh2d_triangulation_interpolation_on_nodes( 57 | meshkernel_with_mesh2d: MeshKernel, 58 | ): 59 | """Tests `mesh2d_triangulation_interpolation` on the nodes of a 2x2 Mesh2d.""" 60 | 61 | mk = meshkernel_with_mesh2d(2, 2) 62 | 63 | samples_x = np.array([0.0, 0.9, 2.1, 0.1, 1.1, 2.2, 0.0, 1.2, 2.1], dtype=np.double) 64 | samples_y = np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0], dtype=np.double) 65 | samples_values = np.array( 66 | [1, 1.9, 3.1, 4.1, 5.1, 6.2, 7.0, 8.2, 9.1], dtype=np.double 67 | ) 68 | samples = GeometryList(samples_x, samples_y, samples_values) 69 | 70 | interpolation = mk.mesh2d_triangulation_interpolation(samples, Mesh2dLocation.NODES) 71 | 72 | assert interpolation.x_coordinates[0] == 0.0 73 | assert interpolation.x_coordinates[1] == 1.0 74 | assert interpolation.x_coordinates[2] == 2.0 75 | assert interpolation.x_coordinates[3] == 0.0 76 | assert interpolation.x_coordinates[4] == 1.0 77 | assert interpolation.x_coordinates[5] == 2.0 78 | assert interpolation.x_coordinates[6] == 0.0 79 | assert interpolation.x_coordinates[7] == 1.0 80 | assert interpolation.x_coordinates[8] == 2.0 81 | 82 | assert interpolation.y_coordinates[0] == 0.0 83 | assert interpolation.y_coordinates[1] == 0.0 84 | assert interpolation.y_coordinates[2] == 0.0 85 | assert interpolation.y_coordinates[3] == 1.0 86 | assert interpolation.y_coordinates[4] == 1.0 87 | assert interpolation.y_coordinates[5] == 1.0 88 | assert interpolation.y_coordinates[6] == 2.0 89 | assert interpolation.y_coordinates[7] == 2.0 90 | assert interpolation.y_coordinates[8] == 2.0 91 | 92 | assert interpolation.values[0] == approx(1, abs=0.00000001) 93 | assert interpolation.values[1] == approx(2, abs=0.00000001) 94 | assert interpolation.values[2] == approx(3, abs=0.00000001) 95 | assert interpolation.values[3] == approx(4, abs=0.00000001) 96 | assert interpolation.values[4] == approx(5, abs=0.00000001) 97 | assert interpolation.values[5] == approx(6, abs=0.00000001) 98 | assert interpolation.values[6] == approx(7, abs=0.00000001) 99 | assert interpolation.values[7] == approx(8, abs=0.00000001) 100 | assert interpolation.values[8] == approx(9, abs=0.00000001) 101 | 102 | 103 | def test_mesh2d_triangulation_interpolation_on_edges( 104 | meshkernel_with_mesh2d: MeshKernel, 105 | ): 106 | """Tests `mesh2d_triangulation_interpolation` on the edges of a 2x2 Mesh2d.""" 107 | 108 | mk = meshkernel_with_mesh2d(2, 2) 109 | 110 | samples_x = np.array( 111 | [0.0, 1.1, 2.2, 0.0, 0.9, 2.0, 0.4, 1.6, 0.6, 1.3, 0.2, 1.5], dtype=np.double 112 | ) 113 | samples_y = np.array( 114 | [0.5, 0.5, 0.5, 1.5, 1.5, 1.5, 0.0, 0.0, 1.0, 1.0, 2.0, 2.0], dtype=np.double 115 | ) 116 | samples_values = np.array( 117 | [1, 2.1, 3.2, 1.0, 1.9, 3.0, 1.4, 2.6, 1.6, 2.3, 1.2, 2.5], dtype=np.double 118 | ) 119 | samples = GeometryList(samples_x, samples_y, samples_values) 120 | 121 | interpolation = mk.mesh2d_triangulation_interpolation(samples, Mesh2dLocation.EDGES) 122 | 123 | assert interpolation.x_coordinates[0] == 0.0 124 | assert interpolation.x_coordinates[1] == 1.0 125 | assert interpolation.x_coordinates[2] == 2.0 126 | assert interpolation.x_coordinates[3] == 0.0 127 | assert interpolation.x_coordinates[4] == 1.0 128 | assert interpolation.x_coordinates[5] == 2.0 129 | assert interpolation.x_coordinates[6] == 0.5 130 | assert interpolation.x_coordinates[7] == 1.5 131 | assert interpolation.x_coordinates[8] == 0.5 132 | assert interpolation.x_coordinates[9] == 1.5 133 | assert interpolation.x_coordinates[10] == 0.5 134 | assert interpolation.x_coordinates[11] == 1.5 135 | 136 | assert interpolation.y_coordinates[0] == 0.5 137 | assert interpolation.y_coordinates[1] == 0.5 138 | assert interpolation.y_coordinates[2] == 0.5 139 | assert interpolation.y_coordinates[3] == 1.5 140 | assert interpolation.y_coordinates[4] == 1.5 141 | assert interpolation.y_coordinates[5] == 1.5 142 | assert interpolation.y_coordinates[6] == 0.0 143 | assert interpolation.y_coordinates[7] == 0.0 144 | assert interpolation.y_coordinates[8] == 1.0 145 | assert interpolation.y_coordinates[9] == 1.0 146 | assert interpolation.y_coordinates[10] == 2.0 147 | assert interpolation.y_coordinates[11] == 2.0 148 | 149 | assert interpolation.values[0] == approx(1, abs=0.00000001) 150 | assert interpolation.values[1] == approx(2, abs=0.00000001) 151 | assert interpolation.values[2] == approx(3, abs=0.00000001) 152 | assert interpolation.values[3] == approx(1, abs=0.00000001) 153 | assert interpolation.values[4] == approx(2, abs=0.00000001) 154 | assert interpolation.values[5] == approx(3, abs=0.00000001) 155 | assert interpolation.values[6] == approx(1.5, abs=0.00000001) 156 | assert interpolation.values[7] == approx(2.5, abs=0.00000001) 157 | assert interpolation.values[8] == approx(1.5, abs=0.00000001) 158 | assert interpolation.values[9] == approx(2.5, abs=0.00000001) 159 | assert interpolation.values[10] == approx(1.5, abs=0.00000001) 160 | assert interpolation.values[11] == approx(2.5, abs=0.00000001) 161 | 162 | 163 | cases_mesh2d_averaging_interpolation = [ 164 | ( 165 | AveragingMethod.SIMPLE_AVERAGING, 166 | np.array([3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0]), 167 | ), 168 | ( 169 | AveragingMethod.CLOSEST_POINT, 170 | np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]), 171 | ), 172 | ( 173 | AveragingMethod.MAX, 174 | np.array([5.0, 6.0, 6.0, 8.0, 9.0, 9.0, 8.0, 9.0, 9.0]), 175 | ), 176 | ( 177 | AveragingMethod.MIN, 178 | np.array([1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 4.0, 4.0, 5.0]), 179 | ), 180 | ( 181 | AveragingMethod.MIN_ABS, 182 | np.array([1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 4.0, 4.0, 5.0]), 183 | ), 184 | ] 185 | 186 | 187 | @pytest.mark.parametrize( 188 | "averaging_method, exp_values", 189 | cases_mesh2d_averaging_interpolation, 190 | ) 191 | def test_mesh2d_averaging_interpolation( 192 | meshkernel_with_mesh2d: MeshKernel, 193 | averaging_method: AveragingMethod, 194 | exp_values: np.ndarray, 195 | ): 196 | """Tests `mesh2d_averaging_interpolation` on the faces of a 3x3 Mesh2d.""" 197 | 198 | mk = meshkernel_with_mesh2d(3, 3) 199 | 200 | samples_x = np.array([0.5, 1.5, 2.5, 0.5, 1.5, 2.5, 0.5, 1.5, 2.5], dtype=np.double) 201 | samples_y = np.array([0.5, 0.5, 0.5, 1.5, 1.5, 1.5, 2.5, 2.5, 2.5], dtype=np.double) 202 | samples_values = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.double) 203 | samples = GeometryList(samples_x, samples_y, samples_values) 204 | 205 | interpolation = mk.mesh2d_averaging_interpolation( 206 | samples, Mesh2dLocation.FACES, averaging_method, 1.5, 1 207 | ) 208 | 209 | assert interpolation.x_coordinates[0] == 0.5 210 | assert interpolation.x_coordinates[1] == 1.5 211 | assert interpolation.x_coordinates[2] == 2.5 212 | assert interpolation.x_coordinates[3] == 0.5 213 | assert interpolation.x_coordinates[4] == 1.5 214 | assert interpolation.x_coordinates[5] == 2.5 215 | assert interpolation.x_coordinates[6] == 0.5 216 | assert interpolation.x_coordinates[7] == 1.5 217 | assert interpolation.x_coordinates[8] == 2.5 218 | 219 | assert interpolation.y_coordinates[0] == 0.5 220 | assert interpolation.y_coordinates[1] == 0.5 221 | assert interpolation.y_coordinates[2] == 0.5 222 | assert interpolation.y_coordinates[3] == 1.5 223 | assert interpolation.y_coordinates[4] == 1.5 224 | assert interpolation.y_coordinates[5] == 1.5 225 | assert interpolation.y_coordinates[6] == 2.5 226 | assert interpolation.y_coordinates[7] == 2.5 227 | assert interpolation.y_coordinates[8] == 2.5 228 | 229 | assert_array_equal(interpolation.values, exp_values) 230 | -------------------------------------------------------------------------------- /tests/test_mesh1d_basics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from mesh2d_factory import Mesh2dFactory 4 | from numpy import ndarray 5 | from numpy.testing import assert_array_equal 6 | 7 | from meshkernel import Contacts, DeleteMeshOption, GeometryList, Mesh1d, MeshKernel 8 | 9 | 10 | def sort_contacts_by_mesh2d_indices(contacts): 11 | """Sort the contacts by the mesh2d indices to get consistent results. 12 | The contacts computed by meshkernel can be in any order 13 | """ 14 | if len(contacts.mesh1d_indices) == 0 or len(contacts.mesh2d_indices) == 0: 15 | return contacts 16 | 17 | indices = np.argsort(contacts.mesh2d_indices) 18 | contacts.mesh1d_indices = contacts.mesh1d_indices[indices] 19 | contacts.mesh2d_indices = contacts.mesh2d_indices[indices] 20 | 21 | 22 | def test_mesh1d_set_and_mesh1d_get(): 23 | r"""Tests `mesh1d_set` and `mesh1d_get` to set and get a simple mesh. 24 | 25 | 1 3 26 | / \ / 27 | 0 2 28 | """ 29 | mk = MeshKernel() 30 | 31 | node_x = np.array([0.0, 1.0, 2.0, 3.0], dtype=np.double) 32 | node_y = np.array([0.0, 1.0, 0.0, 1.0], dtype=np.double) 33 | edge_nodes = np.array([0, 1, 1, 2, 2, 3], dtype=np.int32) 34 | input_mesh1d = Mesh1d(node_x, node_y, edge_nodes) 35 | 36 | mk.mesh1d_set(input_mesh1d) 37 | 38 | output_mesh1d = mk.mesh1d_get() 39 | 40 | # Test if the input and output differs 41 | assert_array_equal(output_mesh1d.edge_nodes, input_mesh1d.edge_nodes) 42 | assert_array_equal(output_mesh1d.node_x, input_mesh1d.node_x) 43 | assert_array_equal(output_mesh1d.node_y, input_mesh1d.node_y) 44 | 45 | 46 | def test_mesh1d_add(): 47 | r"""Tests `mesh1d_add`.""" 48 | mk = MeshKernel() 49 | 50 | node_x = np.array([0.0, 1.0, 2.0, 3.0], dtype=np.double) 51 | node_y = np.array([0.0, 1.0, 0.0, 1.0], dtype=np.double) 52 | edge_nodes = np.array([0, 1, 1, 2, 2, 3], dtype=np.int32) 53 | 54 | input_mesh1d_1 = Mesh1d(node_x, node_y, edge_nodes) 55 | mk.mesh1d_set(input_mesh1d_1) 56 | 57 | input_mesh1d_2 = Mesh1d(node_x + 4, node_y, edge_nodes) 58 | mk.mesh1d_add(input_mesh1d_2) 59 | 60 | output_mesh1d = mk.mesh1d_get() 61 | 62 | assert_array_equal( 63 | output_mesh1d.node_x, 64 | np.concatenate( 65 | (input_mesh1d_1.node_x, input_mesh1d_2.node_x), 66 | axis=None, 67 | ), 68 | ) 69 | 70 | assert_array_equal( 71 | output_mesh1d.node_y, 72 | np.concatenate( 73 | (input_mesh1d_1.node_y, input_mesh1d_2.node_y), 74 | axis=None, 75 | ), 76 | ) 77 | 78 | 79 | def test_contacts_set_and_get(): 80 | """Tests `contacts_set` and `contacts_get`.""" 81 | 82 | mk = MeshKernel() 83 | 84 | mesh1d_indices = np.array([1, 2, 3, 4], dtype=np.int32) 85 | mesh2d_indices = np.array([5, 6, 7, 8], dtype=np.int32) 86 | mk.contacts_set(Contacts(mesh1d_indices, mesh2d_indices)) 87 | contacts = mk.contacts_get() 88 | assert_array_equal(mesh1d_indices, contacts.mesh1d_indices) 89 | assert_array_equal(mesh2d_indices, contacts.mesh2d_indices) 90 | 91 | 92 | def test_contacts_compute_single(): 93 | """Tests `contacts_compute_single` with a 5x5 Mesh2d and a Mesh1d with 5 nodes. 94 | 95 | 30--31--32--33--34--35 96 | | | | | | /| 97 | 24--25--26--27--28--29 98 | | | | | /| | 99 | 18--19--20--21--22--23 100 | | | | /| | | 101 | 12--13--14--15--16--17 102 | | | /| | | | 103 | 6---7---8---9---10--11 104 | | /| | | | | 105 | 0---1---2---3---4---5 106 | """ 107 | 108 | mk = MeshKernel() 109 | 110 | mesh2d = Mesh2dFactory.create(5, 5) 111 | 112 | node_x = np.array([0.75, 1.75, 2.75, 3.75, 4.75], dtype=np.double) 113 | node_y = np.array([0.25, 1.25, 2.25, 3.25, 4.25], dtype=np.double) 114 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 4], dtype=np.int32) 115 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 116 | 117 | mk.mesh2d_set(mesh2d) 118 | mk.mesh1d_set(mesh1d) 119 | 120 | node_mask = np.full(node_x.size, True) 121 | 122 | polygon_x = np.array([-1.0, 6.0, 6.0, -1.0, -1.0], dtype=np.double) 123 | polygon_y = np.array([-1.0, -1.0, 6.0, 6.0, -1.0], dtype=np.double) 124 | polygon = GeometryList(polygon_x, polygon_y) 125 | projection_factor = 0.0 126 | 127 | mk.contacts_compute_single(node_mask, polygon, projection_factor) 128 | 129 | contacts = mk.contacts_get() 130 | sort_contacts_by_mesh2d_indices(contacts) 131 | 132 | assert contacts.mesh1d_indices.size == 5 133 | assert contacts.mesh2d_indices.size == 5 134 | 135 | assert contacts.mesh1d_indices[0] == 0 136 | assert contacts.mesh1d_indices[1] == 1 137 | assert contacts.mesh1d_indices[2] == 2 138 | assert contacts.mesh1d_indices[3] == 3 139 | assert contacts.mesh1d_indices[4] == 4 140 | 141 | assert contacts.mesh2d_indices[0] == 0 142 | assert contacts.mesh2d_indices[1] == 6 143 | assert contacts.mesh2d_indices[2] == 12 144 | assert contacts.mesh2d_indices[3] == 18 145 | assert contacts.mesh2d_indices[4] == 24 146 | 147 | 148 | def test_contacts_compute_multiple(): 149 | """Tests `contacts_compute_multiple` with a 5x5 Mesh2d and a Mesh1d with 5 nodes. 150 | 151 | 30--31--32--33--34--35 152 | | | | | | /| 153 | 24--25--26--27--28--29 154 | | | | | /| | 155 | 18--19--20--21--22--23 156 | | | | /| | | 157 | 12--13--14--15--16--17 158 | | | /| | | | 159 | 6---7---8---9---10--11 160 | | /| | | | | 161 | 0---1---2---3---4---5 162 | """ 163 | 164 | mk = MeshKernel() 165 | 166 | mesh2d = Mesh2dFactory.create(5, 5) 167 | 168 | node_x = np.array([0.7, 1.5, 2.6, 3.9, 4.8], dtype=np.double) 169 | node_y = np.array([0.3, 1.4, 2.6, 3.2, 4.2], dtype=np.double) 170 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 4], dtype=np.int32) 171 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 172 | 173 | mk.mesh2d_set(mesh2d) 174 | mk.mesh1d_set(mesh1d) 175 | 176 | node_mask = np.full(node_x.size, True) 177 | 178 | mk.contacts_compute_multiple(node_mask) 179 | 180 | contacts = mk.contacts_get() 181 | sort_contacts_by_mesh2d_indices(contacts) 182 | 183 | assert contacts.mesh1d_indices.size == 9 184 | assert contacts.mesh2d_indices.size == 9 185 | 186 | assert_array_equal(contacts.mesh1d_indices, [0, 0, 1, 1, 2, 3, 3, 3, 4]) 187 | assert_array_equal(contacts.mesh2d_indices, [0, 1, 6, 7, 12, 13, 18, 19, 24]) 188 | 189 | 190 | def test_contacts_compute_with_polygons(): 191 | """Tests `contacts_compute_with_polygons` with a 5x5 Mesh2d and a Mesh1d with 5 nodes. 192 | 193 | 30--31--32--33--34--35 194 | | | | | | / | 195 | 24--25--26--27--28--29 196 | | | | | / | | 197 | 18--19--20--21--22--23 198 | | | | / | | | 199 | 12--13--14--15--16--17 200 | | | / | | | | 201 | 6---7---8---9---10--11 202 | | / | | | | | 203 | 0---1---2---3---4---5 204 | """ 205 | 206 | mk = MeshKernel() 207 | 208 | mesh2d = Mesh2dFactory.create(5, 5) 209 | 210 | node_x = np.array([0.5, 1.5, 2.5, 3.5, 4.5], dtype=np.double) 211 | node_y = np.array([0.5, 1.5, 2.5, 3.5, 4.5], dtype=np.double) 212 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 4], dtype=np.int32) 213 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 214 | 215 | mk.mesh2d_set(mesh2d) 216 | mk.mesh1d_set(mesh1d) 217 | 218 | node_mask = np.full(node_x.size, True) 219 | 220 | # Two polygons around Mesh2d nodes 4, 5, 23, 22 and 12, 13, 31, 30 221 | separator = -999.0 222 | polygon_x = np.array( 223 | [-0.1, 1.1, 1.1, -0.1, -0.1, separator, 3.9, 5.1, 5.1, 3.9, 3.9], 224 | dtype=np.double, 225 | ) 226 | polygon_y = np.array( 227 | [1.9, 1.9, 5.1, 5.1, 1.9, separator, -0.1, -0.1, 3.1, 3.1, -0.1], 228 | dtype=np.double, 229 | ) 230 | polygon = GeometryList(polygon_x, polygon_y) 231 | 232 | mk.contacts_compute_with_polygons(node_mask, polygon) 233 | 234 | contacts = mk.contacts_get() 235 | sort_contacts_by_mesh2d_indices(contacts) 236 | 237 | assert contacts.mesh1d_indices.size == 2 238 | assert contacts.mesh2d_indices.size == 2 239 | 240 | assert contacts.mesh1d_indices[0] == 1 241 | assert contacts.mesh1d_indices[1] == 3 242 | 243 | assert contacts.mesh2d_indices[0] == 10 244 | assert contacts.mesh2d_indices[1] == 14 245 | 246 | 247 | def test_contacts_compute_with_points(): 248 | """Tests `contacts_compute_with_points` with a 5x5 Mesh2d and a Mesh1d with 5 nodes. 249 | 250 | 30--31--32--33--34--35 251 | | | | | | / | 252 | 24--25--26--27--28--29 253 | | | | | / | | 254 | 18--19--20--21--22--23 255 | | | | / | | | 256 | 12--13--14--15--16--17 257 | | | / | | | | 258 | 6---7---8---9---10--11 259 | | / | | | | | 260 | 0---1---2---3---4---5 261 | """ 262 | 263 | mk = MeshKernel() 264 | 265 | mesh2d = Mesh2dFactory.create(5, 5) 266 | 267 | node_x = np.array([0.5, 1.5, 2.5, 3.5, 4.5], dtype=np.double) 268 | node_y = np.array([0.5, 1.5, 2.5, 3.5, 4.5], dtype=np.double) 269 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 4], dtype=np.int32) 270 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 271 | 272 | mk.mesh2d_set(mesh2d) 273 | mk.mesh1d_set(mesh1d) 274 | 275 | node_mask = np.full(node_x.size, True) 276 | 277 | # Three points in Mesh2d faces 10, 8, 14 278 | points_x = np.array([0.5, 3.5, 4.5], dtype=np.double) 279 | points_y = np.array([2.5, 1.5, 2.5], dtype=np.double) 280 | points = GeometryList(points_x, points_y) 281 | 282 | mk.contacts_compute_with_points(node_mask, points) 283 | 284 | contacts = mk.contacts_get() 285 | sort_contacts_by_mesh2d_indices(contacts) 286 | 287 | assert contacts.mesh1d_indices.size == 3 288 | assert contacts.mesh2d_indices.size == 3 289 | 290 | assert contacts.mesh1d_indices[0] == 2 291 | assert contacts.mesh1d_indices[1] == 1 292 | assert contacts.mesh1d_indices[2] == 3 293 | 294 | assert contacts.mesh2d_indices[0] == 8 295 | assert contacts.mesh2d_indices[1] == 10 296 | assert contacts.mesh2d_indices[2] == 14 297 | 298 | 299 | cases_contacts_compute_boundary = [ 300 | ( 301 | np.array([True, True, True, True, True]), # node_mask 302 | np.array([1, 2, 4], dtype=np.int32), # exp_mesh1d_indices 303 | np.array([0, 2, 3], dtype=np.int32), # exp_mesh2d_indices 304 | ), 305 | ( 306 | np.array([True, False, False, False, True], dtype=np.int32), # node_mask 307 | np.array([4, 4], dtype=np.int32), # exp_mesh1d_indices 308 | np.array([2, 3], dtype=np.int32), # exp_mesh2d_indices 309 | ), 310 | ( 311 | np.array([False, False, True, True, True], dtype=np.int32), # node_mask 312 | np.array([2, 4], dtype=np.int32), # exp_mesh1d_indices 313 | np.array([2, 3], dtype=np.int32), # exp_mesh2d_indices 314 | ), 315 | ( 316 | np.array([True, False, False, True, True], dtype=np.int32), # node_mask 317 | np.array([3, 4], dtype=np.int32), # exp_mesh1d_indices 318 | np.array([2, 3], dtype=np.int32), # exp_mesh2d_indices 319 | ), 320 | ( 321 | np.array([False, False, True, False, False], dtype=np.int32), # node_mask 322 | np.array([2], dtype=np.int32), # exp_mesh1d_indices 323 | np.array([2], dtype=np.int32), # exp_mesh2d_indices 324 | ), 325 | ( 326 | np.array([False, False, False, False, False], dtype=np.int32), # node_mask 327 | np.array([], dtype=np.int32), # exp_mesh1d_indices 328 | np.array([], dtype=np.int32), # exp_mesh2d_indices 329 | ), 330 | ] 331 | 332 | 333 | @pytest.mark.parametrize( 334 | "node_mask, exp_mesh1d_indices, exp_mesh2d_indices", 335 | cases_contacts_compute_boundary, 336 | ) 337 | def test_contacts_compute_boundary( 338 | node_mask: ndarray, exp_mesh1d_indices: ndarray, exp_mesh2d_indices: ndarray 339 | ): 340 | """Tests `contacts_compute_boundary` with a 2x2 Mesh2d and a Mesh1d with 5 nodes. 341 | 342 | 343 | ---3---4 344 | 2 345 | | 6---7---8 346 | 1 | | | 347 | | 3---4---5 348 | 0 | | | 349 | 0---1---2 350 | """ 351 | 352 | mk = MeshKernel() 353 | 354 | mesh2d = Mesh2dFactory.create(2, 2) 355 | 356 | node_x = np.array([-1.0, -1.0, -0.5, 0.5, 1.5], dtype=np.double) 357 | node_y = np.array([-1.0, 1.5, 2.5, 3.0, 3.0], dtype=np.double) 358 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 4], dtype=np.int32) 359 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 360 | 361 | mk.mesh2d_set(mesh2d) 362 | mk.mesh1d_set(mesh1d) 363 | 364 | polygon_x = np.array([-1.1, 3.1, 3.1, -1.1, -1.1], dtype=np.double) 365 | polygon_y = np.array([-0.1, -0.1, 3.1, 3.1, -0.1], dtype=np.double) 366 | polygon = GeometryList(polygon_x, polygon_y) 367 | 368 | mk.contacts_compute_boundary(node_mask, 2.0, polygon) 369 | 370 | contacts = mk.contacts_get() 371 | sort_contacts_by_mesh2d_indices(contacts) 372 | 373 | assert_array_equal(contacts.mesh1d_indices, exp_mesh1d_indices) 374 | assert_array_equal(contacts.mesh2d_indices, exp_mesh2d_indices) 375 | 376 | 377 | def test_contacts_compute_boundary_with_no_polygon(): 378 | """Tests `contacts_compute_boundary` with a 2x2 Mesh2d and a Mesh1d with 5 nodes. 379 | 380 | 381 | ---3---4 382 | 2 383 | | 6---7---8 384 | 1 | | | 385 | | 3---4---5 386 | 0 | | | 387 | 0---1---2 388 | """ 389 | 390 | mk = MeshKernel() 391 | 392 | mesh2d = Mesh2dFactory.create(2, 2) 393 | 394 | node_x = np.array([-1.0, -1.0, -0.5, 0.5, 1.5], dtype=np.double) 395 | node_y = np.array([0.5, 1.5, 2.5, 3.0, 3.0], dtype=np.double) 396 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 4], dtype=np.int32) 397 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 398 | 399 | mk.mesh2d_set(mesh2d) 400 | mk.mesh1d_set(mesh1d) 401 | 402 | node_mask = np.array([True, True, True, True, True]) 403 | mk.contacts_compute_boundary(node_mask, 2.0) 404 | 405 | contacts = mk.contacts_get() 406 | 407 | sort_contacts_by_mesh2d_indices(contacts) 408 | 409 | exp_mesh1d_indices = np.array([0, 2, 4], dtype=np.int32) 410 | exp_mesh2d_indices = np.array([0, 2, 3], dtype=np.int32) 411 | 412 | assert_array_equal(contacts.mesh1d_indices, exp_mesh1d_indices) 413 | assert_array_equal(contacts.mesh2d_indices, exp_mesh2d_indices) 414 | 415 | 416 | def test_contacts_compute_with_points_after_deletion( 417 | meshkernel_with_mesh2d: MeshKernel, 418 | ): 419 | """Tests contacts_compute_with_points and mesh2d_delete to ensure the correct indices are retrieved 420 | in the gap-free array. 421 | """ 422 | 423 | mk = MeshKernel() 424 | 425 | mesh2d = Mesh2dFactory.create(3, 3) 426 | 427 | node_x = np.array([0.75, 2.1], dtype=np.double) 428 | node_y = np.array([0.75, 2.1], dtype=np.double) 429 | edge_nodes = np.array([0, 1], dtype=np.int32) 430 | mesh1d = Mesh1d(node_x, node_y, edge_nodes) 431 | 432 | mk.mesh1d_set(mesh1d) 433 | mk.mesh2d_set(mesh2d) 434 | 435 | node_mask = np.full(node_x.size, True) 436 | 437 | # The points indicating the faces to connect 438 | points_x = np.array([0.75, 2.1], dtype=np.double) 439 | points_y = np.array([0.75, 2.1], dtype=np.double) 440 | points = GeometryList(points_x, points_y) 441 | 442 | mk.contacts_compute_with_points(node_mask, points) 443 | 444 | contacts = mk.contacts_get() 445 | sort_contacts_by_mesh2d_indices(contacts) 446 | 447 | assert contacts.mesh1d_indices.size == 2 448 | assert contacts.mesh2d_indices.size == 2 449 | 450 | assert contacts.mesh1d_indices[0] == 0 451 | assert contacts.mesh1d_indices[1] == 1 452 | 453 | assert contacts.mesh2d_indices[0] == 0 454 | assert contacts.mesh2d_indices[1] == 8 455 | 456 | x_coordinates = np.array([-1.0, 1.5, 1.5, -1.0, -1.0]) 457 | y_coordinates = np.array([1.5, 1.5, 3.5, 3.5, 1.5]) 458 | 459 | polygon = GeometryList(x_coordinates=x_coordinates, y_coordinates=y_coordinates) 460 | mk.mesh2d_delete( 461 | geometry_list=polygon, 462 | delete_option=DeleteMeshOption.INSIDE_NOT_INTERSECTED, 463 | invert_deletion=False, 464 | ) 465 | 466 | mk.contacts_compute_with_points(node_mask, points) 467 | 468 | contacts = mk.contacts_get() 469 | sort_contacts_by_mesh2d_indices(contacts) 470 | 471 | assert contacts.mesh1d_indices.size == 2 472 | assert contacts.mesh2d_indices.size == 2 473 | 474 | assert contacts.mesh1d_indices[0] == 0 475 | assert contacts.mesh1d_indices[1] == 1 476 | 477 | assert contacts.mesh2d_indices[0] == 0 478 | assert contacts.mesh2d_indices[1] == 7 479 | -------------------------------------------------------------------------------- /tests/test_mesh2d_factory.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from mesh2d_factory import Mesh2dFactory 4 | from numpy.testing import assert_array_equal 5 | 6 | from meshkernel import InputError, Mesh2d 7 | 8 | 9 | def test_create_rectilinear_mesh_simple(): 10 | """Test create_rectilinear_mesh``by creating a simple 2x2 mesh 11 | 12 | 6---7---8 13 | | | | 14 | 3---4---5 15 | | | | 16 | 0---1---2 17 | 18 | """ 19 | mesh2d = Mesh2dFactory.create(2, 2) 20 | 21 | # Assert node positions 22 | assert_array_equal( 23 | mesh2d.node_x, np.array([0.0, 1.0, 2.0, 0.0, 1.0, 2.0, 0.0, 1.0, 2.0]) 24 | ) 25 | assert_array_equal( 26 | mesh2d.node_y, np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]) 27 | ) 28 | 29 | # Assert indices of edge nodes 30 | assert_array_equal( 31 | mesh2d.edge_nodes, 32 | np.array( 33 | [0, 3, 1, 4, 2, 5, 3, 6, 4, 7, 5, 8, 1, 0, 2, 1, 4, 3, 5, 4, 7, 6, 8, 7] 34 | ), 35 | ) 36 | 37 | 38 | def test_create_rectilinear_mesh_extensive(): 39 | """Test create_rectilinear_mesh``by creating a 2x3 mesh. 40 | Also set 41 | - origin_x to -1.0 and spacing_x to 2.0 and 42 | - origin_y to 1.0 and spacing_y to 3.0. 43 | 44 | 45 | 8---9---10---11 46 | | | | | 47 | 4---5---6---7 48 | | | | | 49 | 0---1---2---3 50 | 51 | """ 52 | mesh2d = Mesh2dFactory.create( 53 | 2, 3, origin_x=-1.0, origin_y=1.0, spacing_x=2.0, spacing_y=3.0 54 | ) 55 | 56 | # Assert node positions 57 | assert_array_equal( 58 | mesh2d.node_x, 59 | np.array([-1.0, 1.0, 3.0, 5.0, -1.0, 1.0, 3.0, 5.0, -1.0, 1.0, 3.0, 5.0]), 60 | ) 61 | assert_array_equal( 62 | mesh2d.node_y, 63 | np.array([1.0, 1.0, 1.0, 1.0, 4.0, 4.0, 4.0, 4.0, 7.0, 7.0, 7.0, 7.0]), 64 | ) 65 | 66 | # Assert indices of edge nodes 67 | assert_array_equal( 68 | mesh2d.edge_nodes, 69 | np.array( 70 | [ 71 | 0, 72 | 4, 73 | 1, 74 | 5, 75 | 2, 76 | 6, 77 | 3, 78 | 7, 79 | 4, 80 | 8, 81 | 5, 82 | 9, 83 | 6, 84 | 10, 85 | 7, 86 | 11, 87 | 1, 88 | 0, 89 | 2, 90 | 1, 91 | 3, 92 | 2, 93 | 5, 94 | 4, 95 | 6, 96 | 5, 97 | 7, 98 | 6, 99 | 9, 100 | 8, 101 | 10, 102 | 9, 103 | 11, 104 | 10, 105 | ] 106 | ), 107 | ) 108 | 109 | 110 | def test_create_rectilinear_mesh_reject_negative_spacing(): 111 | """Tests if `create_rectilinear_mesh` rejects negative spacing.""" 112 | with pytest.raises(InputError): 113 | Mesh2dFactory.create(2, 2, spacing_x=-1.0) 114 | 115 | with pytest.raises(InputError): 116 | Mesh2dFactory.create(2, 2, spacing_y=-1.0) 117 | 118 | 119 | def test_create_rectilinear_mesh_reject_negative_rows_columns(): 120 | """Tests if `create_rectilinear_mesh` rejects negative spacing.""" 121 | with pytest.raises(InputError): 122 | Mesh2dFactory.create(-1, 2) 123 | 124 | with pytest.raises(InputError): 125 | Mesh2dFactory.create(2, -1) 126 | -------------------------------------------------------------------------------- /tests/test_mesh2d_refinement.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from meshkernel import GeometryList, MakeGridParameters, MeshKernel 4 | 5 | 6 | def test_mesh2d_casulli_refinement(): 7 | """Test `mesh2d_casulli_refinement`.""" 8 | mk = MeshKernel() 9 | mk.mesh2d_make_rectangular_mesh( 10 | MakeGridParameters( 11 | num_columns=10, 12 | num_rows=10, 13 | block_size_x=1, 14 | block_size_y=1, 15 | ) 16 | ) 17 | mesh2d = mk.mesh2d_get() 18 | mk.mesh2d_casulli_refinement() 19 | refined_mesh2d = mk.mesh2d_get() 20 | assert mesh2d.node_x.size < refined_mesh2d.node_x.size 21 | 22 | 23 | def test_mesh2d_casulli_refinement_on_polygon(): 24 | """Test `mesh2d_casulli_refinement_on_polygon`.""" 25 | mk = MeshKernel() 26 | mk.mesh2d_make_rectangular_mesh( 27 | MakeGridParameters( 28 | num_columns=10, 29 | num_rows=10, 30 | block_size_x=1, 31 | block_size_y=1, 32 | ) 33 | ) 34 | mesh2d = mk.mesh2d_get() 35 | polygon_x_coordinates = np.array([2.5, 7.5, 5.5, 2.5], dtype=np.double) 36 | polygon_y_coordinates = np.array([2.5, 4.5, 8.5, 2.5], dtype=np.double) 37 | polygon = GeometryList(polygon_x_coordinates, polygon_y_coordinates) 38 | mk.mesh2d_casulli_refinement_on_polygon(polygon) 39 | refined_mesh2d = mk.mesh2d_get() 40 | assert mesh2d.node_x.size < refined_mesh2d.node_x.size 41 | 42 | 43 | def test_mesh2d_casulli_derefinement(): 44 | """Test `mesh2d_casulli_derefinement`.""" 45 | 46 | mk = MeshKernel() 47 | mk.mesh2d_make_rectangular_mesh( 48 | MakeGridParameters( 49 | num_columns=10, 50 | num_rows=10, 51 | block_size_x=1, 52 | block_size_y=1, 53 | ) 54 | ) 55 | mesh2d = mk.mesh2d_get() 56 | mk.mesh2d_casulli_derefinement() 57 | derefined_mesh2d = mk.mesh2d_get() 58 | assert mesh2d.node_x.size > derefined_mesh2d.node_x.size 59 | 60 | 61 | def test_mesh2d_casulli_derefinement_on_polygon(): 62 | """Test `mesh2d_casulli_derefinement_on_polygon`.""" 63 | mk = MeshKernel() 64 | mk.mesh2d_make_rectangular_mesh( 65 | MakeGridParameters( 66 | num_columns=10, 67 | num_rows=10, 68 | block_size_x=1, 69 | block_size_y=1, 70 | ) 71 | ) 72 | mesh2d = mk.mesh2d_get() 73 | polygon_x_coordinates = np.array([2.5, 7.5, 5.5, 2.5], dtype=np.double) 74 | polygon_y_coordinates = np.array([2.5, 4.5, 8.5, 2.5], dtype=np.double) 75 | polygon = GeometryList(polygon_x_coordinates, polygon_y_coordinates) 76 | mk.mesh2d_casulli_derefinement_on_polygon(polygon) 77 | derefined_mesh2d = mk.mesh2d_get() 78 | assert mesh2d.node_x.size > derefined_mesh2d.node_x.size 79 | -------------------------------------------------------------------------------- /tests/test_miscellaneous.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from meshkernel import GeometryList, MeshKernel 5 | from meshkernel.py_structures import ProjectionType 6 | 7 | cases_get_splines = [ 8 | ( 9 | 20, # number_of_points_between_nodes 10 | np.array([10.0, 20.0, 30.0], dtype=np.double), # x_coordinates 11 | np.array([-5.0, 5.0, -5.0], dtype=np.double), # y_coordinates 12 | ), 13 | ( 14 | 1, # number_of_points_between_nodes 15 | np.array([10.0, 20.0, 30.0], dtype=np.double), # x_coordinates 16 | np.array([-5.0, 5.0, -5.0], dtype=np.double), # y_coordinates 17 | ), 18 | ( 19 | 10000, # number_of_points_between_nodes 20 | np.array([-5.0, 0.0, 5.0], dtype=np.double), # x_coordinates 21 | np.array([-5.0, 100.0, 5.0], dtype=np.double), # y_coordinates 22 | ), 23 | ] 24 | 25 | 26 | @pytest.mark.parametrize( 27 | "number_of_points_between_nodes, x_coordinates, y_coordinates", cases_get_splines 28 | ) 29 | def test_get_splines( 30 | number_of_points_between_nodes: int, 31 | x_coordinates: np.ndarray, 32 | y_coordinates: np.ndarray, 33 | ): 34 | """Test `get_splines` by checking if the dimensions of the generated spline are correct""" 35 | mk = MeshKernel() 36 | geometry_list_in = GeometryList(x_coordinates, y_coordinates) 37 | 38 | geometry_list_out = mk.get_splines(geometry_list_in, number_of_points_between_nodes) 39 | 40 | original_number_of_coordinates = geometry_list_in.x_coordinates.size 41 | expected_new_number_of_coordinates = ( 42 | original_number_of_coordinates * number_of_points_between_nodes 43 | - number_of_points_between_nodes 44 | + original_number_of_coordinates 45 | + 1 46 | ) 47 | 48 | assert expected_new_number_of_coordinates == geometry_list_out.x_coordinates.size 49 | 50 | 51 | def test_get_projection(): 52 | """Test `get_projection` gets a cartesian projection type""" 53 | mk = MeshKernel() 54 | projection = mk.get_projection() 55 | assert projection == ProjectionType.CARTESIAN 56 | -------------------------------------------------------------------------------- /tests/test_orthogonalization.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mesh2d_factory import Mesh2dFactory 3 | from numpy.testing import assert_array_equal 4 | from pytest import approx 5 | 6 | from meshkernel import ( 7 | GeometryList, 8 | Mesh2d, 9 | MeshKernel, 10 | OrthogonalizationParameters, 11 | ProjectToLandBoundaryOption, 12 | ) 13 | 14 | 15 | def test_mesh2d_compute_orthogonalization(): 16 | """Tests `mesh2d_compute_orthogonalization` with a 3x3 Mesh2d with an uncentered middle node. 17 | 6---7---8 18 | | | | 19 | 3---4*--5 20 | | | | 21 | 0---1---2 22 | """ 23 | 24 | mk = MeshKernel() 25 | 26 | node_x = np.array( 27 | [0.0, 1.0, 2.0, 0.0, 1.3, 2.0, 0.0, 1.0, 2.0], 28 | dtype=np.double, 29 | ) 30 | node_y = np.array( 31 | [0.0, 0.0, 0.0, 1.0, 1.3, 1.0, 2.0, 2.0, 2.0], 32 | dtype=np.double, 33 | ) 34 | edge_nodes = np.array( 35 | [0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 0, 3, 1, 4, 2, 5, 3, 6, 4, 7, 5, 8], 36 | dtype=np.int32, 37 | ) 38 | 39 | mk.mesh2d_set(Mesh2d(node_x, node_y, edge_nodes)) 40 | 41 | polygon_x = np.array([-0.1, 2.1, 2.1, -0.1, -0.1], dtype=np.double) 42 | polygon_y = np.array([-0.1, -0.1, 2.1, 2.1, -0.1], dtype=np.double) 43 | polygon = GeometryList(polygon_x, polygon_y) 44 | 45 | land_boundary_x = np.array([0.0, 1.0, 2.0], dtype=np.double) 46 | land_boundary_y = np.array([0.0, 0.0, 0.0], dtype=np.double) 47 | land_boundary = GeometryList(land_boundary_x, land_boundary_y) 48 | 49 | mk.mesh2d_compute_orthogonalization( 50 | project_to_land_boundary_option=ProjectToLandBoundaryOption.DO_NOT_PROJECT_TO_LANDBOUNDARY, 51 | orthogonalization_parameters=OrthogonalizationParameters(outer_iterations=10), 52 | land_boundaries=land_boundary, 53 | selecting_polygon=polygon, 54 | ) 55 | 56 | mesh2d = mk.mesh2d_get() 57 | 58 | assert 1.0 <= mesh2d.node_x[4] < 1.3 59 | assert 1.0 <= mesh2d.node_y[4] < 1.3 60 | 61 | 62 | def test_mesh2d_get_orthogonality_orthogonal_mesh2d(): 63 | """Tests `mesh2d_get_orthogonality` with an orthogonal 2x2 Mesh2d. 64 | 6---7---8 65 | | | | 66 | 3---4---5 67 | | | | 68 | 0---1---2 69 | """ 70 | 71 | mk = MeshKernel() 72 | mk.mesh2d_set(Mesh2dFactory.create(2, 2)) 73 | 74 | orthogonality = mk.mesh2d_get_orthogonality() 75 | 76 | assert orthogonality.values.size == 12 77 | 78 | exp_orthogonality = np.array( 79 | [ 80 | -999.0, 81 | 0.0, 82 | -999.0, 83 | -999.0, 84 | 0.0, 85 | -999.0, 86 | -999.0, 87 | -999.0, 88 | 0.0, 89 | 0.0, 90 | -999.0, 91 | -999.0, 92 | ], 93 | dtype=np.double, 94 | ) 95 | 96 | assert_array_equal(orthogonality.values, exp_orthogonality) 97 | 98 | 99 | def test_mesh2d_get_orthogonality_not_orthogonal_mesh2d(): 100 | """Tests `mesh2d_get_orthogonality` with a non-orthogonal 3x3 Mesh2d. 101 | 6---7---8 102 | | | | 103 | 3---4*--5 104 | | | | 105 | 0---1---2 106 | """ 107 | 108 | mk = MeshKernel() 109 | 110 | node_x = np.array( 111 | [0.0, 1.0, 2.0, 0.0, 1.8, 2.0, 0.0, 1.0, 2.0], 112 | dtype=np.double, 113 | ) 114 | node_y = np.array( 115 | [0.0, 0.0, 0.0, 1.0, 1.8, 1.0, 2.0, 2.0, 2.0], 116 | dtype=np.double, 117 | ) 118 | edge_nodes = np.array( 119 | [0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 0, 3, 1, 4, 2, 5, 3, 6, 4, 7, 5, 8], 120 | dtype=np.int32, 121 | ) 122 | 123 | mk.mesh2d_set(Mesh2d(node_x, node_y, edge_nodes)) 124 | 125 | orthogonality = mk.mesh2d_get_orthogonality() 126 | 127 | assert orthogonality.values.size == 12 128 | 129 | assert orthogonality.values[0] == -999.0 130 | assert orthogonality.values[1] == -999.0 131 | assert orthogonality.values[2] > 0.0 132 | assert orthogonality.values[3] > 0.0 133 | assert orthogonality.values[4] == -999.0 134 | assert orthogonality.values[5] == -999.0 135 | assert orthogonality.values[6] == -999.0 136 | assert orthogonality.values[7] > 0.0 137 | assert orthogonality.values[8] == -999.0 138 | assert orthogonality.values[9] == -999.0 139 | assert orthogonality.values[10] > 0.0 140 | assert orthogonality.values[11] == -999.0 141 | 142 | 143 | def test_mesh2d_get_smoothness_smooth_mesh2d(): 144 | r"""Tests `mesh2d_get_smoothness` with a simple triangular Mesh2d. 145 | 146 | 3---2 147 | / \ / 148 | 0---1 149 | """ 150 | 151 | mk = MeshKernel() 152 | 153 | node_x = np.array( 154 | [0.0, 4.0, 6.0, 2.0, 2.0], 155 | dtype=np.double, 156 | ) 157 | node_y = np.array( 158 | [0.0, 0.0, 3.0, 3.0, 1.0], 159 | dtype=np.double, 160 | ) 161 | edge_nodes = np.array( 162 | [0, 1, 1, 2, 2, 3, 3, 0, 1, 3], 163 | dtype=np.int32, 164 | ) 165 | 166 | mk.mesh2d_set(Mesh2d(node_x, node_y, edge_nodes)) 167 | 168 | smoothness = mk.mesh2d_get_smoothness() 169 | 170 | assert smoothness.values.size == 5 171 | 172 | assert smoothness.values[0] == -999.0 173 | assert smoothness.values[1] == -999.0 174 | assert smoothness.values[2] == -999.0 175 | assert smoothness.values[3] == -999.0 176 | assert smoothness.values[4] == approx(1.0, abs=0.01) 177 | -------------------------------------------------------------------------------- /tests/test_py_structures.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from meshkernel import ( 5 | AveragingMethod, 6 | DeleteMeshOption, 7 | GeometryList, 8 | Mesh2d, 9 | Mesh2dLocation, 10 | MeshRefinementParameters, 11 | OrthogonalizationParameters, 12 | ProjectToLandBoundaryOption, 13 | RefinementType, 14 | ) 15 | 16 | cases_deletemeshoption_values = [ 17 | (DeleteMeshOption.INSIDE_NOT_INTERSECTED, 0), 18 | (DeleteMeshOption.INSIDE_AND_INTERSECTED, 1), 19 | ] 20 | 21 | from meshkernel.errors import InputError 22 | 23 | 24 | @pytest.mark.parametrize("enum_val, exp_int", cases_deletemeshoption_values) 25 | def test_deletemeshoption_values(enum_val: DeleteMeshOption, exp_int: int): 26 | """Tests the integer values of the `DeleteMeshOption` enum.""" 27 | 28 | assert enum_val == exp_int 29 | 30 | 31 | cases_projecttolandboundaryoption_values = [ 32 | (ProjectToLandBoundaryOption.DO_NOT_PROJECT_TO_LANDBOUNDARY, 0), 33 | (ProjectToLandBoundaryOption.TO_ORIGINAL_NETBOUNDARY, 1), 34 | (ProjectToLandBoundaryOption.OUTER_MESH_BOUNDARY_TO_LANDBOUNDARY, 2), 35 | (ProjectToLandBoundaryOption.INNER_AND_OUTER_MESH_BOUNDARY_TO_LANDBOUNDARY, 3), 36 | (ProjectToLandBoundaryOption.WHOLE_MESH, 4), 37 | ] 38 | 39 | 40 | @pytest.mark.parametrize("enum_val, exp_int", cases_projecttolandboundaryoption_values) 41 | def test_projecttolandboundaryoption_values( 42 | enum_val: ProjectToLandBoundaryOption, exp_int: int 43 | ): 44 | """Tests the integer values of the `ProjectToLandBoundaryOption` enum.""" 45 | 46 | assert enum_val == exp_int 47 | 48 | 49 | cases_refinement_type_values = [ 50 | (RefinementType.WAVE_COURANT, 1), 51 | (RefinementType.REFINEMENT_LEVELS, 2), 52 | (RefinementType.RIDGE_DETECTION, 3), 53 | ] 54 | 55 | 56 | @pytest.mark.parametrize("enum_val, exp_int", cases_refinement_type_values) 57 | def test_refinement_type_values(enum_val: RefinementType, exp_int: int): 58 | """Tests the integer values of the `RefinementType` enum.""" 59 | 60 | assert enum_val == exp_int 61 | 62 | 63 | cases_Mesh2dLocation_values = [ 64 | (Mesh2dLocation.FACES, 0), 65 | (Mesh2dLocation.NODES, 1), 66 | (Mesh2dLocation.EDGES, 2), 67 | ] 68 | 69 | 70 | @pytest.mark.parametrize("enum_val, exp_int", cases_Mesh2dLocation_values) 71 | def test_mesh2dlocation_values(enum_val: Mesh2dLocation, exp_int: int): 72 | """Tests the integer values of the `Mesh2dLocation` enum.""" 73 | 74 | assert enum_val == exp_int 75 | 76 | 77 | cases_averagingmethod_values = [ 78 | (AveragingMethod.SIMPLE_AVERAGING, 1), 79 | (AveragingMethod.CLOSEST_POINT, 2), 80 | (AveragingMethod.MAX, 3), 81 | (AveragingMethod.MIN, 4), 82 | (AveragingMethod.INVERSE_WEIGHT_DISTANCE, 5), 83 | (AveragingMethod.MIN_ABS, 6), 84 | ] 85 | 86 | 87 | @pytest.mark.parametrize("enum_val, exp_int", cases_averagingmethod_values) 88 | def test_averagingmethod_values(enum_val: AveragingMethod, exp_int: int): 89 | """Tests the integer values of the `AveragingMethod` enum.""" 90 | 91 | assert enum_val == exp_int 92 | 93 | 94 | def test_mesdh2d_constructor(): 95 | """Tests the default values after constructing a `Mesh2d`.""" 96 | 97 | node_x = np.array([0.0, 1.0, 1.0, 0.0], dtype=np.double) 98 | node_y = np.array([0.0, 0.0, 1.0, 1.0], dtype=np.double) 99 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 0], dtype=np.int32) 100 | 101 | mesh2d = Mesh2d(node_x, node_y, edge_nodes) 102 | 103 | assert mesh2d.node_x.size == 4 104 | assert mesh2d.node_y.size == 4 105 | assert mesh2d.edge_nodes.size == 8 106 | assert mesh2d.face_nodes.size == 0 107 | assert mesh2d.nodes_per_face.size == 0 108 | assert mesh2d.edge_x.size == 0 109 | assert mesh2d.edge_y.size == 0 110 | assert mesh2d.face_x.size == 0 111 | assert mesh2d.face_y.size == 0 112 | 113 | 114 | def test_mesh2d_equal(): 115 | node_x = np.array([0.0, 1.0, 1.0, 0.0], dtype=np.double) 116 | node_y = np.array([0.0, 0.0, 1.0, 1.0], dtype=np.double) 117 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 0], dtype=np.int32) 118 | 119 | mesh2d_1 = Mesh2d(node_x, node_y, edge_nodes) 120 | mesh2d_2 = Mesh2d(node_x, node_y, edge_nodes) 121 | 122 | assert mesh2d_1 == mesh2d_2 123 | 124 | 125 | def test_mesh2d_almost_equal(): 126 | node_x_1 = np.array([0.0, 1.0, 1.0, 0.0], dtype=np.double) 127 | node_y = np.array([0.0, 0.0, 1.0, 1.0], dtype=np.double) 128 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 0], dtype=np.int32) 129 | 130 | mesh2d_1 = Mesh2d(node_x_1, node_y, edge_nodes) 131 | 132 | # Apply a small perturbation to the first element of node_x then create a new mesh 133 | node_x_2 = np.array([0.0, 1.0, 1.0, 0.0], dtype=np.double) 134 | node_x_2[1] += 1.0e-6 135 | mesh2d_2 = Mesh2d(node_x_2, node_y, edge_nodes) 136 | 137 | assert mesh2d_1.almost_equal(mesh2d_2, rtol=0.0, atol=1.0e-5) 138 | assert not mesh2d_1.almost_equal(mesh2d_2, rtol=0.0, atol=1.0e-7) 139 | 140 | 141 | def test_geometrylist_constructor(): 142 | """Tests the default values after constructing a `GeometryList`.""" 143 | 144 | x_coordinates = np.array([0.0], dtype=np.double) 145 | y_coordinates = np.array([1.0], dtype=np.double) 146 | geometry_list = GeometryList(x_coordinates, y_coordinates) 147 | 148 | assert geometry_list.x_coordinates.size == 1 149 | assert geometry_list.y_coordinates.size == 1 150 | assert geometry_list.values.size == 0 151 | assert geometry_list.geometry_separator == -999.0 152 | assert geometry_list.inner_outer_separator == -998.0 153 | 154 | 155 | def test_geometrylist_constructor_raises_exception(): 156 | """Tests `GeometryList` constructor raises an exception when coordinates and values have different lengths.""" 157 | 158 | x_coordinates = np.array([0.0, 2.0], dtype=np.double) 159 | y_coordinates = np.array([1.0, 2.0], dtype=np.double) 160 | values = np.array([1.0], dtype=np.double) 161 | 162 | with pytest.raises(InputError): 163 | GeometryList( 164 | x_coordinates=x_coordinates, y_coordinates=y_coordinates, values=values 165 | ) 166 | 167 | 168 | def test_orthogonalizationparameters_constructor(): 169 | """Tests the default values after constructing a `OrthogonalizationParameters`.""" 170 | 171 | parameters = OrthogonalizationParameters() 172 | 173 | assert parameters.outer_iterations == 2 174 | assert parameters.boundary_iterations == 25 175 | assert parameters.inner_iterations == 25 176 | assert parameters.orthogonalization_to_smoothing_factor == 0.975 177 | assert parameters.orthogonalization_to_smoothing_factor_at_boundary == 1.0 178 | assert parameters.areal_to_angle_smoothing_factor == 1.0 179 | 180 | 181 | def test_meshrefinementparameter_constructor_defaults(): 182 | """Tests the default values after constructing a `MeshRefinementParameters`.""" 183 | 184 | parameters = MeshRefinementParameters( 185 | False, True, 1.0, RefinementType.WAVE_COURANT, False, True 186 | ) 187 | 188 | assert parameters.refine_intersected is False 189 | assert parameters.use_mass_center_when_refining is True 190 | assert parameters.min_edge_size == 1.0 191 | assert parameters.refinement_type is RefinementType.WAVE_COURANT 192 | assert parameters.connect_hanging_nodes is False 193 | assert parameters.account_for_samples_outside_face is True 194 | assert parameters.max_refinement_iterations == 10 195 | -------------------------------------------------------------------------------- /tests/test_py_structures_inputs.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from numpy.testing import assert_array_equal 4 | 5 | from meshkernel import ( 6 | Contacts, 7 | CurvilinearGrid, 8 | CurvilinearParameters, 9 | GeometryList, 10 | GriddedSamples, 11 | InputError, 12 | MakeGridParameters, 13 | Mesh1d, 14 | Mesh2d, 15 | MeshKernel, 16 | MeshRefinementParameters, 17 | OrthogonalizationParameters, 18 | RefinementType, 19 | SplinesToCurvilinearParameters, 20 | ) 21 | 22 | 23 | def test_mesh2d_implicit_int_conversions(): 24 | """Test implicit conversion from int to double for Mesh2d works""" 25 | mk = MeshKernel() 26 | 27 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 3, 0], dtype=np.int16) 28 | node_x = np.array([0, 1, 1, 0], dtype=np.int32) 29 | node_y = np.array([0, 0, 1, 1], dtype=np.int32) 30 | 31 | input_mesh2d = Mesh2d(node_x, node_y, edge_nodes) 32 | mk.mesh2d_set(input_mesh2d) 33 | 34 | output_mesh2d = mk.mesh2d_get() 35 | 36 | # Test if the input and output differs 37 | assert_array_equal(output_mesh2d.edge_nodes, input_mesh2d.edge_nodes) 38 | assert_array_equal(output_mesh2d.node_x, input_mesh2d.node_x) 39 | assert_array_equal(output_mesh2d.node_y, input_mesh2d.node_y) 40 | 41 | # Test if faces are correctly calculated 42 | assert_array_equal(output_mesh2d.face_nodes, np.array([0, 1, 2, 3], dtype=np.int32)) 43 | assert_array_equal(output_mesh2d.nodes_per_face, np.array([4], dtype=np.int32)) 44 | assert_array_equal(output_mesh2d.face_x, np.array([0.5], dtype=np.double)) 45 | assert_array_equal(output_mesh2d.face_y, np.array([0.5], dtype=np.double)) 46 | 47 | 48 | def test_mesh2d_implicit_string_conversions(): 49 | """Test implicit conversions from string to doubles works for Mesh2d""" 50 | node_x = ["1.0", "2.0", "3.0"] 51 | node_y = ["4.0", "5.0", "6.0"] 52 | edge_nodes = ["0", "1", "1", "2"] 53 | face_nodes = ["0", "1", "2"] 54 | nodes_per_face = ["3", "4", "5"] 55 | edge_x = ["1.5", "2.5"] 56 | edge_y = ["4.5", "5.5"] 57 | face_x = ["2.0", "3.0"] 58 | face_y = ["5.0", "6.0"] 59 | edge_faces = ["0", "1"] 60 | face_edges = ["2", "3"] 61 | 62 | mesh_2d = Mesh2d( 63 | node_x=node_x, 64 | node_y=node_y, 65 | edge_nodes=edge_nodes, 66 | face_nodes=face_nodes, 67 | nodes_per_face=nodes_per_face, 68 | edge_x=edge_x, 69 | edge_y=edge_y, 70 | face_x=face_x, 71 | face_y=face_y, 72 | edge_faces=edge_faces, 73 | face_edges=face_edges, 74 | ) 75 | 76 | assert_array_equal(mesh_2d.node_x, np.asarray(node_x, dtype=np.double)) 77 | assert_array_equal(mesh_2d.node_y, np.asarray(node_y, dtype=np.double)) 78 | assert_array_equal(mesh_2d.edge_nodes, np.asarray(edge_nodes, dtype=np.int32)) 79 | assert_array_equal(mesh_2d.face_nodes, np.asarray(face_nodes, dtype=np.int32)) 80 | assert_array_equal( 81 | mesh_2d.nodes_per_face, np.asarray(nodes_per_face, dtype=np.int32) 82 | ) 83 | assert_array_equal(mesh_2d.edge_x, np.asarray(edge_x, dtype=np.double)) 84 | assert_array_equal(mesh_2d.edge_y, np.asarray(edge_y, dtype=np.double)) 85 | assert_array_equal(mesh_2d.face_x, np.asarray(face_x, dtype=np.double)) 86 | assert_array_equal(mesh_2d.face_y, np.asarray(face_y, dtype=np.double)) 87 | assert_array_equal(mesh_2d.edge_faces, np.asarray(edge_faces, dtype=np.int32)) 88 | assert_array_equal(mesh_2d.face_edges, np.asarray(face_edges, dtype=np.int32)) 89 | 90 | 91 | def test_mesh2d_invalid_input(): 92 | """Test exceptions due to malformed input for Mesh2d""" 93 | with pytest.raises(ValueError): 94 | Mesh2d(node_x=["1.0", "invalid", "3.0"]) 95 | 96 | with pytest.raises(ValueError): 97 | Mesh2d(edge_nodes=["0", "1", "invalid", "2", "3"]) 98 | 99 | with pytest.raises(ValueError): 100 | Mesh2d(face_nodes=["0", "1", "2", "abc"]) 101 | 102 | 103 | def test_geometrylist_implicit_int_conversions(): 104 | """Test implicit conversion from int to double for GeometryList works""" 105 | 106 | x_coordinates = np.array([2, 5, 3, 0, 2], dtype=np.int32) 107 | y_coordinates = np.array([5, 3, 5, 2, 0], dtype=np.int32) 108 | values = np.array([0, 0, 1, 1, 1], dtype=np.int32) 109 | 110 | geometry_list = GeometryList( 111 | x_coordinates=x_coordinates, y_coordinates=y_coordinates, values=values 112 | ) 113 | 114 | assert_array_equal( 115 | geometry_list.x_coordinates, np.asarray(x_coordinates, dtype=np.double) 116 | ) 117 | assert_array_equal( 118 | geometry_list.y_coordinates, np.asarray(y_coordinates, dtype=np.double) 119 | ) 120 | assert_array_equal(geometry_list.values, np.asarray(values, dtype=np.double)) 121 | 122 | 123 | def test_geometrylist_implicit_string_conversions(): 124 | """Test implicit conversions from string to doubles works for GeometryList""" 125 | 126 | x_coordinates = ["1.0", "2.0", "3.0"] 127 | y_coordinates = ["4.0", "5.0", "6.0"] 128 | values = ["0.1", "0.2", "0.3"] 129 | 130 | geometry_list = GeometryList( 131 | x_coordinates=x_coordinates, y_coordinates=y_coordinates, values=values 132 | ) 133 | 134 | assert_array_equal( 135 | geometry_list.x_coordinates, np.array([1.0, 2.0, 3.0], dtype=np.double) 136 | ) 137 | assert_array_equal( 138 | geometry_list.y_coordinates, np.array([4.0, 5.0, 6.0], dtype=np.double) 139 | ) 140 | assert_array_equal(geometry_list.values, np.array([0.1, 0.2, 0.3], dtype=np.double)) 141 | assert geometry_list.geometry_separator == -999.0 142 | assert geometry_list.inner_outer_separator == -998.0 143 | 144 | 145 | def test_geometrylist_invalid_input(): 146 | """Test exceptions due to malformed input for GeometryList""" 147 | 148 | with pytest.raises(InputError): 149 | GeometryList(x_coordinates=["1.0", "2.0"], y_coordinates=["4.0"]) 150 | 151 | with pytest.raises(InputError): 152 | GeometryList(x_coordinates=["1.0", "2.0", "3.0"], values=["0.1", "0.2"]) 153 | 154 | with pytest.raises(ValueError): 155 | GeometryList( 156 | x_coordinates=["1.0", "invalid", "3.0"], y_coordinates=["4.0", "5.0", "6.0"] 157 | ) 158 | 159 | 160 | def test_orthogonalization_parameters_implicit_string_conversions(): 161 | """Test implicit conversion from string to double for OrthogonalizationParameters works""" 162 | 163 | orthogonalization_parameters = OrthogonalizationParameters( 164 | outer_iterations="2", 165 | boundary_iterations="25", 166 | inner_iterations="25", 167 | orthogonalization_to_smoothing_factor="0.975", 168 | orthogonalization_to_smoothing_factor_at_boundary="1.0", 169 | areal_to_angle_smoothing_factor="1.0", 170 | ) 171 | 172 | assert orthogonalization_parameters.outer_iterations == 2 173 | assert orthogonalization_parameters.boundary_iterations == 25 174 | assert orthogonalization_parameters.inner_iterations == 25 175 | assert orthogonalization_parameters.orthogonalization_to_smoothing_factor == 0.975 176 | assert ( 177 | orthogonalization_parameters.orthogonalization_to_smoothing_factor_at_boundary 178 | == 1.0 179 | ) 180 | assert orthogonalization_parameters.areal_to_angle_smoothing_factor == 1.0 181 | 182 | 183 | def test_orthogonalization_parameters_invalid_input(): 184 | """Test exceptions due to malformed input for OrthogonalizationParameters""" 185 | 186 | # Test exceptions for invalid inputs 187 | with pytest.raises(ValueError): 188 | OrthogonalizationParameters(outer_iterations="invalid") 189 | 190 | with pytest.raises(ValueError): 191 | OrthogonalizationParameters(boundary_iterations="invalid") 192 | 193 | with pytest.raises(ValueError): 194 | OrthogonalizationParameters(inner_iterations="invalid") 195 | 196 | with pytest.raises(ValueError): 197 | OrthogonalizationParameters(orthogonalization_to_smoothing_factor="invalid") 198 | 199 | with pytest.raises(ValueError): 200 | OrthogonalizationParameters( 201 | orthogonalization_to_smoothing_factor_at_boundary="invalid" 202 | ) 203 | 204 | with pytest.raises(ValueError): 205 | OrthogonalizationParameters(areal_to_angle_smoothing_factor="invalid") 206 | 207 | 208 | def test_curvilinear_grid_implicit_string_conversions(): 209 | """Test implicit conversion from string to double for CurvilinearGrid works""" 210 | 211 | # Test valid input 212 | node_x = ["1.0", "2.0", "3.0"] 213 | node_y = ["4.0", "5.0", "6.0"] 214 | num_m = "3" 215 | num_n = "3" 216 | 217 | curvilinear_grid = CurvilinearGrid( 218 | node_x=node_x, node_y=node_y, num_m=num_m, num_n=num_n 219 | ) 220 | 221 | assert_array_equal( 222 | curvilinear_grid.node_x, np.array([1.0, 2.0, 3.0], dtype=np.double) 223 | ) 224 | assert_array_equal( 225 | curvilinear_grid.node_y, np.array([4.0, 5.0, 6.0], dtype=np.double) 226 | ) 227 | assert curvilinear_grid.num_m == 3 228 | assert curvilinear_grid.num_n == 3 229 | 230 | 231 | def test_curvilinear_grid_invalid_input(): 232 | """Test exceptions due to malformed input for CurvilinearGrid""" 233 | with pytest.raises(ValueError): 234 | CurvilinearGrid( 235 | node_x=["1.0", "invalid", "3.0"], 236 | node_y=["4.0", "5.0", "6.0"], 237 | num_m="3", 238 | num_n="3", 239 | ) 240 | 241 | with pytest.raises(ValueError): 242 | CurvilinearGrid( 243 | node_x=["1.0", "2.0", "3.0"], 244 | node_y=["4.0", "5.0", "6.0"], 245 | num_m="3", 246 | num_n="abc", 247 | ) 248 | 249 | 250 | def test_curvilinear_parameters_implicit_string_conversions(): 251 | """Test implicit conversion from string to double for CurvilinearParameters works""" 252 | # Test valid input 253 | curvilinear_parameters = CurvilinearParameters( 254 | m_refinement="2000", 255 | n_refinement="40", 256 | smoothing_iterations="10", 257 | smoothing_parameter="0.5", 258 | attraction_parameter="0.0", 259 | ) 260 | 261 | assert curvilinear_parameters.m_refinement == 2000 262 | assert curvilinear_parameters.n_refinement == 40 263 | assert curvilinear_parameters.smoothing_iterations == 10 264 | assert curvilinear_parameters.smoothing_parameter == 0.5 265 | assert curvilinear_parameters.attraction_parameter == 0.0 266 | 267 | 268 | def test_curvilinear_parameters_invalid_input(): 269 | """Test exceptions due to malformed input for CurvilinearParameters""" 270 | with pytest.raises(ValueError): 271 | CurvilinearParameters(m_refinement="abc") 272 | 273 | with pytest.raises(ValueError): 274 | CurvilinearParameters(n_refinement="2.5") 275 | 276 | with pytest.raises(ValueError): 277 | CurvilinearParameters(smoothing_iterations="xyz") 278 | 279 | with pytest.raises(ValueError): 280 | CurvilinearParameters(smoothing_parameter="invalid") 281 | 282 | 283 | def test_splines_to_curvilinear_parameters_implicit_conversions(): 284 | """Test implicit conversion from string to double for SplinesToCurvilinearParameters works""" 285 | 286 | splines_to_curvilinear_parameters = SplinesToCurvilinearParameters( 287 | aspect_ratio="0.1", 288 | aspect_ratio_grow_factor="1.1", 289 | average_width="500.0", 290 | curvature_adapted_grid_spacing="1", 291 | grow_grid_outside=0, 292 | maximum_num_faces_in_uniform_part="5", 293 | nodes_on_top_of_each_other_tolerance="0.0001", 294 | min_cosine_crossing_angles="0.95", 295 | check_front_collisions=0, 296 | remove_skinny_triangles=1, 297 | ) 298 | 299 | assert splines_to_curvilinear_parameters.aspect_ratio == 0.1 300 | assert splines_to_curvilinear_parameters.aspect_ratio_grow_factor == 1.1 301 | assert splines_to_curvilinear_parameters.average_width == 500.0 302 | assert splines_to_curvilinear_parameters.curvature_adapted_grid_spacing == 1 303 | assert not splines_to_curvilinear_parameters.grow_grid_outside 304 | assert splines_to_curvilinear_parameters.maximum_num_faces_in_uniform_part == 5 305 | assert ( 306 | splines_to_curvilinear_parameters.nodes_on_top_of_each_other_tolerance == 0.0001 307 | ) 308 | assert splines_to_curvilinear_parameters.min_cosine_crossing_angles == 0.95 309 | assert not splines_to_curvilinear_parameters.check_front_collisions 310 | assert splines_to_curvilinear_parameters.remove_skinny_triangles 311 | 312 | 313 | def test_splines_to_curvilinear_parameters_invalid_input(): 314 | """Test exceptions due to malformed input for SplinesToCurvilinearParameters""" 315 | with pytest.raises(ValueError): 316 | SplinesToCurvilinearParameters(aspect_ratio="abc") 317 | 318 | with pytest.raises(ValueError): 319 | SplinesToCurvilinearParameters(average_width="invalid") 320 | 321 | with pytest.raises(ValueError): 322 | SplinesToCurvilinearParameters(curvature_adapted_grid_spacing="2.5") 323 | 324 | with pytest.raises(ValueError): 325 | SplinesToCurvilinearParameters(nodes_on_top_of_each_other_tolerance="xyz") 326 | 327 | 328 | def test_mesh_refinement_implicit_string_conversions(): 329 | """Test implicit conversion from string to double for MeshRefinementParameters works""" 330 | 331 | # Test valid input 332 | refinement_parameters = MeshRefinementParameters( 333 | refine_intersected="1", 334 | use_mass_center_when_refining=False, 335 | min_edge_size="0.5", 336 | refinement_type=2, 337 | connect_hanging_nodes=False, 338 | account_for_samples_outside_face="1", 339 | max_refinement_iterations="10", 340 | smoothing_iterations="5", 341 | max_courant_time="120.0", 342 | directional_refinement=True, 343 | ) 344 | 345 | assert refinement_parameters.refine_intersected 346 | assert not refinement_parameters.use_mass_center_when_refining 347 | assert refinement_parameters.min_edge_size == 0.5 348 | assert refinement_parameters.refinement_type == RefinementType.REFINEMENT_LEVELS 349 | assert not refinement_parameters.connect_hanging_nodes 350 | assert refinement_parameters.account_for_samples_outside_face 351 | assert refinement_parameters.max_refinement_iterations == 10 352 | assert refinement_parameters.smoothing_iterations == 5 353 | assert refinement_parameters.max_courant_time == 120.0 354 | assert refinement_parameters.directional_refinement 355 | 356 | 357 | def test_mesh_refinement_parameters_invalid_input(): 358 | """Test exceptions due to malformed input for MeshRefinementParameters""" 359 | 360 | # Test exceptions for invalid inputs 361 | with pytest.raises(ValueError): 362 | MeshRefinementParameters(min_edge_size="abc") 363 | 364 | with pytest.raises(ValueError): 365 | MeshRefinementParameters(max_refinement_iterations="2.5") 366 | 367 | with pytest.raises(ValueError): 368 | MeshRefinementParameters(max_courant_time="xyz") 369 | 370 | 371 | def test_make_grid_parameters_implicit_string_conversions(): 372 | """Test implicit conversion from string to double for MakeGridParameters works""" 373 | 374 | # Test valid input 375 | make_grid_parameters = MakeGridParameters( 376 | num_columns="3", 377 | num_rows="3", 378 | angle="0.0", 379 | origin_x="0.0", 380 | origin_y="0.0", 381 | block_size_x="10.0", 382 | block_size_y="10.0", 383 | upper_right_x="0.0", 384 | upper_right_y="0.0", 385 | ) 386 | 387 | assert make_grid_parameters.num_columns == 3 388 | assert make_grid_parameters.num_rows == 3 389 | assert make_grid_parameters.angle == 0.0 390 | assert make_grid_parameters.origin_x == 0.0 391 | assert make_grid_parameters.origin_y == 0.0 392 | assert make_grid_parameters.block_size_x == 10.0 393 | assert make_grid_parameters.block_size_y == 10.0 394 | assert make_grid_parameters.upper_right_x == 0.0 395 | assert make_grid_parameters.upper_right_y == 0.0 396 | 397 | 398 | def test_cmake_grid_parameters_invalid_input(): 399 | """Test exceptions due to malformed input for MakeGridParameters""" 400 | 401 | with pytest.raises(ValueError): 402 | MakeGridParameters(num_columns="abc") 403 | 404 | with pytest.raises(ValueError): 405 | MakeGridParameters(origin_x="invalid") 406 | 407 | with pytest.raises(ValueError): 408 | MakeGridParameters(num_rows="2.5") 409 | 410 | 411 | def test_mesh1d_implicit_string_conversions(): 412 | """Test implicit conversion from string to double for Mesh1d works""" 413 | 414 | node_x = ["1.0", "2.0", "3.0"] 415 | node_y = ["4.0", "5.0", "6.0"] 416 | edge_nodes = ["0", "1", "1", "2"] 417 | 418 | mesh_1d = Mesh1d(node_x=node_x, node_y=node_y, edge_nodes=edge_nodes) 419 | 420 | assert np.array_equal(mesh_1d.node_x, np.asarray(node_x, dtype=np.double)) 421 | assert np.array_equal(mesh_1d.node_y, np.asarray(node_y, dtype=np.double)) 422 | assert np.array_equal(mesh_1d.edge_nodes, np.asarray(edge_nodes, dtype=np.int32)) 423 | 424 | 425 | def test_mesh1d_invalid_input(): 426 | """Test exceptions due to malformed input for Mesh1d""" 427 | 428 | # Test exceptions for invalid inputs 429 | with pytest.raises(ValueError): 430 | Mesh1d( 431 | node_x=["1.0", "abc", "3.0"], 432 | node_y=["4.0", "5.0", "6.0"], 433 | edge_nodes=["0", "1"], 434 | ) 435 | 436 | with pytest.raises(TypeError): 437 | Mesh1d(edge_nodes=["0", "1", "1", "2.5"]) 438 | 439 | 440 | def test_contacts_implicit_string_conversions(): 441 | """Test implicit conversion from string to double for Contacts works""" 442 | 443 | # Test valid input 444 | mesh1d_indices = ["0", "1", "2"] 445 | mesh2d_indices = ["0", "1", "2"] 446 | 447 | contacts = Contacts(mesh1d_indices=mesh1d_indices, mesh2d_indices=mesh2d_indices) 448 | 449 | assert np.array_equal( 450 | contacts.mesh1d_indices, np.asarray(mesh1d_indices, dtype=np.int32) 451 | ) 452 | assert np.array_equal( 453 | contacts.mesh2d_indices, np.asarray(mesh2d_indices, dtype=np.int32) 454 | ) 455 | 456 | 457 | def test_contacts_invalid_input(): 458 | """Test exceptions due to malformed input for Contacts""" 459 | with pytest.raises(ValueError): 460 | Contacts(mesh1d_indices=["0", "1", "abc"], mesh2d_indices=["0", "1", "2"]) 461 | 462 | with pytest.raises(ValueError): 463 | Contacts(mesh2d_indices=["0", "1", "2.5"], mesh1d_indices=["0", "1", "2"]) 464 | 465 | 466 | def test_gridded_parameters_implicit_string_conversions(): 467 | """Test implicit conversion from string to double for GriddedSamples works""" 468 | 469 | num_x = "3" 470 | num_y = "4" 471 | x_origin = "1.0" 472 | y_origin = "2.0" 473 | cell_size = "0.5" 474 | x_coordinates = ["1.0", "2.0", "3.0"] 475 | y_coordinates = ["2.0", "3.0", "4.0"] 476 | values = np.array( 477 | [ 478 | "10.0", 479 | "20.0", 480 | "30.0", 481 | "40.0", 482 | "50.0", 483 | "60.0", 484 | "70.0", 485 | "80.0", 486 | "90.0", 487 | "100.0", 488 | ] 489 | ) 490 | 491 | gridded_samples = GriddedSamples( 492 | num_x=num_x, 493 | num_y=num_y, 494 | x_origin=x_origin, 495 | y_origin=y_origin, 496 | cell_size=cell_size, 497 | x_coordinates=x_coordinates, 498 | y_coordinates=y_coordinates, 499 | values=values, 500 | ) 501 | 502 | assert gridded_samples.num_x == 3 503 | assert gridded_samples.num_y == 4 504 | assert gridded_samples.x_origin == 1.0 505 | assert gridded_samples.y_origin == 2.0 506 | assert gridded_samples.cell_size == 0.5 507 | assert np.array_equal( 508 | gridded_samples.x_coordinates, np.asarray(x_coordinates, dtype=np.double) 509 | ) 510 | assert np.array_equal( 511 | gridded_samples.y_coordinates, np.asarray(y_coordinates, dtype=np.double) 512 | ) 513 | assert np.array_equal(gridded_samples.values, np.asarray(values, dtype=np.double)) 514 | 515 | 516 | def test_gridded_parameters_invalid_input(): 517 | """Test exceptions due to malformed input for GriddedSamples""" 518 | 519 | with pytest.raises(ValueError): 520 | GriddedSamples(num_x="abc") 521 | 522 | with pytest.raises(ValueError): 523 | GriddedSamples(cell_size="invalid") 524 | 525 | with pytest.raises(ValueError): 526 | GriddedSamples(x_origin="invalid") 527 | -------------------------------------------------------------------------------- /tests/test_transformation_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | import transformation_utils 4 | from numpy.testing import assert_almost_equal, assert_array_equal 5 | 6 | 7 | def test_rotate_point(): 8 | """Tests `translate` applied to a single point""" 9 | point = [1.0, 2.0] 10 | origin = [0.0, 0.0] 11 | angle = -30.0 12 | point_rotated = transformation_utils.rotate(point, origin, angle) 13 | new_angle = np.deg2rad(angle) + np.arctan(point[1] / point[0]) 14 | norm = np.sqrt(point[0] * point[0] + point[1] * point[1]) 15 | assert_almost_equal(point_rotated[0], norm * np.cos(new_angle)) 16 | assert_almost_equal(point_rotated[1], norm * np.sin(new_angle)) 17 | 18 | 19 | def test_translate_point(): 20 | """Tests `translate` applied to a single point""" 21 | point = [1.0, 2.0] 22 | translation = [3.0, 4.0] 23 | point_translated = transformation_utils.translate(point, translation) 24 | assert point_translated[0] == point[0] + translation[0] 25 | assert point_translated[1] == point[1] + translation[1] 26 | 27 | 28 | def test_translate_points(): 29 | """Tests `translate` applied to several points""" 30 | point_x = np.array([0.0, 1.0, 1.0, 0.0], dtype=np.double) 31 | point_y = np.array([0.0, 0.0, 1.0, 1.0], dtype=np.double) 32 | translation = [3.0, 4.0] 33 | translated_point_x, translated_point_y = transformation_utils.translate( 34 | [point_x, point_y], translation 35 | ) 36 | assert_array_equal(point_x + translation[0], translated_point_x) 37 | assert_array_equal(point_y + translation[1], translated_point_y) 38 | -------------------------------------------------------------------------------- /tests/test_versions.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from meshkernel import MeshKernel 4 | from meshkernel.version import __backend_version__, __version__ 5 | 6 | 7 | def test_get_meshkernel_version(): 8 | """Tests if we can get the version of MeshKernel through the API""" 9 | mk = MeshKernel() 10 | meshkernel_version = mk.get_meshkernel_version() 11 | assert len(meshkernel_version) > 0 12 | 13 | 14 | def test_get_meshkernelpy_version(): 15 | """Tests if we can get the version of MeshKernelPy through the API""" 16 | mk = MeshKernel() 17 | meshkernelpy_version = mk.get_meshkernelpy_version() 18 | assert meshkernelpy_version == __version__ 19 | -------------------------------------------------------------------------------- /tests/transformation_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def rotate(point, origin, angle): 5 | """ 6 | Rotate a point by a given angle about a given origin. 7 | 8 | Args: 9 | point: The point to rotate 10 | origin: The point about which the point is to be rotated 11 | angle: The angle in degrees by which the point is to be rotated 12 | """ 13 | point_x, point_y = point 14 | origin_x, origin_y = origin 15 | angle_rad = np.deg2rad(angle) 16 | angle_cos = np.cos(angle_rad) 17 | angle_sin = np.sin(angle_rad) 18 | offset_x = point_x - origin_x 19 | offset_y = point_y - origin_y 20 | point_rot_x = origin_x + angle_cos * offset_x - angle_sin * offset_y 21 | point_rot_y = origin_y + angle_sin * offset_x + angle_cos * offset_y 22 | return point_rot_x, point_rot_y 23 | 24 | 25 | def translate(point, translation): 26 | """ 27 | Translate a point 28 | 29 | Args: 30 | point: The point to translate 31 | translation: The translation vector 32 | """ 33 | point_x, point_y = point 34 | translation_x, translation_y = translation 35 | return point_x + translation_x, point_y + translation_y 36 | --------------------------------------------------------------------------------