From cb17cf3e268773f5ca8bd24236706e01e7109555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20S=2E=20Dokken?= Date: Fri, 23 Aug 2024 11:19:41 +0200 Subject: [PATCH 1/5] Add some minor fixes to code --- .github/dependabot.yml | 11 ++++ pyproject.toml | 38 +++++--------- .../postprocessing_common.py | 7 ++- .../automated_preprocessing.py | 17 +++--- .../generate_solid_probe.py | 52 +++++++++++++------ .../preprocessing_common.py | 7 ++- tests/test_compute_hemodynamics.py | 2 +- 7 files changed, 78 insertions(+), 56 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0d08e261 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/pyproject.toml b/pyproject.toml index 0af2a14c..6cd9e835 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,10 +5,12 @@ requires = ["setuptools>=61.0.0", "wheel"] name = "vasp" version = "0.1.0" description = "Vascular Fluid Structure Interaction Pipline" -authors = [{name = "Kei Yamamoto", email = "keiya@simula.no"}, - {name = "Johannes Ring", email = "johannr@simula.no"}, - {name = "David A. Bruneau", email = "david.bruneau@mail.utoronto.ca"}, - {name = "Jørgen S. Dokken", email = "dokken@simula.no"}] +authors = [ + { name = "Kei Yamamoto", email = "keiya@simula.no" }, + { name = "Johannes Ring", email = "johannr@simula.no" }, + { name = "David A. Bruneau", email = "david.bruneau@mail.utoronto.ca" }, + { name = "Jørgen S. Dokken", email = "dokken@simula.no" }, +] license = { file = "LICENSE" } readme = "README.md" dependencies = [ @@ -33,17 +35,9 @@ vasp-create-spectrum = "vasp.automatedPostprocessing.postprocessing_h5py.create_ vasp-create-hi-pass-viz = "vasp.automatedPostprocessing.postprocessing_h5py.create_hi_pass_viz:main" [project.optional-dependencies] -test = [ - "flake8", - 'mypy', - "pytest", - "pytest-cov", - "pytest-xdist", -] +test = ["flake8", 'mypy', "pytest", "pytest-cov", "pytest-xdist"] -docs = [ - "jupyter-book" -] +docs = ["jupyter-book"] [tool.pytest.ini_options] addopts = [ @@ -51,21 +45,13 @@ addopts = [ "--cov=./", "--cov-report=xml", "--cov-report=term-missing", - "-v" -] -testpaths = [ - "tests" + "-v", ] +testpaths = ["tests"] [tool.mypy] ignore_missing_imports = true # Folders to exclude -exclude = [ - "docs/", - "build/", -] +exclude = ["docs/", "build/"] # Folder to check with mypy -files = [ - "src", - "tests" -] +files = ["src", "tests"] diff --git a/src/vasp/automatedPostprocessing/postprocessing_common.py b/src/vasp/automatedPostprocessing/postprocessing_common.py index 0a85736a..7032fede 100644 --- a/src/vasp/automatedPostprocessing/postprocessing_common.py +++ b/src/vasp/automatedPostprocessing/postprocessing_common.py @@ -12,9 +12,11 @@ import h5py -def get_domain_ids(mesh_path, fluid_domain_id, solid_domain_id): +def get_domain_ids(mesh_path:Path, fluid_domain_id:int, solid_domain_id:int)->Tuple[List[int], List[int], List[int]]: """ - Given a mesh file, this function returns the IDs of the fluid and solid domains + Given a mesh file, this function returns the IDs of the fluid and solid domains. + The IDs is a list of integers that correspond to the index of the coordinates (nodes) + in the mesh file. Args: mesh_path (Path): Path to the mesh file that contains the fluid and solid domains @@ -26,6 +28,7 @@ def get_domain_ids(mesh_path, fluid_domain_id, solid_domain_id): solid_ids (list): List of IDs of the solid domain all_ids (list): List of IDs of the whole mesh """ + assert mesh_path.exists() and mesh_path.is_file(), f"Mesh file {mesh_path} does not exist" with h5py.File(mesh_path) as vector_data: domains = vector_data['domains/values'][:] topology = vector_data['domains/topology'][:, :] diff --git a/src/vasp/automatedPreprocessing/automated_preprocessing.py b/src/vasp/automatedPreprocessing/automated_preprocessing.py index 8e9296e1..727a9e16 100755 --- a/src/vasp/automatedPreprocessing/automated_preprocessing.py +++ b/src/vasp/automatedPreprocessing/automated_preprocessing.py @@ -12,9 +12,9 @@ vtk_triangulate_surface, write_parameters, vmtk_cap_polydata, compute_centerlines, get_centerline_tolerance, \ get_vtk_point_locator, extract_single_line, vtk_merge_polydata, get_point_data_array, smooth_voronoi_diagram, \ create_new_surface, compute_centers, vmtk_smooth_surface, str2bool, vmtk_compute_voronoi_diagram, \ - prepare_output_surface, vmtk_compute_geometric_features + prepare_output_surface, vmtk_compute_geometric_features, read_polydata -from vampy.automatedPreprocessing.preprocessing_common import read_polydata, get_centers_for_meshing, \ +from vampy.automatedPreprocessing.preprocessing_common import get_centers_for_meshing, \ dist_sphere_diam, dist_sphere_curvature, dist_sphere_constant, get_regions_to_refine, add_flow_extension, \ write_mesh, mesh_alternative, find_boundaries, compute_flow_rate, setup_model_network, \ radiusArrayName, scale_surface, get_furtest_surface_point, check_if_closed_surface @@ -142,8 +142,7 @@ def run_pre_processing(input_model, verbose_print, smoothing_method, smoothing_f resampling_step *= scale_factor # Check if surface is closed and uncapps model if True - is_capped = check_if_closed_surface(surface) - if is_capped: + if check_if_closed_surface(surface): if not file_name_clipped_model.is_file(): print("--- Clipping the models inlets and outlets.\n") # Value of gradients_limit should be generally low, to detect flat surfaces corresponding @@ -157,7 +156,6 @@ def run_pre_processing(input_model, verbose_print, smoothing_method, smoothing_f # Get model parameters parameters = get_parameters(str(base_path)) - if "check_surface" not in parameters.keys(): surface = vtk_clean_polydata(surface) surface = vtk_triangulate_surface(surface) @@ -166,8 +164,7 @@ def run_pre_processing(input_model, verbose_print, smoothing_method, smoothing_f print_surface_info(surface) find_and_delete_nan_triangles(surface) surface = clean_surface(surface) - foundNaN = find_and_delete_nan_triangles(surface) - if foundNaN: + if find_and_delete_nan_triangles(surface): raise RuntimeError(("There is an issue with the surface. " "Nan coordinates or some other shenanigans.")) else: @@ -180,9 +177,9 @@ def run_pre_processing(input_model, verbose_print, smoothing_method, smoothing_f # Get centerlines print("--- Get centerlines\n") inlet, outlets = get_centers_for_meshing(surface, has_multiple_inlets, str(base_path)) - has_outlet = len(outlets) != 0 # Get point the furthest away from the inlet when only one boundary + has_outlet = len(outlets) != 0 if not has_outlet: outlets = get_furtest_surface_point(inlet, surface) @@ -212,7 +209,7 @@ def run_pre_processing(input_model, verbose_print, smoothing_method, smoothing_f # Extract the region centerline refine_region_centerline = [] info = get_parameters(str(base_path)) - number_of_regions = info["number_of_regions"] + number_of_regions = isave_pathnfo["number_of_regions"] # Compute mean distance between points for i in range(number_of_regions): @@ -308,6 +305,8 @@ def run_pre_processing(input_model, verbose_print, smoothing_method, smoothing_f elif smoothing_method == "no_smooth" or None: print("--- No smoothing of surface\n") + else: + raise ValueError(f"Unknown smoothing method: {smoothing_method}") # Add flow extensions if add_flow_extensions: diff --git a/src/vasp/automatedPreprocessing/generate_solid_probe.py b/src/vasp/automatedPreprocessing/generate_solid_probe.py index 2152edd7..0b8c7f10 100644 --- a/src/vasp/automatedPreprocessing/generate_solid_probe.py +++ b/src/vasp/automatedPreprocessing/generate_solid_probe.py @@ -14,30 +14,52 @@ def parse_arguments() -> argparse.Namespace: Returns: argparse.Namespace: Parsed command-line arguments. """ - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--mesh-path', type=Path, default=None, - help="Path to the mesh file") - parser.add_argument("--fsi-region", - type=float, - nargs="+", - default=None, - help="list of points defining the FSI region. x_min, x_max, y_min, y_max, z_min, z_max") + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--mesh-path", type=Path, default=None, help="Path to the mesh file" + ) + parser.add_argument( + "--fsi-region", + type=float, + nargs="+", + default=None, + help="list of points defining the FSI region. x_min, x_max, y_min, y_max, z_min, z_max", + ) return parser.parse_args() def generate_solid_probe(mesh_path: Path, fsi_region: list) -> None: + """ + Generate probes in the solid region of the FSI mesh. + The probes are stored in the same folder as the input mesh file, with the extensions `_solid_probe.csv` and `_solid_probe.json`. + + NOTE: + The solid region is defined by mesh domain ID 2, while the fluid region is defined by mesh domain ID 1. + :param mesh_path: Path to FSI mesh file (HDF5 format) + :param fsi_region: List of points defining the FSI region within the mesh, ordered (x_min, x_max, y_min, y_max, z_min, z_max) + """ fluid_domain_id = 1 solid_domain_id = 2 + assert ( + mesh_path.exists() and mesh_path.is_file() + ), f"Mesh file {mesh_path} does not exist" with h5py.File(mesh_path, "r") as mesh: - coords = mesh['mesh/coordinates'][:, :] + coords = mesh["mesh/coordinates"][:, :] - fluid_ids, solid_ids, all_ids = get_domain_ids(mesh_path, fluid_domain_id, solid_domain_id) + _, solid_ids, _ = get_domain_ids(mesh_path, fluid_domain_id, solid_domain_id) x_min, x_max, y_min, y_max, z_min, z_max = fsi_region - points_in_box = np.where((coords[:, 0] > x_min) & (coords[:, 0] < x_max) & - (coords[:, 1] > y_min) & (coords[:, 1] < y_max) & - (coords[:, 2] > z_min) & (coords[:, 2] < z_max))[0] + points_in_box = np.where( + (coords[:, 0] > x_min) + & (coords[:, 0] < x_max) + & (coords[:, 1] > y_min) + & (coords[:, 1] < y_max) + & (coords[:, 2] > z_min) + & (coords[:, 2] < z_max) + )[0] solid_probe_ids = np.intersect1d(points_in_box, solid_ids) # pick 50 points from the solid probe @@ -54,13 +76,11 @@ def generate_solid_probe(mesh_path: Path, fsi_region: list) -> None: json_file_name = mesh_path.stem + "_solid_probe.json" output_path_json = mesh_path.parent / json_file_name - with open(output_path_json, 'w') as f: + with open(output_path_json, "w") as f: json.dump(solid_probe_coords.tolist(), f) print(f"Solid probe saved to {output_path_json}") - return None - def main(): args = parse_arguments() diff --git a/src/vasp/automatedPreprocessing/preprocessing_common.py b/src/vasp/automatedPreprocessing/preprocessing_common.py index 4f071ea1..9bfa3372 100644 --- a/src/vasp/automatedPreprocessing/preprocessing_common.py +++ b/src/vasp/automatedPreprocessing/preprocessing_common.py @@ -2,17 +2,19 @@ # SPDX-License-Identifier: GPL-3.0-or-later from pathlib import Path -from typing import Union +from typing import Union, TYPE_CHECKING import numpy as np import meshio -from vtk import vtkPolyData from dolfin import Mesh, MeshFunction, File, HDF5File, FunctionSpace, Function, XDMFFile, cells, Edge from vmtk import vmtkdistancetospheres, vmtkdijkstradistancetopoints from morphman import vmtkscripts, write_polydata from vasp.automatedPreprocessing.vmtkmeshgeneratorfsi import vmtkMeshGeneratorFsi +if TYPE_CHECKING: + from vtk import vtkPolyData + # Global array names distanceToSpheresArrayName = "DistanceToSpheres" distanceToSpheresArrayNameSolid = "Thickness" @@ -24,6 +26,7 @@ def distance_to_spheres_solid_thickness(surface: vtkPolyData, save_path: Union[s min_distance: float = 0.25, max_distance: float = 0.3) -> vtkPolyData: """ Determines the solid thickness using vmtkdistancetospheres. + Write the distance data to `save_path`. Args: surface (vtkPolyData): Input surface model diff --git a/tests/test_compute_hemodynamics.py b/tests/test_compute_hemodynamics.py index d4d0526e..9cde7c2b 100644 --- a/tests/test_compute_hemodynamics.py +++ b/tests/test_compute_hemodynamics.py @@ -21,7 +21,7 @@ def test_compute_hemodynamics(tmpdir): Ref. https://en.wikipedia.org/wiki/Hagen–Poiseuille_equation https://github.com/keiyamamo/turtleFSI/blob/pipe/turtleFSI/problems/pipe_laminar.py """ - folder_path = Path("tests/test_data/hemodynamics_data") + folder_path = Path(__file__).parent / "test_data/hemodynamics_data" # Copy the data to temporary folder tmpdir_path = Path(tmpdir) From b72ae611d8d9326603faff98e95a3460335b051a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Mon, 26 Aug 2024 12:53:36 +0200 Subject: [PATCH 2/5] Apply suggestions from code review --- src/vasp/automatedPreprocessing/automated_preprocessing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vasp/automatedPreprocessing/automated_preprocessing.py b/src/vasp/automatedPreprocessing/automated_preprocessing.py index 727a9e16..dc2f1161 100755 --- a/src/vasp/automatedPreprocessing/automated_preprocessing.py +++ b/src/vasp/automatedPreprocessing/automated_preprocessing.py @@ -209,7 +209,7 @@ def run_pre_processing(input_model, verbose_print, smoothing_method, smoothing_f # Extract the region centerline refine_region_centerline = [] info = get_parameters(str(base_path)) - number_of_regions = isave_pathnfo["number_of_regions"] + number_of_regions = info["number_of_regions"] # Compute mean distance between points for i in range(number_of_regions): From fa592bacdc5ce96f63e1ea3e96adcb81203b23a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Mon, 26 Aug 2024 13:15:25 +0200 Subject: [PATCH 3/5] Revert type checking --- src/vasp/automatedPreprocessing/preprocessing_common.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vasp/automatedPreprocessing/preprocessing_common.py b/src/vasp/automatedPreprocessing/preprocessing_common.py index 9bfa3372..a003e158 100644 --- a/src/vasp/automatedPreprocessing/preprocessing_common.py +++ b/src/vasp/automatedPreprocessing/preprocessing_common.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from pathlib import Path -from typing import Union, TYPE_CHECKING +from typing import Union import numpy as np import meshio @@ -12,8 +12,7 @@ from vasp.automatedPreprocessing.vmtkmeshgeneratorfsi import vmtkMeshGeneratorFsi -if TYPE_CHECKING: - from vtk import vtkPolyData +from vtk import vtkPolyData # Global array names distanceToSpheresArrayName = "DistanceToSpheres" From c4556618c894fa90875b3d1f922bc2487f4554db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20S=2E=20Dokken?= Date: Mon, 26 Aug 2024 13:32:32 +0200 Subject: [PATCH 4/5] Flake8 --- src/vasp/automatedPostprocessing/postprocessing_common.py | 3 ++- src/vasp/automatedPreprocessing/generate_solid_probe.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vasp/automatedPostprocessing/postprocessing_common.py b/src/vasp/automatedPostprocessing/postprocessing_common.py index 7032fede..a7ffb4f0 100644 --- a/src/vasp/automatedPostprocessing/postprocessing_common.py +++ b/src/vasp/automatedPostprocessing/postprocessing_common.py @@ -12,7 +12,8 @@ import h5py -def get_domain_ids(mesh_path:Path, fluid_domain_id:int, solid_domain_id:int)->Tuple[List[int], List[int], List[int]]: +def get_domain_ids( + mesh_path: Path, fluid_domain_id: int, solid_domain_id: int) -> Tuple[List[int], List[int], List[int]]: """ Given a mesh file, this function returns the IDs of the fluid and solid domains. The IDs is a list of integers that correspond to the index of the coordinates (nodes) diff --git a/src/vasp/automatedPreprocessing/generate_solid_probe.py b/src/vasp/automatedPreprocessing/generate_solid_probe.py index 0b8c7f10..d4e3c308 100644 --- a/src/vasp/automatedPreprocessing/generate_solid_probe.py +++ b/src/vasp/automatedPreprocessing/generate_solid_probe.py @@ -33,13 +33,15 @@ def parse_arguments() -> argparse.Namespace: def generate_solid_probe(mesh_path: Path, fsi_region: list) -> None: """ Generate probes in the solid region of the FSI mesh. - The probes are stored in the same folder as the input mesh file, with the extensions `_solid_probe.csv` and `_solid_probe.json`. + The probes are stored in the same folder as the input mesh file, with the extensions + `_solid_probe.csv` and `_solid_probe.json`. NOTE: The solid region is defined by mesh domain ID 2, while the fluid region is defined by mesh domain ID 1. :param mesh_path: Path to FSI mesh file (HDF5 format) - :param fsi_region: List of points defining the FSI region within the mesh, ordered (x_min, x_max, y_min, y_max, z_min, z_max) + :param fsi_region: List of points defining the FSI region within the mesh, ordered + `(x_min, x_max, y_min, y_max, z_min, z_max)` """ fluid_domain_id = 1 solid_domain_id = 2 From ae04ff43141b615909d28248ea0d7b464dc8222b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20S=2E=20Dokken?= Date: Fri, 30 Aug 2024 13:33:08 +0200 Subject: [PATCH 5/5] Formatting --- src/vasp/automatedPostprocessing/postprocessing_common.py | 7 ++++++- .../postprocessing_h5py/postprocessing_common_h5py.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vasp/automatedPostprocessing/postprocessing_common.py b/src/vasp/automatedPostprocessing/postprocessing_common.py index a7ffb4f0..f2ae3ec4 100644 --- a/src/vasp/automatedPostprocessing/postprocessing_common.py +++ b/src/vasp/automatedPostprocessing/postprocessing_common.py @@ -9,11 +9,16 @@ from typing import Union, Optional, Dict, Tuple, List import numpy as np +import numpy.typing as npt import h5py def get_domain_ids( - mesh_path: Path, fluid_domain_id: int, solid_domain_id: int) -> Tuple[List[int], List[int], List[int]]: + mesh_path: Path, + fluid_domain_id: Union[int, List[int]], + solid_domain_id: Union[int, List[int]]) -> Tuple[npt.NDArray[np.integer], + npt.NDArray[np.integer], + npt.NDArray[np.integer]]: """ Given a mesh file, this function returns the IDs of the fluid and solid domains. The IDs is a list of integers that correspond to the index of the coordinates (nodes) diff --git a/src/vasp/automatedPostprocessing/postprocessing_h5py/postprocessing_common_h5py.py b/src/vasp/automatedPostprocessing/postprocessing_h5py/postprocessing_common_h5py.py index 01c3efb1..8ad2ddce 100755 --- a/src/vasp/automatedPostprocessing/postprocessing_h5py/postprocessing_common_h5py.py +++ b/src/vasp/automatedPostprocessing/postprocessing_h5py/postprocessing_common_h5py.py @@ -101,7 +101,7 @@ def get_interface_ids(mesh_path: Union[str, Path], fluid_domain_id: Union[int, l Returns: np.ndarray: Array containing the interface node IDs. """ - fluid_ids, solid_ids, _ = get_domain_ids(mesh_path, fluid_domain_id, solid_domain_id) + fluid_ids, solid_ids, _ = get_domain_ids(Path(mesh_path), fluid_domain_id, solid_domain_id) # Find the intersection of fluid and solid node ID's interface_ids_set = set(fluid_ids) & set(solid_ids) @@ -193,7 +193,7 @@ def create_transformed_matrix(input_path: Union[str, Path], output_folder: Union # Get node ID's from input mesh. If save_deg=2, you can supply the original mesh to get the data for the # corner nodes, or supply a refined mesh to get the data for all nodes (very computationally intensive) if quantity in {"d", "v", "p"}: - _, _, all_ids = get_domain_ids(mesh_path, fluid_domain_id, solid_domain_id) + _, _, all_ids = get_domain_ids(Path(mesh_path), fluid_domain_id, solid_domain_id) ids = all_ids # Get name of xdmf file