From 3c0aaf3b7f589f93b24100cdf1f3a350732dea03 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 18 Nov 2024 19:02:49 +0000 Subject: [PATCH 1/5] Update tree in README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e299171..19c3cec 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,9 @@ The documentation is generated by Sphinx. To build the documentation, under the ├── docs (Documentation) │ ├── conf.py │ └── index.rst +├── play +│ ├── play_conv_feat.py +│ └── play_dataset.py ├── script (Utility scripts) │ ├── make_dataset.sh (Script for making datasets of different sizes) │ ├── build_helmholtz_dataset.py (Build helmholtz dataset) @@ -159,9 +162,7 @@ The documentation is generated by Sphinx. To build the documentation, under the │ ├── plot.py │ ├── train_model.py │ └── ... -├── tests -│ ├── play_conv_feat.py -│ ├── play_dataset.py +├── test │ ├── test_import.py │ └── ... ├── install.sh (Installation script for UM2N and its dependencies) From d5c28d35109a5763cd4df175f871fb22a2e6545f Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 18 Nov 2024 19:03:00 +0000 Subject: [PATCH 2/5] Add root level Makefile --- Makefile | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a66b960 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +all: install + +.PHONY: install test + +install: + @echo "Installing UM2N..." + @python3 -m pip install -e . + @echo "Done." + @echo "Setting up pre-commit..." + @pre-commit install + @echo "Done." + +lint: + @echo "Checking lint..." + @ruff check + @echo "PASS" + +test: lint + @echo "Running all tests..." + @python3 -m pytest -v --durations=20 test + @echo "Done." + +coverage: + @echo "Generating coverage report..." + @python3 -m coverage erase + @python3 -m coverage run --source=UM2N -m pytest -v test + @python3 -m coverage html + @echo "Done." + +tree: + @tree -d . From 8c7df8c12b6e22d09606b100c74ac27e0972e638 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 18 Nov 2024 19:03:14 +0000 Subject: [PATCH 3/5] Rename test directory --- tests/dataset_integrity_check.ipynb | 228 ---------------------------- tests/test_import.py | 31 ---- tests/test_unstructured_mesh.py | 118 -------------- 3 files changed, 377 deletions(-) delete mode 100644 tests/dataset_integrity_check.ipynb delete mode 100644 tests/test_import.py delete mode 100644 tests/test_unstructured_mesh.py diff --git a/tests/dataset_integrity_check.ipynb b/tests/dataset_integrity_check.ipynb deleted file mode 100644 index 19af45d..0000000 --- a/tests/dataset_integrity_check.ipynb +++ /dev/null @@ -1,228 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "import warnings\n", - "\n", - "# import glob\n", - "import torch\n", - "from torch_geometric.utils import index_to_mask\n", - "\n", - "import UM2N\n", - "\n", - "warnings.filterwarnings('ignore')\n", - "device = torch.device('cuda' if torch.cuda.is_available()\n", - "else 'cpu')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def check_phi_attached(data_path):\n", - " data_set = UM2N.MeshDataset(data_path)\n", - " for i in range(len(data_set)):\n", - " try:\n", - " assert hasattr(data_set[i], 'phi')\n", - " except AttributeError:\n", - " raise ValueError(\"[NO PHI]: \", data_path)\n", - " return\n", - "\n", - "data_type = [\n", - " \"iso_full\",\n", - " \"iso_pad\",\n", - " \"aniso_full\",\n", - " \"aniso_pad\",\n", - "]\n", - "\n", - "subset_name = [\n", - " \"data\",\n", - "]\n", - "\n", - "\n", - "n_grid_start = 15\n", - "n_grid_end = 35\n", - "\n", - "data_type = [\n", - " \"iso_full\",\n", - " \"iso_pad\",\n", - " \"aniso_full\",\n", - " \"aniso_pad\",\n", - "]\n", - "\n", - "subset_name = [\n", - " \"data\",\n", - "]\n", - "\n", - "\n", - "# check for validation set\n", - "for n_grid in range(n_grid_start, n_grid_end + 1):\n", - " for data in data_type:\n", - " for subset in subset_name:\n", - " base_path = (\n", - " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", - " )\n", - " try:\n", - " check_phi_attached(base_path)\n", - " print(f\"suceess: check for {base_path}\")\n", - " except ValueError:\n", - " print(f\"failed: check for {base_path}\")\n", - " print()\n", - "\n", - "\n", - "n_grid_start = 15\n", - "n_grid_end = 20\n", - "\n", - "data_type = [\n", - " \"iso_pad\",\n", - " \"aniso_full\",\n", - "]\n", - "\n", - "subset_name = [\n", - " \"data\",\n", - "]\n", - "\n", - "# check for training set\n", - "for n_grid in range(n_grid_start, n_grid_end + 1, 5):\n", - " for data in data_type:\n", - " for subset in subset_name:\n", - " base_path = (\n", - " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", - " )\n", - " try:\n", - " check_phi_attached(base_path)\n", - " except ValueError:\n", - " print(f\"failed: check for {base_path}\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def check_neighbour(data_path, source_idx=3):\n", - " # file_pattern = os.path.join(data_path, 'data_*.npy')\n", - " # files = glob.glob(file_pattern)\n", - " # for file in files:\n", - " data_set = UM2N.MeshDataset(data_path)\n", - " for i in range(len(data_set)):\n", - " coords = data_set[i].x[:, :2]\n", - " num_nodes = coords.shape[0]\n", - " source_mask = index_to_mask(\n", - " torch.tensor([source_idx]), num_nodes\n", - " )\n", - " nei = UM2N.get_neighbors(source_mask, data_set[i].edge_index)\n", - " if (nei.sum() == 6):\n", - " pass\n", - " else:\n", - " raise ValueError(f\"In dataset {data_path} The number of neighbors is not 6\")\n", - " return\n", - "\n", - "\n", - "check_neighbour(\"/Users/cw1722/Documents/warpmesh/data/helmholtz/z=<0,1>_ndist=None_max_dist=6_<16x16>_n=100_iso_full/data\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "n_grid_start = 15\n", - "n_grid_end = 35\n", - "\n", - "data_type = [\n", - " \"iso_full\",\n", - " \"iso_pad\",\n", - " \"aniso_full\",\n", - " \"aniso_pad\",\n", - "]\n", - "\n", - "subset_name = [\n", - " \"data\",\n", - "]\n", - "\n", - "\n", - "# check for validation set\n", - "for n_grid in range(n_grid_start, n_grid_end + 1):\n", - " for data in data_type:\n", - " for subset in subset_name:\n", - " base_path = (\n", - " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", - " )\n", - " print(f\"passed: check for {base_path}\")\n", - " check_neighbour(base_path)\n", - "\n", - "\n", - "n_grid_start = 15\n", - "n_grid_end = 20\n", - "\n", - "data_type = [\n", - " \"iso_pad\",\n", - " \"aniso_full\",\n", - "]\n", - "\n", - "subset_name = [\n", - " \"data\",\n", - "]\n", - "\n", - "# check for training set\n", - "for n_grid in range(n_grid_start, n_grid_end + 1, 5):\n", - " for data in data_type:\n", - " for subset in subset_name:\n", - " base_path = (\n", - " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", - " )\n", - " print(f\"passed: check for {base_path}\")\n", - " check_neighbour(base_path)\n", - "\n", - "print(\"All checks passed!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/tests/test_import.py b/tests/test_import.py deleted file mode 100644 index a7e4c0d..0000000 --- a/tests/test_import.py +++ /dev/null @@ -1,31 +0,0 @@ -# Author: Chunyang Wang -# GitHub Username: acse-cw1722 - -from pytest import fixture - - -@fixture(scope="module") -def UM2N(): - import UM2N - - return UM2N - - -@fixture(scope="module") -def firedrake(): - import firedrake - - return firedrake - - -@fixture(scope="module") -def movement(): - import movement - - return movement - - -def test_import(UM2N, firedrake, movement): - assert UM2N - assert firedrake - assert movement diff --git a/tests/test_unstructured_mesh.py b/tests/test_unstructured_mesh.py deleted file mode 100644 index aed93b0..0000000 --- a/tests/test_unstructured_mesh.py +++ /dev/null @@ -1,118 +0,0 @@ -""" -Unit tests for the generate_mesh mesh generator module. -""" - -import os - -import numpy as np -import pytest -import ufl -from firedrake.assemble import assemble -from firedrake.bcs import DirichletBC -from firedrake.constant import Constant - -from UM2N.generator.unstructured_mesh import ( - UnstructuredRandomPolygonalMeshGenerator, - UnstructuredSquareMeshGenerator, -) - - -@pytest.fixture(params=[1, 2, 3, 4]) -def num_elem_bnd(request): - return request.param - - -@pytest.fixture(params=[1, 10, 0.2, np.pi]) -def scale(request): - return request.param - - -@pytest.fixture(params=[1, 2], ids=["delaunay", "frontal"]) -def mesh_algorithm(request): - return request.param - - -@pytest.fixture( - params=[ - UnstructuredRandomPolygonalMeshGenerator, - UnstructuredSquareMeshGenerator, - ] -) -def generator(request): - return request.param - - -def generate_mesh(generator, mesh_algorithm, scale=1.0, **kwargs): - """ - Utility mesh generator function for testing purposes. - """ - mesh_gen = generator(mesh_type=mesh_algorithm, scale=scale) - kwargs.setdefault("remove_file", True) - mesh = mesh_gen.generate_mesh(**kwargs) - mesh.init() - return mesh - - -def test_file_removal(): - """ - Test that the remove_file keyword argument works as expected. - """ - output_filename = "./tmp.msh" - assert not os.path.exists(output_filename) - generate_mesh( - UnstructuredSquareMeshGenerator, - 1, - res=1.0, - output_filename=output_filename, - remove_file=False, - ) - assert os.path.exists(output_filename) - os.remove(output_filename) - assert not os.path.exists(output_filename) - generate_mesh( - UnstructuredSquareMeshGenerator, 1, res=1.0, output_filename=output_filename - ) - assert not os.path.exists(output_filename) - - -def test_boundary_segments(generator): - """ - Check that the boundary segments are tagged with integers counting from 1. - """ - mesh = generate_mesh(generator, 1, res=1.0) - boundary_ids = mesh.exterior_facets.unique_markers - assert set(boundary_ids) == set(range(1, len(boundary_ids) + 1)) - - -def test_num_points_boundaries_square(num_elem_bnd, mesh_algorithm): - """ - Check that the numbers of points on each boundary segment of a unit square mesh are - as expected. - """ - mesh = generate_mesh(UnstructuredSquareMeshGenerator, 1, res=1.0 / num_elem_bnd) - boundary_ids = mesh.exterior_facets.unique_markers - for boundary_id in boundary_ids: - dbc = DirichletBC(mesh.coordinates.function_space(), 0, boundary_id) - assert len(dbc.nodes) == num_elem_bnd + 1 - - -def test_area_squaremesh(num_elem_bnd, mesh_algorithm, scale): - """ - Check that the area of a square mesh is equal to the scale factor squared. - """ - mesh = generate_mesh( - UnstructuredSquareMeshGenerator, 1, res=1.0 / num_elem_bnd, scale=scale - ) - assert np.isclose(assemble(Constant(1.0, domain=mesh) * ufl.dx), scale**2) - - -def test_num_cells_with_res_and_scale(generator, num_elem_bnd, mesh_algorithm): - """ - Check that doubling or halving the overall resolution doesn't affect the number of - cells for the square mesh, so long as the resolution is changed accordingly. - """ - generator = UnstructuredSquareMeshGenerator - mesh1 = generate_mesh(generator, mesh_algorithm, res=1.0 / num_elem_bnd) - mesh2 = generate_mesh(generator, mesh_algorithm, res=2.0 / num_elem_bnd, scale=2.0) - meshp5 = generate_mesh(generator, mesh_algorithm, res=0.5 / num_elem_bnd, scale=0.5) - assert np.allclose((mesh2.num_cells(), meshp5.num_cells()), mesh1.num_cells()) From eab819e4092845b8b4edc41f54647af7deb98d7b Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 18 Nov 2024 19:03:29 +0000 Subject: [PATCH 4/5] Use reusable test workflow --- .github/workflows/test_suite.yml | 75 +++------- test/dataset_integrity_check.ipynb | 228 +++++++++++++++++++++++++++++ test/test_import.py | 31 ++++ test/test_unstructured_mesh.py | 118 +++++++++++++++ 4 files changed, 398 insertions(+), 54 deletions(-) create mode 100644 test/dataset_integrity_check.ipynb create mode 100644 test/test_import.py create mode 100644 test/test_unstructured_mesh.py diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml index b6198e4..e484eea 100644 --- a/.github/workflows/test_suite.yml +++ b/.github/workflows/test_suite.yml @@ -1,63 +1,30 @@ -name: Install and test UM2N +name: 'Run UM2N Test Suite' on: + # Run test suite whenever main is updated push: branches: - main + # Run test suite whenever commits are pushed to an open PR pull_request: -jobs: - test-warpmesh: - name: Test UM2N - runs-on: ubuntu-latest - container: - image: firedrakeproject/firedrake:latest - options: --user root - steps: - - uses: actions/checkout@v3 - - - name: Cleanup - if: ${{ always() }} - run: | - cd .. - rm -rf build - - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Lint check - if: ${{ always() }} - run: | - . /home/firedrake/firedrake/bin/activate - python3 -m pip install ruff - ruff check - - - name: Install Movement - run: | - . /home/firedrake/firedrake/bin/activate - git clone https://github.com/mesh-adaptation/movement.git - cd movement - python3 -m pip install -e . + # Run test suite every Sunday at 1AM + schedule: + - cron: '0 1 * * 0' - - name: Install PyTorch - run: | - . /home/firedrake/firedrake/bin/activate - python3 -m pip install torch --index-url https://download.pytorch.org/whl/cpu - - - name: Install PyTorch3d - run: | - . /home/firedrake/firedrake/bin/activate - python3 -m pip install 'git+https://github.com/facebookresearch/pytorch3d.git' - - - name: Install UM2N - run: | - . /home/firedrake/firedrake/bin/activate - python3 -m pip install -e . - - - name: Run UM2N test suite - run: | - . /home/firedrake/firedrake/bin/activate - python3 -m pytest tests/test* -v +jobs: + test_suite: + uses: mesh-adaptation/mesh-adaptation-docs/.github/workflows/reusable_test_suite.yml@main + with: + install-command: 'python -m pip uninstall -y UM2N && python -m pip install -e .' + test-command: | + export GITHUB_ACTIONS_TEST_RUN=1 + python $(which firedrake-clean) + python -m coverage erase + python -m coverage run -a --source=UM2N -m pytest -v --durations=20 test + python -m coverage report + changed-files-patterns: | + **/*.py + **/*.msh + **/*.geo diff --git a/test/dataset_integrity_check.ipynb b/test/dataset_integrity_check.ipynb new file mode 100644 index 0000000..19af45d --- /dev/null +++ b/test/dataset_integrity_check.ipynb @@ -0,0 +1,228 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import warnings\n", + "\n", + "# import glob\n", + "import torch\n", + "from torch_geometric.utils import index_to_mask\n", + "\n", + "import UM2N\n", + "\n", + "warnings.filterwarnings('ignore')\n", + "device = torch.device('cuda' if torch.cuda.is_available()\n", + "else 'cpu')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def check_phi_attached(data_path):\n", + " data_set = UM2N.MeshDataset(data_path)\n", + " for i in range(len(data_set)):\n", + " try:\n", + " assert hasattr(data_set[i], 'phi')\n", + " except AttributeError:\n", + " raise ValueError(\"[NO PHI]: \", data_path)\n", + " return\n", + "\n", + "data_type = [\n", + " \"iso_full\",\n", + " \"iso_pad\",\n", + " \"aniso_full\",\n", + " \"aniso_pad\",\n", + "]\n", + "\n", + "subset_name = [\n", + " \"data\",\n", + "]\n", + "\n", + "\n", + "n_grid_start = 15\n", + "n_grid_end = 35\n", + "\n", + "data_type = [\n", + " \"iso_full\",\n", + " \"iso_pad\",\n", + " \"aniso_full\",\n", + " \"aniso_pad\",\n", + "]\n", + "\n", + "subset_name = [\n", + " \"data\",\n", + "]\n", + "\n", + "\n", + "# check for validation set\n", + "for n_grid in range(n_grid_start, n_grid_end + 1):\n", + " for data in data_type:\n", + " for subset in subset_name:\n", + " base_path = (\n", + " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", + " )\n", + " try:\n", + " check_phi_attached(base_path)\n", + " print(f\"suceess: check for {base_path}\")\n", + " except ValueError:\n", + " print(f\"failed: check for {base_path}\")\n", + " print()\n", + "\n", + "\n", + "n_grid_start = 15\n", + "n_grid_end = 20\n", + "\n", + "data_type = [\n", + " \"iso_pad\",\n", + " \"aniso_full\",\n", + "]\n", + "\n", + "subset_name = [\n", + " \"data\",\n", + "]\n", + "\n", + "# check for training set\n", + "for n_grid in range(n_grid_start, n_grid_end + 1, 5):\n", + " for data in data_type:\n", + " for subset in subset_name:\n", + " base_path = (\n", + " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", + " )\n", + " try:\n", + " check_phi_attached(base_path)\n", + " except ValueError:\n", + " print(f\"failed: check for {base_path}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def check_neighbour(data_path, source_idx=3):\n", + " # file_pattern = os.path.join(data_path, 'data_*.npy')\n", + " # files = glob.glob(file_pattern)\n", + " # for file in files:\n", + " data_set = UM2N.MeshDataset(data_path)\n", + " for i in range(len(data_set)):\n", + " coords = data_set[i].x[:, :2]\n", + " num_nodes = coords.shape[0]\n", + " source_mask = index_to_mask(\n", + " torch.tensor([source_idx]), num_nodes\n", + " )\n", + " nei = UM2N.get_neighbors(source_mask, data_set[i].edge_index)\n", + " if (nei.sum() == 6):\n", + " pass\n", + " else:\n", + " raise ValueError(f\"In dataset {data_path} The number of neighbors is not 6\")\n", + " return\n", + "\n", + "\n", + "check_neighbour(\"/Users/cw1722/Documents/warpmesh/data/helmholtz/z=<0,1>_ndist=None_max_dist=6_<16x16>_n=100_iso_full/data\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_grid_start = 15\n", + "n_grid_end = 35\n", + "\n", + "data_type = [\n", + " \"iso_full\",\n", + " \"iso_pad\",\n", + " \"aniso_full\",\n", + " \"aniso_pad\",\n", + "]\n", + "\n", + "subset_name = [\n", + " \"data\",\n", + "]\n", + "\n", + "\n", + "# check for validation set\n", + "for n_grid in range(n_grid_start, n_grid_end + 1):\n", + " for data in data_type:\n", + " for subset in subset_name:\n", + " base_path = (\n", + " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", + " )\n", + " print(f\"passed: check for {base_path}\")\n", + " check_neighbour(base_path)\n", + "\n", + "\n", + "n_grid_start = 15\n", + "n_grid_end = 20\n", + "\n", + "data_type = [\n", + " \"iso_pad\",\n", + " \"aniso_full\",\n", + "]\n", + "\n", + "subset_name = [\n", + " \"data\",\n", + "]\n", + "\n", + "# check for training set\n", + "for n_grid in range(n_grid_start, n_grid_end + 1, 5):\n", + " for data in data_type:\n", + " for subset in subset_name:\n", + " base_path = (\n", + " f\"/Users/cw1722/Documents/warpmesh/data/dataset/helmholtz/z=<0,1>_ndist=None_max_dist=6_<{n_grid}x{n_grid}>_n=100_{data}/{subset}\"\n", + " )\n", + " print(f\"passed: check for {base_path}\")\n", + " check_neighbour(base_path)\n", + "\n", + "print(\"All checks passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/test/test_import.py b/test/test_import.py new file mode 100644 index 0000000..a7e4c0d --- /dev/null +++ b/test/test_import.py @@ -0,0 +1,31 @@ +# Author: Chunyang Wang +# GitHub Username: acse-cw1722 + +from pytest import fixture + + +@fixture(scope="module") +def UM2N(): + import UM2N + + return UM2N + + +@fixture(scope="module") +def firedrake(): + import firedrake + + return firedrake + + +@fixture(scope="module") +def movement(): + import movement + + return movement + + +def test_import(UM2N, firedrake, movement): + assert UM2N + assert firedrake + assert movement diff --git a/test/test_unstructured_mesh.py b/test/test_unstructured_mesh.py new file mode 100644 index 0000000..aed93b0 --- /dev/null +++ b/test/test_unstructured_mesh.py @@ -0,0 +1,118 @@ +""" +Unit tests for the generate_mesh mesh generator module. +""" + +import os + +import numpy as np +import pytest +import ufl +from firedrake.assemble import assemble +from firedrake.bcs import DirichletBC +from firedrake.constant import Constant + +from UM2N.generator.unstructured_mesh import ( + UnstructuredRandomPolygonalMeshGenerator, + UnstructuredSquareMeshGenerator, +) + + +@pytest.fixture(params=[1, 2, 3, 4]) +def num_elem_bnd(request): + return request.param + + +@pytest.fixture(params=[1, 10, 0.2, np.pi]) +def scale(request): + return request.param + + +@pytest.fixture(params=[1, 2], ids=["delaunay", "frontal"]) +def mesh_algorithm(request): + return request.param + + +@pytest.fixture( + params=[ + UnstructuredRandomPolygonalMeshGenerator, + UnstructuredSquareMeshGenerator, + ] +) +def generator(request): + return request.param + + +def generate_mesh(generator, mesh_algorithm, scale=1.0, **kwargs): + """ + Utility mesh generator function for testing purposes. + """ + mesh_gen = generator(mesh_type=mesh_algorithm, scale=scale) + kwargs.setdefault("remove_file", True) + mesh = mesh_gen.generate_mesh(**kwargs) + mesh.init() + return mesh + + +def test_file_removal(): + """ + Test that the remove_file keyword argument works as expected. + """ + output_filename = "./tmp.msh" + assert not os.path.exists(output_filename) + generate_mesh( + UnstructuredSquareMeshGenerator, + 1, + res=1.0, + output_filename=output_filename, + remove_file=False, + ) + assert os.path.exists(output_filename) + os.remove(output_filename) + assert not os.path.exists(output_filename) + generate_mesh( + UnstructuredSquareMeshGenerator, 1, res=1.0, output_filename=output_filename + ) + assert not os.path.exists(output_filename) + + +def test_boundary_segments(generator): + """ + Check that the boundary segments are tagged with integers counting from 1. + """ + mesh = generate_mesh(generator, 1, res=1.0) + boundary_ids = mesh.exterior_facets.unique_markers + assert set(boundary_ids) == set(range(1, len(boundary_ids) + 1)) + + +def test_num_points_boundaries_square(num_elem_bnd, mesh_algorithm): + """ + Check that the numbers of points on each boundary segment of a unit square mesh are + as expected. + """ + mesh = generate_mesh(UnstructuredSquareMeshGenerator, 1, res=1.0 / num_elem_bnd) + boundary_ids = mesh.exterior_facets.unique_markers + for boundary_id in boundary_ids: + dbc = DirichletBC(mesh.coordinates.function_space(), 0, boundary_id) + assert len(dbc.nodes) == num_elem_bnd + 1 + + +def test_area_squaremesh(num_elem_bnd, mesh_algorithm, scale): + """ + Check that the area of a square mesh is equal to the scale factor squared. + """ + mesh = generate_mesh( + UnstructuredSquareMeshGenerator, 1, res=1.0 / num_elem_bnd, scale=scale + ) + assert np.isclose(assemble(Constant(1.0, domain=mesh) * ufl.dx), scale**2) + + +def test_num_cells_with_res_and_scale(generator, num_elem_bnd, mesh_algorithm): + """ + Check that doubling or halving the overall resolution doesn't affect the number of + cells for the square mesh, so long as the resolution is changed accordingly. + """ + generator = UnstructuredSquareMeshGenerator + mesh1 = generate_mesh(generator, mesh_algorithm, res=1.0 / num_elem_bnd) + mesh2 = generate_mesh(generator, mesh_algorithm, res=2.0 / num_elem_bnd, scale=2.0) + meshp5 = generate_mesh(generator, mesh_algorithm, res=0.5 / num_elem_bnd, scale=0.5) + assert np.allclose((mesh2.num_cells(), meshp5.num_cells()), mesh1.num_cells()) From f8af39636507e52dfc8f29303637e1030139b6d9 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Sat, 23 Nov 2024 12:36:10 +0000 Subject: [PATCH 5/5] Pass docker-image arg to test suite --- .github/workflows/test_suite.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml index e484eea..d4a38a4 100644 --- a/.github/workflows/test_suite.yml +++ b/.github/workflows/test_suite.yml @@ -28,3 +28,4 @@ jobs: **/*.py **/*.msh **/*.geo + docker-image: firedrake-um2n