diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c1626ccd7..70dc0b87b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.7 + rev: v0.11.2 hooks: - id: ruff - id: ruff-format @@ -12,21 +12,6 @@ repos: exclude: ^(examples/dyna_decks\/|tests/heart/assets\/) args: [-L solf, "--ignore-words", "doc/styles/config/vocabularies/ANSYS/accept.txt", "-w"] -- repo: https://github.com/pycqa/pydocstyle - rev: 6.3.0 - hooks: - - id: pydocstyle - additional_dependencies: [toml] - args: ["--convention=numpy"] - exclude: | - (?x)^( - tests/| - examples/| - src/ansys/heart/misc/| - src/ansys/heart/writer/custom_keywords/| - .*__init__.py - ) - - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: @@ -37,12 +22,12 @@ repos: exclude: ^(tests/heart/assets\/) - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.31.2 + rev: 0.32.1 hooks: - id: check-github-workflows - repo: https://github.com/ansys/pre-commit-hooks - rev: v0.5.1 + rev: v0.5.2 hooks: - id: add-license-headers files: '(ansys|examples|tests)/.*\.(py)' diff --git a/doc/source/changelog/972.fixed.md b/doc/source/changelog/972.fixed.md new file mode 100644 index 000000000..0571309ba --- /dev/null +++ b/doc/source/changelog/972.fixed.md @@ -0,0 +1 @@ +add todos and docstring \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3aa2e20ba..251694621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi" # Check https://flit.readthedocs.io/en/latest/pyproject_toml.html for all available sections name = "pyansys-heart" version = "0.11.dev0" -description = "Python framework for heart modeling using ANSYS tools." +description = "Python framework for heart modeling using Ansys tools." readme = "README.rst" requires-python = ">=3.10,<4" license = { file = "LICENSE" } @@ -21,6 +21,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ @@ -93,6 +94,8 @@ select = [ "I", # isort, see https://docs.astral.sh/ruff/rules/#isort-i "N", # pep8-naming, see https://docs.astral.sh/ruff/rules/#pep8-naming-n "TD", # flake8-todos, https://docs.astral.sh/ruff/rules/#flake8-todos-td + # TODO: convert the path to pathlib in all modules + #"PTH", # flake8-use-pathlib, https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth ] ignore = [ "TD002", # Missing author in TODOs comment diff --git a/src/ansys/heart/core/utils/connectivity.py b/src/ansys/heart/core/utils/connectivity.py index af338ba8a..ca8766d3c 100644 --- a/src/ansys/heart/core/utils/connectivity.py +++ b/src/ansys/heart/core/utils/connectivity.py @@ -255,6 +255,7 @@ def _dfs(visited, graph, node): for edge_group in edge_groups: counts = np.unique(edge_group, return_counts=True)[1] if np.all(counts == 2): + # TODO: @mhoeijm - remove all print statements # print("Closed edge loop") group_types.append("closed") elif np.any(counts != 2): diff --git a/src/ansys/heart/postprocessor/auto_process.py b/src/ansys/heart/postprocessor/auto_process.py index 1c46739ae..3b0650b6f 100644 --- a/src/ansys/heart/postprocessor/auto_process.py +++ b/src/ansys/heart/postprocessor/auto_process.py @@ -45,16 +45,15 @@ def zerop_post(directory: str, model: HeartModel) -> tuple[dict, np.ndarray, np. Parameters ---------- directory : str - Path to simulation folder + Path to simulation folder. model : HeartModel - model to post-process + model to post-process. Returns ------- tuple[dict, np.ndarray, np.ndarray] - dictionary with convergence information - stress free configuration - computed end-of-diastolic configuration + dictionary with convergence information, stress free configuration, + computed end-of-diastolic configuration. """ folder = "post" os.makedirs(os.path.join(directory, folder), exist_ok=True) @@ -149,9 +148,9 @@ def mech_post(directory: str, model: HeartModel): Parameters ---------- directory : str - d3plot folder + d3plot folder. model : HeartModel - heart model + heart model. """ last_cycle_duration = 800 folder = "post" diff --git a/src/ansys/heart/postprocessor/dpf_utils.py b/src/ansys/heart/postprocessor/dpf_utils.py index efa05028d..867e0b514 100644 --- a/src/ansys/heart/postprocessor/dpf_utils.py +++ b/src/ansys/heart/postprocessor/dpf_utils.py @@ -105,7 +105,18 @@ def get_initial_coordinates(self): return self.model.results.initial_coordinates.eval()[0].data def get_ep_fields(self, at_step: int = None) -> dpf.FieldsContainer: - """Get EP fields container.""" + """Get EP fields container. + + Parameters + ---------- + at_step : int, optional + At this step, by default None. + + Returns + ------- + dpf.FieldsContainer + Fields container. + """ fields = dpf.FieldsContainer() time_ids = ( @@ -300,7 +311,7 @@ def get_time(self) -> np.ndarray: Returns ------- np.ndarray - time array + time array. """ # see pydpf examples, lsdyna-operators icvout_op = dpf.Operator("lsdyna::binout::ICV_P") @@ -318,12 +329,12 @@ def get_pressure(self, icv_id: int) -> np.ndarray: Parameters ---------- icv_id : int - control volume id + control volume id. Returns ------- np.ndarray - pressure array + pressure array. """ if icv_id not in self._icv_ids: raise ValueError("icv_id not found.") @@ -336,12 +347,12 @@ def get_volume(self, icv_id: int) -> np.ndarray: Parameters ---------- icv_id : int - control volume id + control volume id. Returns ------- np.ndarray - volume array + volume array. """ if icv_id not in self._icv_ids: raise ValueError("icv_id not found.") @@ -359,12 +370,12 @@ def get_flowrate(self, icvi_id: int) -> np.ndarray: Parameters ---------- icvi_id : int - control volume interaction id + control volume interaction id. Returns ------- np.ndarray - flowrate array + flowrate array. """ if icvi_id not in self._icvi_ids: raise ValueError("icvi_id not found.") @@ -588,15 +599,15 @@ def compute_12_lead_ECGs( # noqa: N802 ECGs : np.ndarray mxn array containing ECGs, where m is the number of time steps and n the 10 electrodes in this order: - "V1" "V2" "V3" "V4" "V5" "V6" "RA" "LA" "RL" "LL" + "V1" "V2" "V3" "V4" "V5" "V6" "RA" "LA" "RL" "LL". plot : bool, optional - plot option, by default True + plot option, by default True. Returns ------- np.ndarray 12-Lead ECGs in this order: - "I" "II" "III" "aVR" "aVL" "aVF" "V1" "V2" "V3" "V4" "V5" "V6" + "I" "II" "III" "aVR" "aVL" "aVF" "V1" "V2" "V3" "V4" "V5" "V6". """ right_arm = ECGs[:, 6] left_arm = ECGs[:, 7] @@ -713,12 +724,12 @@ def convert_to_pvgrid_at_t(self, time: float, fname: str = None) -> pv.Unstructu time : float time to convert fname : str - filename to be save save data, default is None + filename to be save save data, default is None. Returns ------- pv.UnstructuredGrid - result in pyvista object + result in pyvista object. """ mesh = self.data.meshgrid.copy() i_frame = np.where(self.data.time == time)[0][0] diff --git a/src/ansys/heart/postprocessor/klotz_curve.py b/src/ansys/heart/postprocessor/klotz_curve.py index c687f2a15..4bd4f9ab5 100644 --- a/src/ansys/heart/postprocessor/klotz_curve.py +++ b/src/ansys/heart/postprocessor/klotz_curve.py @@ -45,9 +45,9 @@ def __init__(self, vm: float, pm: float): Parameters ---------- vm : float - Volume in mL + Volume in mL. pm : float - Pressure in mmHg + Pressure in mmHg. """ self.vm = vm self.pm = pm @@ -78,7 +78,7 @@ def get_pressure(self, volume: float | np.ndarray) -> float | np.ndarray: Returns ------- float| np.ndarray - pressure in mmHg + pressure in mmHg. """ return self.Alpha * volume**self.Beta @@ -88,12 +88,12 @@ def get_volume(self, pressure: np.ndarray) -> np.ndarray: Parameters ---------- pressure : np.ndarray - pressure in mmHg + pressure in mmHg. Returns ------- np.ndarray - volume in mmL + volume in mmL. """ if not isinstance(pressure, np.ndarray): raise TypeError("Input must be 1-dimensioanl np.array.") @@ -111,12 +111,12 @@ def plot_EDPVR(self, simulation_data: list = None) -> matplotlib.figure.Figure: Parameters ---------- simulation_data : list, optional - [volume, pressure] from simulation, by default None + [volume, pressure] from simulation, by default None. Returns ------- matplotlib.figure.Figure - figure + figure. """ vv = np.linspace(0, 1.1 * self.vm, num=101) pp = self.get_pressure(vv) diff --git a/src/ansys/heart/postprocessor/laplace_post.py b/src/ansys/heart/postprocessor/laplace_post.py index c7e036d06..2797e9822 100644 --- a/src/ansys/heart/postprocessor/laplace_post.py +++ b/src/ansys/heart/postprocessor/laplace_post.py @@ -41,14 +41,14 @@ def read_laplace_solution( Parameters ---------- directory : str - directory of d3plot files + directory of d3plot files. field_list : list[str] - name of each d3plot file/field + name of each d3plot file/field. Returns ------- pv.UnstructuredGrid - grid with point data of each field + grid with point data of each field. """ data = D3plotReader(os.path.join(directory, field_list[0] + ".d3plot")) grid: pv.UnstructuredGrid = data.model.metadata.meshed_region.grid @@ -84,19 +84,19 @@ def update_transmural_by_normal(grid: pv.UnstructuredGrid, surface: pv.PolyData) Note ---- Assume mesh is coarse compared to the thinkness, solid cell normal - is interpolated from closest surface normal + is interpolated from closest surface normal. Parameters ---------- - grid : pv.UnstructuredGrid + grid : pv.UnstructuredGrid. atrium grid surface : pv.PolyData - atrium endocardium surface + atrium endocardium surface. Returns ------- np.ndarray - cell transmural direction vector + cell transmural direction vector. """ surface_normals = surface.clean().compute_normals() @@ -120,14 +120,14 @@ def orthogonalization( Parameters ---------- grad_trans : np.ndarray - transmural vector + transmural vector. k : np.ndarray - Bundle selection vector + Bundle selection vector. Returns ------- tuple[np.ndarray, np.ndarray, np.ndarray] - local coordinate system e_l,e_n,e_t + local coordinate system e_l,e_n,e_t. """ norm = np.linalg.norm(grad_trans, axis=1) bad_cells = np.argwhere(norm == 0).ravel() @@ -241,7 +241,7 @@ def compute_ra_fiber_cs( settings : AtrialFiber Atrial fiber settings. endo_surface : pv.PolyData, optional - _description_, by default None + _description_, by default None. If given, normal direction will be updated by surface normal instead of Laplace solution. Notes @@ -376,18 +376,18 @@ def set_rotation_bounds( Parameters ---------- w : np.ndarray - intra-ventricular interpolation weight if outflow_tracts is not None + intra-ventricular interpolation weight if outflow_tracts is not None. endo : float - rotation angle at endocardium + rotation angle at endocardium. epi : float - rotation angle at epicardium + rotation angle at epicardium. outflow_tracts : list[float, float], optional - rotation angle of enendocardium do and epicardium on outflow tract, by default None + rotation angle of enendocardium do and epicardium on outflow tract, by default None. Returns ------- tuple[np.ndarray, np.ndarray] - cell-wise rotation bounds for endocardium and epicardium + cell-wise rotation bounds for endocardium and epicardium. """ def _sigmoid(z): diff --git a/src/ansys/heart/preprocessor/conduction_beam.py b/src/ansys/heart/preprocessor/conduction_beam.py index 9f42fba2f..2fd293c89 100644 --- a/src/ansys/heart/preprocessor/conduction_beam.py +++ b/src/ansys/heart/preprocessor/conduction_beam.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Module containing class for creating conduxtion system.""" +"""Module containing class for creating conduction system.""" import networkx as nx import numpy as np @@ -73,6 +73,7 @@ class ConductionSystem: """Methods to generate conduction system.""" def __init__(self, model: FourChamber): + """Initialize the conduction system.""" self.m = model def compute_sa_node(self, target_coord=None) -> Point: @@ -81,6 +82,16 @@ def compute_sa_node(self, target_coord=None) -> Point: SinoAtrial node is defined on the endocardium of the right atrium and between sup vena cava and inf vena cave. + + Parameters + ---------- + target_coord : np.array, optional + Target coordinate to define the SA node, by default None. + + Returns + ------- + Point + returns the SA node. """ if target_coord is None: sup_vcava_centroid = next( @@ -120,6 +131,11 @@ def compute_av_node(self, target_coord=None) -> Point: AtrioVentricular node is on right artrium endocardium surface and closest to septum. + Parameters + ---------- + target_coord : np.array, optional + Target coordinate to define the AV node, by default None. + Returns ------- Point @@ -152,7 +168,18 @@ def compute_av_node(self, target_coord=None) -> Point: return atrioventricular_point def compute_av_conduction(self, beam_length: float = 1.5) -> BeamMesh: - """Compute Atrio-Ventricular conduction by means of beams following a geodesic path.""" + """Compute Atrio-Ventricular conduction by means of beams following a geodesic path. + + Parameters + ---------- + beam_length : float, optional + Beam length, by default 1.5. + + Returns + ------- + BeamMesh + Returns the beam network. + """ right_atrium_endo = self.m.mesh.get_surface(self.m.right_atrium.endocardium.id) try: @@ -227,7 +254,18 @@ def _get_hisbundle_bifurcation(self): return bifurcation_coord def compute_his_conduction(self, beam_length: float = 1.5) -> tuple[BeamMesh, Point, Point]: - """Compute His bundle conduction.""" + """Compute His bundle conduction. + + Parameters + ---------- + beam_length : float, optional + Beam length, by default 1.5. + + Returns + ------- + tuple[BeamMesh, Point, Point] + Returns the beam network, left and right His bundle end points. + """ bifurcation_coord = self._get_hisbundle_bifurcation() # path start from AV point, to septum start point, then to septum end point @@ -346,9 +384,14 @@ def find_path( start : np.ndarray Start point coordinates. end : np.ndarray - End point coordinates + End point coordinates. return_segment : bool, optional - Return a segment set (list of triangles) on which the path relies, by default True + Return a segment set (list of triangles) on which the path relies, by default True. + + Returns + ------- + np.ndarray + Returns the path as a list of node ids. """ #! mesh can now have multiple element types: TETRA, TRIANGLE, etc. mesh = mesh.extract_cells_by_type(pv.CellType.TETRA) @@ -416,7 +459,23 @@ def _mesh_to_nx_graph(mesh): def _create_his_side( self, side: str, new_nodes, edges, beam_length, bifurcation_coord, bifurcation_id ): - """Create His side after bifucation.""" + """Create His side after bifucation. + + Parameters + ---------- + side : str + Side of the heart, either "left" or "right". + new_nodes : np.array + New nodes. + edges : np.array + Edges. + beam_length : float + Beam length. + bifurcation_coord : np.array + Bifurcation coordinate. + bifurcation_id : int + Bifurcation node id. + """ if side.lower() == "left": endo = self.m.mesh.get_surface(self.m.left_ventricle.endocardium.id) elif side.lower() == "right": @@ -453,7 +512,19 @@ def _create_his_side( return (position_id_his_end, his_end_coord, new_nodes, edges, sgmt) def compute_left_right_bundle(self, start_coord, start_id, side: str, beam_length: float = 1.5): - """Bundle branch.""" + """Bundle branch. + + Parameters + ---------- + start_coord : np.array + Start coordinate. + start_id : int + Start node id. + side : str + Side of the heart, either "Left" or "Right". + beam_length : float, optional + Beam length, by default 1.5. + """ if side == "Left": ventricle = self.m.left_ventricle endo_surface = self.m.mesh.get_surface(self.m.left_ventricle.endocardium.id) @@ -500,7 +571,17 @@ def _get_closest_point_id(surface: pv.PolyData, point): return surface.get_cell(cell_id).point_ids[0] def compute_bachman_bundle(self, start_coord, end_coord, beam_length: float = 1.5) -> BeamMesh: - """Compute Bachman bundle conduction system.""" + """Compute Bachman bundle conduction system. + + Parameters + ---------- + start_coord : np.array + Start coordinate. + end_coord : np.array + End coordinate. + beam_length : float, optional + Beam length, by default 1.5. + """ la_epi = self.m.mesh.get_surface(self.m.left_atrium.epicardium.id) ra_epi = self.m.mesh.get_surface(self.m.right_atrium.epicardium.id) epi = la_epi.merge(ra_epi) diff --git a/src/ansys/heart/preprocessor/database_utils.py b/src/ansys/heart/preprocessor/database_utils.py index 7e39eb7e3..646865981 100644 --- a/src/ansys/heart/preprocessor/database_utils.py +++ b/src/ansys/heart/preprocessor/database_utils.py @@ -106,9 +106,18 @@ def _get_interface_surfaces(mesh: pv.UnstructuredGrid, labels: dict, tag_to_labe Parameters ---------- mesh : pv.UnstructuredGrid - Volume mesh + Volume mesh. labels : dict - Label dict to which to add the interface labels + Label dict to which to add the interface labels. + tag_to_label : dict + Tag to label dictionary mapping. + + Returns + ------- + List[pv.PolyData] + List of interface surfaces. + dict + Updated label dictionary. """ tetras = np.reshape(mesh.cells, (mesh.n_cells, 5))[:, 1:] faces, c0, c1 = face_tetra_connectivity(tetras) @@ -156,9 +165,16 @@ def _find_endo_epicardial_regions(geom_all: pv.PolyData, tag_to_label: dict): Parameters ---------- geom_all : pv.PolyData - Entire heart model + Entire heart model. tag_to_label : dict - Dictionary that maps the tags to the corresponding labels + Dictionary that maps the tags to the corresponding labels. + + Returns + ------- + pv.PolyData + Updated geometry with endo and epicardial regions. + dict + Updated tag to label dictionary. """ geom_all.cell_data["orig_ids"] = np.arange(0, geom_all.n_cells) @@ -224,15 +240,15 @@ def _get_part_definitions(original_labels: dict, boundary_label_to_boundary_id: Parameters ---------- original_labels : dict - Dictionary with the original labels + Dictionary with the original labels. boundary_label_to_boundary_id : dict - Dictionary of the boundary label to boundary id map + Dictionary of the boundary label to boundary id map. Returns ------- dict Dictionary with the part definitions. That is part id and corresponding - boundaries that enclose that part. + boundaries that enclose that part. """ part_definitions = {} for original_label, original_tag in original_labels.items(): @@ -304,7 +320,18 @@ def _get_part_definitions(original_labels: dict, boundary_label_to_boundary_id: def _sort_edge_loop(edges): - """Sorts the points in an edge loop.""" + """Sorts the points in an edge loop. + + Parameters + ---------- + edges : np.ndarray + Array of edges. + + Returns + ------- + np.ndarray + Sorted edge loop. + """ remaining_edges = edges next_edge = edges[0] sorted_edges = [next_edge] @@ -339,18 +366,18 @@ def _smooth_boundary_edges( Parameters ---------- surface_mesh : pv.PolyData - Input surface mesh + Input surface mesh. id_to_label_map : dict - ID to label map + ID to label map. sub_label_to_smooth : str, optional - Sub label to smooth, by default "endocardium" + Sub label to smooth, by default "endocardium". window_size : int, optional - Window size of the smoothing method, by default 5 + Window size of the smoothing method, by default 5. Returns ------- Tuple[pv.PolyData, dict] - Preprocessor compatible polydata object and dictionary with part definitions + Preprocessor compatible polydata object and dictionary with part definitions. """ surfaces_to_smooth = [ id for id, label in id_to_label_map.items() if sub_label_to_smooth in label @@ -433,16 +460,16 @@ def get_compatible_input( Parameters ---------- mesh_path : str - Path to the input mesh (UnstructuredGrid or MultiBlock) + Path to the input mesh (UnstructuredGrid or MultiBlock). model_type : str, optional - Type of model to extract, by default "FullHeart" + Type of model to extract, by default "FullHeart". database : str, optional - Database name, by default "Rodero2021" + Database name, by default "Rodero2021". Returns ------- Tuple[pv.PolyData, dict] - Preprocessor compatible polydata object and dictionary with part definitions + Preprocessor compatible polydata object and dictionary with part definitions. """ case_num = os.path.basename(mesh_path) case_num = int(case_num.replace(".case", "").replace(".vtk", "")) diff --git a/src/ansys/heart/preprocessor/input.py b/src/ansys/heart/preprocessor/input.py index ca7b5586f..e7c375471 100644 --- a/src/ansys/heart/preprocessor/input.py +++ b/src/ansys/heart/preprocessor/input.py @@ -123,6 +123,8 @@ class _InputBoundary(pv.PolyData): + """Class to manage input boundaries.""" + def __init__( self, var_inp=None, @@ -138,6 +140,7 @@ def __init__( id=None, name: str = "", ) -> None: + """Initiate the input boundary.""" super().__init__( var_inp, faces, n_faces, lines, n_lines, strips, n_strips, deep, force_ext, force_float ) @@ -214,6 +217,7 @@ def __init__( scalar: str = None, part_definitions: dict = None, ) -> None: + """Initiate the input model.""" self.input_polydata: pv.PolyData = None """Input boundary.""" self.part_definitions = part_definitions diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index 8fefe62b8..06ce8ecb4 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -323,9 +323,14 @@ def _update_size_per_part( global_size : float Global size to use for parts that are not referenced. part_names : list[str] - Part names involved in the model/ + Part names involved in the model. size_per_part : dict, optional - Size per part used to override global size, by default None + Size per part used to override global size, by default None. + + Returns + ------- + dict + Dictionary containing the mesh size per part. """ # convert both to Fluent naming convention. Note: so remove cases and spaces part_names = [_to_fluent_convention(part) for part in part_names] @@ -763,15 +768,15 @@ def mesh_from_non_manifold_input_model( path_to_output : Union[str, Path] Path to the resulting Fluent mesh file. global_mesh_size : float, optional - Uniform mesh size to use for all volumes and surfaces, by default 2.0 + Uniform mesh size to use for all volumes and surfaces, by default 2.0. _global_wrap_size : float, optional - Global size used by the wrapper to reconstruct the geometry, by default 1.5 + Global size used by the wrapper to reconstruct the geometry, by default 1.5. overwrite_existing_mesh : bool, optional - Flag indicating whether to overwrite an existing mesh, by default True + Flag indicating whether to overwrite an existing mesh, by default True. mesh_size_per_part : dict, optional - Dictionary specifying the mesh size that should be used for each part, by default None + Dictionary specifying the mesh size that should be used for each part, by default None. _wrap_size_per_part : dict, optional - Dictionary specifying the mesh size that should be used to wrap each part, by default None + Dictionary specifying the mesh size that should be used to wrap each part, by default None. Notes ----- diff --git a/src/ansys/heart/simulator/settings/material/curve.py b/src/ansys/heart/simulator/settings/material/curve.py index ab8f5a54b..cf2f96c5f 100644 --- a/src/ansys/heart/simulator/settings/material/curve.py +++ b/src/ansys/heart/simulator/settings/material/curve.py @@ -39,14 +39,14 @@ def strocchi_active(t_end=800, t_act=0) -> tuple[np.ndarray, np.ndarray]: Parameters ---------- t_end : int, optional - heart beat period, by default 800 + heart beat period, by default 800. t_act : int, optional - start time, by default 0 + start time, by default 0. Returns ------- tuple[np.ndarray, np.ndarray] - (time, stress) array + (time, stress) array. """ # parameters used in Strocchi in ms tau_r = 130 * 800 / t_end @@ -72,17 +72,17 @@ def kumaraswamy_active(t_end=1000) -> tuple[np.ndarray, np.ndarray]: """ Active stress in Gaƫtan Desrues doi.org/10.1007/978-3-030-78710-3_43. - T_peak is described in MAT295 + T_peak is described in MAT295. Parameters ---------- t_end : int, optional - heart beat duration, by default 1000 + heart beat duration, by default 1000. Returns ------- tuple[np.ndarray, np.ndarray] - (timen,stress) array + (timen,stress) array. """ apd90 = 250 * t_end / 1000 # action potential duration time_repolarization = 750 * t_end / 1000 # repolarization time @@ -107,14 +107,14 @@ def constant_ca2(tb: float = 800, ca2ionm: float = 4.35) -> tuple[np.ndarray, np Parameters ---------- tb : float, optional - heart beat period, by default 800 + heart beat period, by default 800. ca2ionm : : float, optional - amplitude which equals ca2ionm in MAT_295 + amplitude which equals ca2ionm in MAT_295. Returns ------- tuple[np.ndarray, np.ndarray] - (time, stress) array + (time, stress) array. """ t = np.linspace(0, tb, 101) v = np.ones((101)) * ca2ionm @@ -141,13 +141,13 @@ def __init__( Parameters ---------- func : tuple[np.ndarray, np.ndarray] - (time, stress or ca2) array for one heart beat + (time, stress or ca2) array for one heart beat. type : Literal["stress", "ca2"], optional - type of curve, by default "ca2" + type of curve, by default "ca2". threshold : float, optional threshold of des/active active stress, by default 0.5e-6. n : int, optional - No. of heart beat will be written for LS-DYNA, by default 5 + No. of heart beat will be written for LS-DYNA, by default 5. Note ---- diff --git a/src/ansys/heart/simulator/settings/material/ep_material.py b/src/ansys/heart/simulator/settings/material/ep_material.py index b7bb96cc5..71585506d 100644 --- a/src/ansys/heart/simulator/settings/material/ep_material.py +++ b/src/ansys/heart/simulator/settings/material/ep_material.py @@ -284,4 +284,5 @@ def __repr__(self): mat2 = EPMaterial.Passive(sigma_fiber=0, sigma_sheet=2) mat3 = EPMaterial.Active(sigma_fiber=1, sigma_sheet=2, sigma_sheet_normal=3) mat4 = EPMaterial.Active(sigma_fiber=1, sigma_sheet=2, sigma_sheet_normal=3) + # TODO: @mhoeijm - use logger print("done") diff --git a/src/ansys/heart/simulator/simulator.py b/src/ansys/heart/simulator/simulator.py index 0d74eadc7..47313e150 100644 --- a/src/ansys/heart/simulator/simulator.py +++ b/src/ansys/heart/simulator/simulator.py @@ -125,9 +125,9 @@ def compute_fibers( Parameters ---------- method : Literal["LSDYNA", "D, optional - method, by default "LSDYNA" + method, by default "LSDYNA". rotation_angles : dict, optional - rotation angle alpha and beta, by default None + rotation angle alpha and beta, by default None. """ LOGGER.info("Computing fiber orientation...") @@ -301,7 +301,7 @@ def compute_left_atrial_fiber( Parameters ---------- appendage : list[float], optional - Coordinates of appendage, by default None + Coordinates of appendage, by default None. If not defined, we use the cap named 'appendage'. Returns @@ -385,7 +385,6 @@ def _run_dyna(self, path_to_input: pathlib, options: str = ""): Path to the LS-DYNA simulation file. options : str, optional Additional options to pass to command line, by default "". - """ if options != "": old_options = copy.deepcopy(self.dyna_settings.dyna_options) @@ -693,7 +692,13 @@ def __init__( return def simulate(self, folder_name="ep_meca"): - """Launch the main simulation.""" + """Launch the main simulation. + + Parameters + ---------- + folder_name : str + folder name for the main simulation. + """ # MechanicalSimulator handle dynain file from zerop MechanicsSimulator.simulate(self, folder_name=folder_name) diff --git a/src/ansys/heart/writer/dynawriter.py b/src/ansys/heart/writer/dynawriter.py index 68959e9f8..6c820c9a8 100644 --- a/src/ansys/heart/writer/dynawriter.py +++ b/src/ansys/heart/writer/dynawriter.py @@ -124,10 +124,8 @@ def __init__(self, model: HeartModel, settings: SimulationSettings = None) -> No Simulation settings used to create the LS-DYNA model. Loads defaults if None, by default None - Example - ------- - TODO: add example """ + # TODO: @mhoeijm: Add examples for the class docstring. self.model = model """Model information necessary for creating the LS-DYNA .k files.""" diff --git a/src/ansys/heart/writer/heart_decks.py b/src/ansys/heart/writer/heart_decks.py index a717d9e5f..b1413443d 100644 --- a/src/ansys/heart/writer/heart_decks.py +++ b/src/ansys/heart/writer/heart_decks.py @@ -35,6 +35,7 @@ class BaseDecks: """ def __init__(self) -> None: + """Initiate the decks.""" self.main = Deck() self.parts = Deck() self.nodes = Deck() @@ -52,9 +53,16 @@ def add_deck(self, deckname: str): class MechanicsDecks(BaseDecks): - """Useful decks for a mechanics simulation.""" + """Useful decks for a mechanics simulation. + + Parameters + ---------- + BaseDecks : BaseDecks + Inherits from BaseDecks class. + """ def __init__(self) -> None: + """Initiate the decks.""" super().__init__() self.cap_elements = Deck() self.control_volume = Deck() @@ -62,9 +70,16 @@ def __init__(self) -> None: class FiberGenerationDecks(BaseDecks): - """Useful decks for fiber generation.""" + """Useful decks for fiber generation. + + Parameters + ---------- + BaseDecks : BaseDecks + Inherits from BaseDecks class. + """ def __init__(self) -> None: + """Initiate the decks.""" super().__init__() self.ep_settings = Deck() self.create_fiber = Deck() @@ -83,6 +98,7 @@ class ElectrophysiologyDecks(BaseDecks): """Useful decks for Electrophysiology simulations.""" def __init__(self) -> None: + """Initiate the decks.""" super().__init__() self.cell_models = Deck() self.ep_settings = Deck() @@ -93,5 +109,6 @@ class ElectroMechanicsDecks(ElectrophysiologyDecks, MechanicsDecks): """Useful decks for a electromechanics simulation.""" def __init__(self) -> None: + """Initiate the decks.""" super().__init__() self.duplicate_nodes = Deck() diff --git a/src/ansys/heart/writer/keyword_utils.py b/src/ansys/heart/writer/keyword_utils.py index 28783bacd..07c706c66 100644 --- a/src/ansys/heart/writer/keyword_utils.py +++ b/src/ansys/heart/writer/keyword_utils.py @@ -44,6 +44,10 @@ def create_node_keyword(nodes: np.array, offset: int = 0) -> keywords.Node: keywords.Node Formatted node keyword """ + # TODO: @mhoeijm: offset is not used, should it be? + # if not required, remove it, otherwise improve the docstring + # by explaining what it does + # create array with node ids nids = np.arange(0, nodes.shape[0], 1) + 1 @@ -117,6 +121,11 @@ def add_beams_to_kw( beam keyword offset : int beam id offset + + Returns + ------- + keywords.ElementBeam + Formatted beam keyword """ # get beam id of last beam: if not beam_kw.elements.empty and offset == 0: @@ -160,7 +169,7 @@ def create_segment_set_keyword( Returns ------- keywords.SetSegment - Formatted segment set keyword + Formatted segment set keyword with the provided segments. """ if segments.shape[1] < 3 or segments.shape[1] > 4: raise ValueError("expecting segments to have 3 or 4 columns") @@ -222,6 +231,20 @@ def create_element_shell_keyword( ) -> keywords.ElementShell: """Create element shell keyword. + Parameters + ---------- + shells : np.array + Array of elements. + part_id : int, optional + Part id, by default 1. + id_offset : int, optional + Offset for the element id, by default 0. + + Returns + ------- + keywords.ElementShell + Formatted element shell keyword. + Notes ----- From a numpy array of elements. Each row corresponds to an element. @@ -363,7 +386,26 @@ def create_define_curve_kw( curve_id: int = 1, lcint: int = 15000, ) -> keywords.DefineCurve: - """Create define curve from x and y values.""" + """Create define curve from x and y values. + + Parameters + ---------- + x : np.array + X values of the curve. + y : np.array + Y values of the curve. + curve_name : str, optional + Name of the curve, by default 'my-title'. + curve_id : int, optional + Curve id, by default 1. + lcint : int, optional + Load curve interpolation, by default 15000. + + Returns + ------- + keywords.DefineCurve + Formatted define curve keyword. + """ kw = keywords.DefineCurve() kw.options["TITLE"].active = True kw.title = curve_name @@ -385,11 +427,16 @@ def create_define_sd_orientation_kw( Parameters ---------- vectors : np.array - Array of shape Nx3 with the defined vector + Array of shape Nx3 with the defined vector. vector_id_offset : int, optional - Offset for the vector id, by default 0 + Offset for the vector id, by default 0. iop : int, optional - Option, by default 0 + Option, by default 0. + + Returns + ------- + keywords.DefineSdOrientation + Formatted define SD orientation keyword. """ kw = keywords.DefineSdOrientation() if len(vectors.shape) == 2: @@ -421,18 +468,23 @@ def create_discrete_elements_kw( Parameters ---------- nodes : np.array - Nx2 Array with node ids used for the discrete element + Nx2 Array with node ids used for the discrete element. part_id : int Part id of the discrete elements given vector_ids : Union[np.array, int] Orientation ids (vector ids) along which the spring acts. Can be either an array of length N, or a scalar integer scale_factor : Union[np.array, float] - Scale factor on forces, either an array of length N or scalar value + Scale factor on forces, either an array of length N or scalar value. element_id_offset : int, optional - Offset value for the element ids, by default 0 + Offset value for the element ids, by default 0. init_offset : float, optional - Initial offset: initial displacement or rotation at t=0, by default 0.0 + Initial offset: initial displacement or rotation at t=0, by default 0.0. + + Returns + ------- + keywords.ElementDiscrete + Formatted discrete element keyword. """ num_elements = nodes.shape[0] @@ -466,19 +518,19 @@ def get_list_of_used_ids(keyword_db: Deck, keyword_str: str) -> np.ndarray: Notes ----- - E.g. for *SECTION, *PART and *MAT ids + E.g. for *SECTION, *PART and *MAT ids. Parameters ---------- database : Deck - Database of keywords + Database of keywords. keyword : str - Keyword which to find + Keyword which to find. Returns ------- np.ndarray - Array of ids (ints) which are already used + Array of ids (ints) which are already used. """ ids = np.empty(0, dtype=int) @@ -540,6 +592,13 @@ def fast_element_writer( ): """Fast implementation of the element writer. + Parameters + ---------- + element_kw : Union[keywords.ElementSolidOrtho, keywords.ElementSolid] + Element keyword to write. + filename : str + Filename to write to. + Notes ----- Use this as an alternative to the dynalib writer diff --git a/src/ansys/heart/writer/material_keywords.py b/src/ansys/heart/writer/material_keywords.py index 83291a327..3c0e58696 100644 --- a/src/ansys/heart/writer/material_keywords.py +++ b/src/ansys/heart/writer/material_keywords.py @@ -163,8 +163,10 @@ def active_curve( Parameters ---------- - curve_name : str - Type of curve to compute + curve_type : str, optional + Type of curve to generate, by default "Strocchi2020" + endtime : float, optional + End time of the curve, by default 15 """ # time array # T = np.arange( 0, endtime, timestep )