From 413b637e34ef882cdf03c5ac78c29d2205fe3626 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Wed, 5 Jul 2023 14:42:05 +0200 Subject: [PATCH 001/116] Added water boundary for multiline or interpolation --- stem/IO/kratos_water_boundaries_io.py | 86 ++++++++++++++++++ stem/water_boundaries.py | 105 ++++++++++++++++++++++ tests/test_data/expected_water_lines.json | 62 +++++++++++++ tests/test_kratos_water_boundaries_io.py | 45 ++++++++++ tests/test_water_boundaries.py | 17 ++++ 5 files changed, 315 insertions(+) create mode 100644 stem/IO/kratos_water_boundaries_io.py create mode 100644 stem/water_boundaries.py create mode 100644 tests/test_data/expected_water_lines.json create mode 100644 tests/test_kratos_water_boundaries_io.py create mode 100644 tests/test_water_boundaries.py diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py new file mode 100644 index 000000000..e214a3c33 --- /dev/null +++ b/stem/IO/kratos_water_boundaries_io.py @@ -0,0 +1,86 @@ +from typing import Dict, List, Any +from copy import deepcopy + +from stem.water_boundaries import WaterBoundary + + +class KratosWaterBoundariesIO: + + def __init__(self, domain: str): + self.domain = domain + + def __phreatic_multi_line_boundary_dict(self, water_boundary: WaterBoundary): + """ + Creates a dictionary containing the water boundary parameters for phreatic multi line boundary + + Attributes: + - water_boundary: water boundary object + + Returns: dictionary containing the water boundary parameters + + """ + + parameters : Dict[str, Any] = { + "model_part_name": f"{self.domain}.{water_boundary.name}", + "variable_name": "WATER_PRESSURE", + "table": [0, 0, 0], + "value": water_boundary.water_boundary.water_pressure, + "is_fixed": water_boundary.water_boundary.is_fixed, + "gravity_direction": water_boundary.water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.water_boundary.out_of_plane_direction, + "fluid_pressure_type": water_boundary.type, + "specific_weight": water_boundary.water_boundary.specific_weight, + "x_coordinates": water_boundary.water_boundary.x_coordinates, + "y_coordinates": water_boundary.water_boundary.y_coordinates, + "z_coordinates": water_boundary.water_boundary.z_coordinates, + } + boundary_dict: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": parameters, + } + return boundary_dict + + def __interpolate_line_boundary_dict(self, water_boundary: WaterBoundary): + """ + Creates a dictionary containing the water boundary parameters for interpolate line boundary + + Attributes: + - water_boundary: water boundary object + + Returns: dictionary containing the water boundary parameters + + """ + boundary_dict: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": f"{self.domain}.{water_boundary.name}", + "variable_name": "WATER_PRESSURE", + "is_fixed": water_boundary.water_boundary.is_fixed, + "table": 0, + "fluid_pressure_type": water_boundary.type, + "gravity_direction": water_boundary.water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.water_boundary.out_of_plane_direction, + } + } + return boundary_dict + + def create_water_boundary_dict(self, water_boundary: WaterBoundary): + """ + Creates a dictionary containing the water boundary parameters + + Attributes: + - water_boundary: water boundary object + + Returns: dictionary containing the water boundary parameters + + """ + if water_boundary.water_boundary.type == "Phreatic_Multi_Line": + return self.__phreatic_multi_line_boundary_dict(water_boundary) + elif water_boundary.water_boundary.type == "Interpolate_Line": + return self.__interpolate_line_boundary_dict(water_boundary) + else: + raise ValueError(f"Unknown water boundary type: {water_boundary.water_boundary.type}") \ No newline at end of file diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py new file mode 100644 index 000000000..8577f406c --- /dev/null +++ b/stem/water_boundaries.py @@ -0,0 +1,105 @@ +from typing import List, Dict, Any, Union, Optional +from dataclasses import dataclass, field +from abc import ABC + + + +@dataclass +class WaterBoundaryParameters(ABC): + """ + Abstract base class for load water boundary parameters + + Attributes: + - surfaces_assigment (List[str]): List of surfaces to which the water boundary is assigned. + - is_fixed (bool): True if the water boundary is fixed, False otherwise. + - gravity_direction (int): Direction of the gravity vector. + - out_of_plane_direction (int): Direction of the out of plane vector. + + + """ + surfaces_assigment: List[str] = field(default_factory=lambda: [""]) + is_fixed: bool = True + gravity_direction: int = 1 + out_of_plane_direction: int = 2 + + +@dataclass +class PhreaticMultiLineBoundary(WaterBoundaryParameters): + """ + Class containing the load parameters for a phreatic line boundary condition + + Attributes: + - x_coordinates (List[float]): X coordinates of the phreatic line [m]. + - y_coordinates (List[float]): Y coordinates of the phreatic line [m]. + - z_coordinates (List[float]): Z coordinates of the phreatic line [m]. + - specific_weight (float): Specific weight of the water [kN/m3]. + + + """ + x_coordinates: List[float] = field(default_factory=lambda: [0.0]) + y_coordinates: List[float] = field(default_factory=lambda: [0.0]) + z_coordinates: List[float] = field(default_factory=lambda: [0.0]) + specific_weight: float = 9.81 + water_pressure: float = 0.0 + + def __post_init__(self): + """ + Post initialization method of the class. It checks that the coordinates are of the same length. + + Returns: None + + """ + + # Check that the coordinates are of the same length + if len(self.x_coordinates) != len(self.y_coordinates): + raise ValueError("The x and y coordinates must be of the same length") + # check if coordinate z is defined + if len(self.z_coordinates) > 1: + if len(self.x_coordinates) != len(self.z_coordinates): + raise ValueError("The x/y and z coordinates must be of the same length") + else: + # define default z coordinates + self.z_coordinates = [0.0] * len(self.x_coordinates) + + + @property + def type(self): + return "Phreatic_Multi_Line" + +@dataclass +class InterpolateLineBoundary(WaterBoundaryParameters): + """ + Class containing the boundary parameters for a interpolate line boundary condition. + + + """ + pass + + @property + def type(self): + return "Interpolate_Line" + + +class WaterBoundary: + """ + Class containing water boundary information acting on a body part + + Attributes: + - water_boundary (WaterBoundaryParameters): Water boundary parameters + - type (str): Type of water boundary + + """ + + def __init__(self, water_boundary: WaterBoundaryParameters, name: str): + """ + Constructor of the class + + Attributes: + - water_boundary (WaterBoundaryParameters): Water boundary parameters + + """ + + self.water_boundary: WaterBoundaryParameters = water_boundary + self.type: str = self.water_boundary.type + self.name: str = name + diff --git a/tests/test_data/expected_water_lines.json b/tests/test_data/expected_water_lines.json new file mode 100644 index 000000000..d036e94c5 --- /dev/null +++ b/tests/test_data/expected_water_lines.json @@ -0,0 +1,62 @@ +{ + "test": [ + { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": "PorousDomain.water_soils_1", + "variable_name": "WATER_PRESSURE", + "is_fixed": true, + "value": 0.0, + "table": [ + 0, + 0, + 0 + ], + "fluid_pressure_type": "Phreatic_Multi_Line", + "gravity_direction": 1, + "out_of_plane_direction": 2, + "x_coordinates": [ + -40.0, + -11.4, + 0.0, + 9.0, + 21.5, + 95.0 + ], + "y_coordinates": [ + 0.44, + 0.44, + 3.0, + 3.0, + -0.5, + -0.5 + ], + "z_coordinates": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "specific_weight": 9.81 + } + }, + { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": "PorousDomain.water_soils_2", + "variable_name": "WATER_PRESSURE", + "is_fixed": true, + "table": 0, + "fluid_pressure_type": "Interpolate_Line", + "gravity_direction": 1, + "out_of_plane_direction": 2 + } + } + ] +} \ No newline at end of file diff --git a/tests/test_kratos_water_boundaries_io.py b/tests/test_kratos_water_boundaries_io.py new file mode 100644 index 000000000..dfad9d4e7 --- /dev/null +++ b/tests/test_kratos_water_boundaries_io.py @@ -0,0 +1,45 @@ +from tests.utils import TestUtils +import json + +from stem.IO.kratos_water_boundaries_io import KratosWaterBoundariesIO +from stem.water_boundaries import WaterBoundary, InterpolateLineBoundary, PhreaticMultiLineBoundary + + +class TestKratosWaterBoundariesIO: + + def test_create_water_boundary_process_dict(self): + """ + + Test the creation of the water boundary process dictionary for the + ProjectParameters.json file + + """ + multi_line_boundary = PhreaticMultiLineBoundary( + is_fixed=True, + gravity_direction=1, + out_of_plane_direction=2, + water_pressure=0, + x_coordinates=[-40.0, -11.4, 0.0, 9.0, 21.5, 95.0], + y_coordinates=[0.44, 0.44, 3.0, 3.0, -0.5, -0.5], + surfaces_assigment=["domain a", "domain b", "domain c"], + ) + water_boundary = WaterBoundary(multi_line_boundary, name="water_soils_1") + # use the kratos io to create the dictionary + kratos_io = KratosWaterBoundariesIO(domain="PorousDomain") + # set the interpolation type + interpolation_type = InterpolateLineBoundary( + surfaces_assigment=["domain d"], + ) + water_boundary_interpolate = WaterBoundary(interpolation_type, name="water_soils_2") + + # check the dictionary + # read the expected dictionary from the json + with open("test_data/expected_water_lines.json") as json_file: + expected_water_boundary_json = json.load(json_file) + # compare the dictionaries + TestUtils.assert_dictionary_almost_equal(expected_water_boundary_json['test'][0], + kratos_io.create_water_boundary_dict( + water_boundary + )) + TestUtils.assert_dictionary_almost_equal(expected_water_boundary_json['test'][1], + kratos_io.create_water_boundary_dict(water_boundary_interpolate)) diff --git a/tests/test_water_boundaries.py b/tests/test_water_boundaries.py new file mode 100644 index 000000000..290cdc7c4 --- /dev/null +++ b/tests/test_water_boundaries.py @@ -0,0 +1,17 @@ +import pytest + +from stem.water_boundaries import * + +class TestWaterBoundaries: + + def test_raise_errors_for_water_boundaries(self): + + pytest.raises(ValueError, PhreaticMultiLineBoundary, x_coordinates=[0, 1, 2], y_coordinates=[0, 1, 2, 3]) + + pytest.raises(ValueError, PhreaticMultiLineBoundary, x_coordinates=[0, 1, 2, 3, 4], y_coordinates=[0, 1, 2, 3]) + + pytest.raises(ValueError, + PhreaticMultiLineBoundary, + x_coordinates=[0, 1, 2, 3], + y_coordinates=[0, 1, 2, 3], + z_coordinates=[0, 1, 2, 3, 4]) \ No newline at end of file From d2959fd7d3219f6bf3256c755788c6687b9b9194 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Wed, 5 Jul 2023 14:46:11 +0200 Subject: [PATCH 002/116] Path corrected --- tests/test_kratos_water_boundaries_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_kratos_water_boundaries_io.py b/tests/test_kratos_water_boundaries_io.py index dfad9d4e7..7a81c461c 100644 --- a/tests/test_kratos_water_boundaries_io.py +++ b/tests/test_kratos_water_boundaries_io.py @@ -34,7 +34,7 @@ def test_create_water_boundary_process_dict(self): # check the dictionary # read the expected dictionary from the json - with open("test_data/expected_water_lines.json") as json_file: + with open("tests/test_data/expected_water_lines.json") as json_file: expected_water_boundary_json = json.load(json_file) # compare the dictionaries TestUtils.assert_dictionary_almost_equal(expected_water_boundary_json['test'][0], From 3221d7ae2f6ad91af3219a11369e8ead89fb8fb4 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Wed, 5 Jul 2023 14:53:38 +0200 Subject: [PATCH 003/116] Type is now more restrictive --- stem/water_boundaries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index 8577f406c..60c76f05a 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -90,7 +90,7 @@ class WaterBoundary: """ - def __init__(self, water_boundary: WaterBoundaryParameters, name: str): + def __init__(self, water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary], name: str): """ Constructor of the class @@ -99,7 +99,7 @@ def __init__(self, water_boundary: WaterBoundaryParameters, name: str): """ - self.water_boundary: WaterBoundaryParameters = water_boundary + self.water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary] = water_boundary self.type: str = self.water_boundary.type self.name: str = name From de2303eaae12cfa40fe7a6317621cb8967e5250d Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Wed, 5 Jul 2023 15:00:29 +0200 Subject: [PATCH 004/116] Type is now even more restrictive --- stem/IO/kratos_water_boundaries_io.py | 49 ++++++++++++++------------- stem/water_boundaries.py | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index e214a3c33..3c5828df0 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -1,7 +1,6 @@ -from typing import Dict, List, Any -from copy import deepcopy +from typing import Dict, Any -from stem.water_boundaries import WaterBoundary +from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary class KratosWaterBoundariesIO: @@ -9,7 +8,7 @@ class KratosWaterBoundariesIO: def __init__(self, domain: str): self.domain = domain - def __phreatic_multi_line_boundary_dict(self, water_boundary: WaterBoundary): + def __phreatic_multi_line_boundary_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary): """ Creates a dictionary containing the water boundary parameters for phreatic multi line boundary @@ -20,19 +19,19 @@ def __phreatic_multi_line_boundary_dict(self, water_boundary: WaterBoundary): """ - parameters : Dict[str, Any] = { - "model_part_name": f"{self.domain}.{water_boundary.name}", + parameters: Dict[str, Any] = { + "model_part_name": f"{self.domain}.{name}", "variable_name": "WATER_PRESSURE", "table": [0, 0, 0], - "value": water_boundary.water_boundary.water_pressure, - "is_fixed": water_boundary.water_boundary.is_fixed, - "gravity_direction": water_boundary.water_boundary.gravity_direction, - "out_of_plane_direction": water_boundary.water_boundary.out_of_plane_direction, - "fluid_pressure_type": water_boundary.type, - "specific_weight": water_boundary.water_boundary.specific_weight, - "x_coordinates": water_boundary.water_boundary.x_coordinates, - "y_coordinates": water_boundary.water_boundary.y_coordinates, - "z_coordinates": water_boundary.water_boundary.z_coordinates, + "value": water_boundary.water_pressure, + "is_fixed": water_boundary.is_fixed, + "gravity_direction": water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.out_of_plane_direction, + "fluid_pressure_type": type, + "specific_weight": water_boundary.specific_weight, + "x_coordinates": water_boundary.x_coordinates, + "y_coordinates": water_boundary.y_coordinates, + "z_coordinates": water_boundary.z_coordinates, } boundary_dict: Dict[str, Any] = { "python_module": "apply_scalar_constraint_table_process", @@ -42,7 +41,7 @@ def __phreatic_multi_line_boundary_dict(self, water_boundary: WaterBoundary): } return boundary_dict - def __interpolate_line_boundary_dict(self, water_boundary: WaterBoundary): + def __interpolate_line_boundary_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary): """ Creates a dictionary containing the water boundary parameters for interpolate line boundary @@ -57,13 +56,13 @@ def __interpolate_line_boundary_dict(self, water_boundary: WaterBoundary): "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", "process_name": "ApplyScalarConstraintTableProcess", "Parameters": { - "model_part_name": f"{self.domain}.{water_boundary.name}", + "model_part_name": f"{self.domain}.{name}", "variable_name": "WATER_PRESSURE", - "is_fixed": water_boundary.water_boundary.is_fixed, + "is_fixed": water_boundary.is_fixed, "table": 0, - "fluid_pressure_type": water_boundary.type, - "gravity_direction": water_boundary.water_boundary.gravity_direction, - "out_of_plane_direction": water_boundary.water_boundary.out_of_plane_direction, + "fluid_pressure_type": type, + "gravity_direction": water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.out_of_plane_direction, } } return boundary_dict @@ -79,8 +78,10 @@ def create_water_boundary_dict(self, water_boundary: WaterBoundary): """ if water_boundary.water_boundary.type == "Phreatic_Multi_Line": - return self.__phreatic_multi_line_boundary_dict(water_boundary) + return self.__phreatic_multi_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, + water_boundary.water_boundary) elif water_boundary.water_boundary.type == "Interpolate_Line": - return self.__interpolate_line_boundary_dict(water_boundary) + return self.__interpolate_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, + water_boundary.water_boundary) else: - raise ValueError(f"Unknown water boundary type: {water_boundary.water_boundary.type}") \ No newline at end of file + raise ValueError(f"Unknown water boundary type: {water_boundary.water_boundary.type}") diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index 60c76f05a..965ff2596 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Any, Union, Optional +from typing import List, Union from dataclasses import dataclass, field from abc import ABC From e39bec7a64c00bf0608a349a9a72c89768b947c1 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Wed, 5 Jul 2023 15:06:17 +0200 Subject: [PATCH 005/116] Typing also passed in the funstion --- stem/IO/kratos_water_boundaries_io.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index 3c5828df0..d232fc1d1 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -78,10 +78,12 @@ def create_water_boundary_dict(self, water_boundary: WaterBoundary): """ if water_boundary.water_boundary.type == "Phreatic_Multi_Line": + local_water_boundary: PhreaticMultiLineBoundary = water_boundary.water_boundary return self.__phreatic_multi_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, - water_boundary.water_boundary) + local_water_boundary) elif water_boundary.water_boundary.type == "Interpolate_Line": + local_water_boundary: InterpolateLineBoundary = water_boundary.water_boundary return self.__interpolate_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, - water_boundary.water_boundary) + local_water_boundary) else: raise ValueError(f"Unknown water boundary type: {water_boundary.water_boundary.type}") From 6a37653a271d678b761af11f0c863a1bd94f05dc Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 6 Jul 2023 08:52:37 +0200 Subject: [PATCH 006/116] Types are checked in the if statement --- stem/IO/kratos_water_boundaries_io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index d232fc1d1..cdaa76c4f 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -77,11 +77,11 @@ def create_water_boundary_dict(self, water_boundary: WaterBoundary): Returns: dictionary containing the water boundary parameters """ - if water_boundary.water_boundary.type == "Phreatic_Multi_Line": + if water_boundary.water_boundary.__class__ == PhreaticMultiLineBoundary: local_water_boundary: PhreaticMultiLineBoundary = water_boundary.water_boundary return self.__phreatic_multi_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, local_water_boundary) - elif water_boundary.water_boundary.type == "Interpolate_Line": + elif water_boundary.water_boundary.__class__ == InterpolateLineBoundary: local_water_boundary: InterpolateLineBoundary = water_boundary.water_boundary return self.__interpolate_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, local_water_boundary) From b294ac2946527d1875a2fc9cb0d9791a05272699 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 6 Jul 2023 08:56:45 +0200 Subject: [PATCH 007/116] new variables created --- stem/IO/kratos_water_boundaries_io.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index cdaa76c4f..2c166e46b 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -78,12 +78,12 @@ def create_water_boundary_dict(self, water_boundary: WaterBoundary): """ if water_boundary.water_boundary.__class__ == PhreaticMultiLineBoundary: - local_water_boundary: PhreaticMultiLineBoundary = water_boundary.water_boundary + multi_line_boundary: PhreaticMultiLineBoundary = water_boundary.water_boundary return self.__phreatic_multi_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, - local_water_boundary) + multi_line_boundary) elif water_boundary.water_boundary.__class__ == InterpolateLineBoundary: - local_water_boundary: InterpolateLineBoundary = water_boundary.water_boundary + interpolate_line_boundary: InterpolateLineBoundary = water_boundary.water_boundary return self.__interpolate_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, - local_water_boundary) + interpolate_line_boundary) else: raise ValueError(f"Unknown water boundary type: {water_boundary.water_boundary.type}") From 77d97de2156108b2e62333ac233795ee29891c17 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 6 Jul 2023 09:17:59 +0200 Subject: [PATCH 008/116] Changed the functions so that it satisfies mypy test --- stem/IO/kratos_water_boundaries_io.py | 106 +++++++++++++++----------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index 2c166e46b..e02e35dd4 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -1,4 +1,5 @@ from typing import Dict, Any +from typing import overload from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary @@ -8,64 +9,90 @@ class KratosWaterBoundariesIO: def __init__(self, domain: str): self.domain = domain - def __phreatic_multi_line_boundary_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary): + + @overload + def __water_boundary_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary) -> Dict[str, Any]: """ Creates a dictionary containing the water boundary parameters for phreatic multi line boundary Attributes: - water_boundary: water boundary object + - type: type of the water boundary + - name: name of the water boundary Returns: dictionary containing the water boundary parameters """ + ... - parameters: Dict[str, Any] = { - "model_part_name": f"{self.domain}.{name}", - "variable_name": "WATER_PRESSURE", - "table": [0, 0, 0], - "value": water_boundary.water_pressure, - "is_fixed": water_boundary.is_fixed, - "gravity_direction": water_boundary.gravity_direction, - "out_of_plane_direction": water_boundary.out_of_plane_direction, - "fluid_pressure_type": type, - "specific_weight": water_boundary.specific_weight, - "x_coordinates": water_boundary.x_coordinates, - "y_coordinates": water_boundary.y_coordinates, - "z_coordinates": water_boundary.z_coordinates, - } - boundary_dict: Dict[str, Any] = { - "python_module": "apply_scalar_constraint_table_process", - "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", - "process_name": "ApplyScalarConstraintTableProcess", - "Parameters": parameters, - } - return boundary_dict - - def __interpolate_line_boundary_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary): + @overload + def __water_boundary_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary ) -> Dict[str, Any]: """ Creates a dictionary containing the water boundary parameters for interpolate line boundary Attributes: - water_boundary: water boundary object + - type: type of the water boundary + - name: name of the water boundary Returns: dictionary containing the water boundary parameters + """ - boundary_dict: Dict[str, Any] = { - "python_module": "apply_scalar_constraint_table_process", - "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", - "process_name": "ApplyScalarConstraintTableProcess", - "Parameters": { + ... + + def __water_boundary_dict(self, name: str, type: str, water_boundary: Any) -> Dict[str, Any]: + """ + Creates a dictionary containing the water boundary parameters + + Attributes: + - name: name of the water boundary + - type: type of the water boundary + - water_boundary: water boundary object + + Returns: None at the moment + + """ + if isinstance(water_boundary, PhreaticMultiLineBoundary): + parameters: Dict[str, Any] = { "model_part_name": f"{self.domain}.{name}", "variable_name": "WATER_PRESSURE", + "table": [0, 0, 0], + "value": water_boundary.water_pressure, "is_fixed": water_boundary.is_fixed, - "table": 0, - "fluid_pressure_type": type, "gravity_direction": water_boundary.gravity_direction, "out_of_plane_direction": water_boundary.out_of_plane_direction, + "fluid_pressure_type": type, + "specific_weight": water_boundary.specific_weight, + "x_coordinates": water_boundary.x_coordinates, + "y_coordinates": water_boundary.y_coordinates, + "z_coordinates": water_boundary.z_coordinates, } - } - return boundary_dict + boundary_dict: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": parameters, + } + return boundary_dict + elif isinstance(water_boundary, InterpolateLineBoundary): + boundary_dict: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": f"{self.domain}.{name}", + "variable_name": "WATER_PRESSURE", + "is_fixed": water_boundary.is_fixed, + "table": 0, + "fluid_pressure_type": type, + "gravity_direction": water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.out_of_plane_direction, + } + } + return boundary_dict + else: + raise NotImplementedError("This type of boundary is not implemented") def create_water_boundary_dict(self, water_boundary: WaterBoundary): """ @@ -77,13 +104,6 @@ def create_water_boundary_dict(self, water_boundary: WaterBoundary): Returns: dictionary containing the water boundary parameters """ - if water_boundary.water_boundary.__class__ == PhreaticMultiLineBoundary: - multi_line_boundary: PhreaticMultiLineBoundary = water_boundary.water_boundary - return self.__phreatic_multi_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, - multi_line_boundary) - elif water_boundary.water_boundary.__class__ == InterpolateLineBoundary: - interpolate_line_boundary: InterpolateLineBoundary = water_boundary.water_boundary - return self.__interpolate_line_boundary_dict(water_boundary.name, water_boundary.water_boundary.type, - interpolate_line_boundary) - else: - raise ValueError(f"Unknown water boundary type: {water_boundary.water_boundary.type}") + return self.__water_boundary_dict(water_boundary.name, + water_boundary.water_boundary.type, + water_boundary.water_boundary) From 378c38fec509fb8ad657e48cf5210c43bce03bf7 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 6 Jul 2023 09:18:33 +0200 Subject: [PATCH 009/116] Reformatted code --- stem/IO/kratos_water_boundaries_io.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index e02e35dd4..b77c52522 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -9,7 +9,6 @@ class KratosWaterBoundariesIO: def __init__(self, domain: str): self.domain = domain - @overload def __water_boundary_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary) -> Dict[str, Any]: """ @@ -26,7 +25,7 @@ def __water_boundary_dict(self, name: str, type: str, water_boundary: PhreaticMu ... @overload - def __water_boundary_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary ) -> Dict[str, Any]: + def __water_boundary_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary) -> Dict[str, Any]: """ Creates a dictionary containing the water boundary parameters for interpolate line boundary From c4b2d4f8450ff483811427efd7961a64ca6f94c9 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 6 Jul 2023 09:21:57 +0200 Subject: [PATCH 010/116] mypy fix the same value cannot be initialised in the if statement --- stem/IO/kratos_water_boundaries_io.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index b77c52522..f63f10ba8 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -67,15 +67,15 @@ def __water_boundary_dict(self, name: str, type: str, water_boundary: Any) -> Di "y_coordinates": water_boundary.y_coordinates, "z_coordinates": water_boundary.z_coordinates, } - boundary_dict: Dict[str, Any] = { + boundary_dict_multi_line: Dict[str, Any] = { "python_module": "apply_scalar_constraint_table_process", "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", "process_name": "ApplyScalarConstraintTableProcess", "Parameters": parameters, } - return boundary_dict + return boundary_dict_multi_line elif isinstance(water_boundary, InterpolateLineBoundary): - boundary_dict: Dict[str, Any] = { + boundary_dict_interpolate: Dict[str, Any] = { "python_module": "apply_scalar_constraint_table_process", "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", "process_name": "ApplyScalarConstraintTableProcess", @@ -89,7 +89,7 @@ def __water_boundary_dict(self, name: str, type: str, water_boundary: Any) -> Di "out_of_plane_direction": water_boundary.out_of_plane_direction, } } - return boundary_dict + return boundary_dict_interpolate else: raise NotImplementedError("This type of boundary is not implemented") From 2504591127cf19b8184a4ae236d5f30e90ab454a Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 6 Jul 2023 09:50:41 +0200 Subject: [PATCH 011/116] Deleted overload functions and defined the abstract class as type --- stem/IO/kratos_water_boundaries_io.py | 36 ++------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index f63f10ba8..792b592c4 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -1,7 +1,6 @@ from typing import Dict, Any -from typing import overload -from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary +from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary, WaterBoundaryParameters class KratosWaterBoundariesIO: @@ -9,38 +8,7 @@ class KratosWaterBoundariesIO: def __init__(self, domain: str): self.domain = domain - @overload - def __water_boundary_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary) -> Dict[str, Any]: - """ - Creates a dictionary containing the water boundary parameters for phreatic multi line boundary - - Attributes: - - water_boundary: water boundary object - - type: type of the water boundary - - name: name of the water boundary - - Returns: dictionary containing the water boundary parameters - - """ - ... - - @overload - def __water_boundary_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary) -> Dict[str, Any]: - """ - Creates a dictionary containing the water boundary parameters for interpolate line boundary - - Attributes: - - water_boundary: water boundary object - - type: type of the water boundary - - name: name of the water boundary - - Returns: dictionary containing the water boundary parameters - - - """ - ... - - def __water_boundary_dict(self, name: str, type: str, water_boundary: Any) -> Dict[str, Any]: + def __water_boundary_dict(self, name: str, type: str, water_boundary: WaterBoundaryParameters) -> Dict[str, Any]: """ Creates a dictionary containing the water boundary parameters From e35a1d69ddd61d2206e26ea8449f625267693299 Mon Sep 17 00:00:00 2001 From: noordam Date: Thu, 6 Jul 2023 10:42:33 +0200 Subject: [PATCH 012/116] initial wip commit --- stem/model.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/stem/model.py b/stem/model.py index 4aec9b724..3db02e665 100644 --- a/stem/model.py +++ b/stem/model.py @@ -2,6 +2,7 @@ from stem.model_part import ModelPart, BodyModelPart +from gmsh_utils import gmsh_IO class Model: """ @@ -15,10 +16,40 @@ class Model: """ def __init__(self): - + self.ndim = None self.project_parameters = None self.solver = None + self.geometry = None + self.mesh = None self.body_model_parts: List[BodyModelPart] = [] self.process_model_parts: List[ModelPart] = [] + def add_soil_layer(self, coordinates, material_parameters, name, extrusion_length=None): + """ + Adds a soil layer to the model. + + Args: + - coordinates (np.array): The coordinates of the soil layer. + - material_parameters (dict): A dictionary containing the material parameters. + + """ + gmsh_io = gmsh_IO.GmshIO() + if self.ndim == 2: + gmsh_io.make_geometry_2d(coordinates, name) + + if self.ndim == 3 and extrusion_length is None: + raise ValueError("extrusion_length must be specified for 3D models") + + body_model_part = BodyModelPart() + body_model_part.name = name + body_model_part.material = material_parameters + + body_model_part.set_geometry(gmsh_io.geo_data) + + self.body_model_parts.append(body_model_part) + + gmsh_utils.make + + + From b4159257a6a26ecd716272240fe2d9d1bce05ded Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Fri, 7 Jul 2023 16:52:29 +0200 Subject: [PATCH 013/116] Added phreatic line boundary --- stem/IO/kratos_water_boundaries_io.py | 22 +++++++++++++++++- stem/water_boundaries.py | 28 +++++++++++++++++++++-- tests/test_data/expected_water_lines.json | 18 +++++++++++++++ tests/test_kratos_water_boundaries_io.py | 15 +++++++++++- 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index 792b592c4..8521298df 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -1,6 +1,6 @@ from typing import Dict, Any -from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary, WaterBoundaryParameters +from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary, WaterBoundaryParameters, PhreaticLine class KratosWaterBoundariesIO: @@ -58,6 +58,26 @@ def __water_boundary_dict(self, name: str, type: str, water_boundary: WaterBound } } return boundary_dict_interpolate + elif isinstance(water_boundary, PhreaticLine): + boundary_dict_phreatic_line: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": f"{self.domain}.{name}", + "variable_name": "WATER_PRESSURE", + "is_fixed": water_boundary.is_fixed, + "table": [0, 0], + "fluid_pressure_type": type, + "gravity_direction": water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.out_of_plane_direction, + "specific_weight": water_boundary.specific_weight, + "first_reference_coordinate": water_boundary.first_reference_coordinate, + "second_reference_coordinate": water_boundary.second_reference_coordinate, + "value": water_boundary.value, + } + } + return boundary_dict_phreatic_line else: raise NotImplementedError("This type of boundary is not implemented") diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index 965ff2596..bb2bb889f 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -80,6 +80,30 @@ def type(self): return "Interpolate_Line" +@dataclass +class PhreaticLine(WaterBoundaryParameters): + """ + Class containing the boundary parameters for phreatic line boundary condition. This condition is should only contain + two points. + + Attributes: + - first_reference_coordinate (List[float]): First reference coordinate of the phreatic line [m]. + - second_reference_coordinate (List[float]): Second reference coordinate of the phreatic line [m]. + - specific_weight (float): Specific weight of the water . + - value (float): Value of the water pressure . + + + """ + first_reference_coordinate: List[float] = field(default_factory=lambda: [0.0]) + second_reference_coordinate: List[float] = field(default_factory=lambda: [0.0]) + specific_weight: float = 9.81 + value: float = 0.0 + + @property + def type(self): + return "Phreatic_Line" + + class WaterBoundary: """ Class containing water boundary information acting on a body part @@ -90,7 +114,7 @@ class WaterBoundary: """ - def __init__(self, water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary], name: str): + def __init__(self, water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary, PhreaticLine], name: str): """ Constructor of the class @@ -99,7 +123,7 @@ def __init__(self, water_boundary: Union[InterpolateLineBoundary, PhreaticMultiL """ - self.water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary] = water_boundary + self.water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary, PhreaticLine] = water_boundary self.type: str = self.water_boundary.type self.name: str = name diff --git a/tests/test_data/expected_water_lines.json b/tests/test_data/expected_water_lines.json index d036e94c5..847433de9 100644 --- a/tests/test_data/expected_water_lines.json +++ b/tests/test_data/expected_water_lines.json @@ -57,6 +57,24 @@ "gravity_direction": 1, "out_of_plane_direction": 2 } + }, + { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": "PorousDomain.water_soils_3", + "variable_name": "WATER_PRESSURE", + "is_fixed": true, + "value": 0.0, + "table": [0, 0], + "fluid_pressure_type": "Phreatic_Line", + "gravity_direction": 1, + "out_of_plane_direction": 2, + "first_reference_coordinate" : [0.0,1.0,0.0], + "second_reference_coordinate": [1.0,0.5,0.0], + "specific_weight": 10000.0 + } } ] } \ No newline at end of file diff --git a/tests/test_kratos_water_boundaries_io.py b/tests/test_kratos_water_boundaries_io.py index 7a81c461c..7348e0eb4 100644 --- a/tests/test_kratos_water_boundaries_io.py +++ b/tests/test_kratos_water_boundaries_io.py @@ -2,7 +2,7 @@ import json from stem.IO.kratos_water_boundaries_io import KratosWaterBoundariesIO -from stem.water_boundaries import WaterBoundary, InterpolateLineBoundary, PhreaticMultiLineBoundary +from stem.water_boundaries import WaterBoundary, InterpolateLineBoundary, PhreaticMultiLineBoundary, PhreaticLine class TestKratosWaterBoundariesIO: @@ -31,6 +31,17 @@ def test_create_water_boundary_process_dict(self): surfaces_assigment=["domain d"], ) water_boundary_interpolate = WaterBoundary(interpolation_type, name="water_soils_2") + # check phreatic line + phreatic_line = PhreaticLine( + is_fixed=True, + gravity_direction=1, + out_of_plane_direction=2, + value=0, + first_reference_coordinate=[0.0,1.0,0.0], + second_reference_coordinate=[1.0,0.5,0.0], + specific_weight=10000.0, + ) + water_boundary_phreatic_line = WaterBoundary(phreatic_line, name="water_soils_3") # check the dictionary # read the expected dictionary from the json @@ -43,3 +54,5 @@ def test_create_water_boundary_process_dict(self): )) TestUtils.assert_dictionary_almost_equal(expected_water_boundary_json['test'][1], kratos_io.create_water_boundary_dict(water_boundary_interpolate)) + TestUtils.assert_dictionary_almost_equal(expected_water_boundary_json['test'][2], + kratos_io.create_water_boundary_dict(water_boundary_phreatic_line)) From 146cabdc1823150e94344dd05ecdd46d75fb3741 Mon Sep 17 00:00:00 2001 From: noordam Date: Mon, 10 Jul 2023 17:30:04 +0200 Subject: [PATCH 014/116] tmp commit --- stem/model.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/stem/model.py b/stem/model.py index 3db02e665..1f05aba80 100644 --- a/stem/model.py +++ b/stem/model.py @@ -45,11 +45,15 @@ def add_soil_layer(self, coordinates, material_parameters, name, extrusion_lengt body_model_part.name = name body_model_part.material = material_parameters - body_model_part.set_geometry(gmsh_io.geo_data) + body_model_part.get_geometry_from_geo_data(gmsh_io.geo_data, name) self.body_model_parts.append(body_model_part) - gmsh_utils.make + +if __name__ == '__main__': + coordinates = [[0, 0,0], [1, 0,0], [1, 1,0], [0, 1,0]] + + From c777d920402d5bac86622b7bf500aa9ea146f9e3 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 11 Jul 2023 15:46:08 +0200 Subject: [PATCH 015/116] added tests for adding a single soil layer and multiple soil layers in 2D --- stem/geometry.py | 84 +++++++++++++++++-- stem/model.py | 83 +++++++++++++++--- tests/test_model.py | 199 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+), 20 deletions(-) create mode 100644 tests/test_model.py diff --git a/stem/geometry.py b/stem/geometry.py index bf083cb43..37b09e231 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -41,6 +41,23 @@ def __init__(self, id: int): self.__id: int = id self.coordinates: List[float] = [] + @classmethod + def create(cls, coordinates: Sequence[float], id: int): + """ + Creates a point object from a list of coordinates and an id. + + Args: + - coordinates (List[float]): An iterable of floats representing the x, y and z coordinates of the point. + - id (int): The id of the point. + + Returns: + - Point: A point object. + + """ + point = cls(id) + point.coordinates = coordinates + return point + @property def id(self) -> int: """ @@ -87,6 +104,24 @@ def __init__(self, id: int): self.__id: int = id self.point_ids: List[int] = [] + @classmethod + def create(cls, point_ids: Sequence[int], id: int): + """ + Creates a line object from a list of point ids and an id. + + Args: + - point_ids (List[int]): An Iterable of two integers representing the ids of the points that make up the\ + line. + - id (int): The id of the line. + + Returns: + - Line: A line object. + + """ + line = cls(id) + line.point_ids = point_ids + return line + @property def id(self) -> int: """ @@ -146,6 +181,23 @@ def id(self, value: int): """ self.__id = value + @classmethod + def create(cls, line_ids: Sequence[int], id: int): + """ + Creates a surface object from a list of line ids and an id. + + Args: + - line_ids (List[int]): An Iterable of three or more integers representing the ids of the lines that make\ + up the surface. + - id (int): The id of the surface. + + Returns: + - Surface: A surface object. + + """ + surface = cls(id) + surface.line_ids = line_ids + return surface class Volume(GeometricalObjectABC): """ @@ -184,6 +236,23 @@ def id(self, value: int): """ self.__id = value + @classmethod + def create(cls, surface_ids: Sequence[int], id: int): + """ + Creates a volume object from a list of surface ids and an id. + + Args: + - surface_ids (List[int]): An Iterable of four or more integers representing the ids of the surfaces that\ + make up the volume. + - id (int): The id of the volume. + + Returns: + - Volume: A volume object. + + """ + volume = cls(id) + volume.surface_ids = surface_ids + return volume class Geometry: """ @@ -236,9 +305,7 @@ def __set_point(geo_data: Dict[str, Any], point_id: int): """ # create point - point = Point(point_id) - point.coordinates = geo_data["points"][point.id] - return point + return Point.create(geo_data["points"][point_id],point_id) @staticmethod def __set_line(geo_data: Dict[str,Any], line_id: int): @@ -257,8 +324,8 @@ def __set_line(geo_data: Dict[str,Any], line_id: int): points = [] # create line and lower dimensional objects - line = Line(abs(line_id)) - line.point_ids = geo_data["lines"][line.id] + line_id = abs(line_id) + line = Line.create(geo_data["lines"][line_id], line_id) for point_id in line.point_ids: points.append(Geometry.__set_point(geo_data, point_id)) return line, points @@ -281,8 +348,8 @@ def __create_surface(geo_data: Dict[str, Any], surface_id: int): lines = [] # create surface and lower dimensional objects - surface = Surface(abs(surface_id)) - surface.line_ids = geo_data["surfaces"][surface.id] + surface_id = abs(surface_id) + surface = Surface.create(geo_data["surfaces"][surface_id], surface_id) for line_id in surface.line_ids: line, line_points = Geometry.__set_line(geo_data, line_id) @@ -337,8 +404,7 @@ def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: s elif ndim_group == 3: # Create volumes and lower dimensional objects for id in group_data["geometry_ids"]: - volume = Volume(id) - volume.surface_ids = geo_data["volumes"][volume.id] + volume = Volume.create(geo_data["volumes"][id], id) # create surfaces and lower dimensional objects which are part of the current volume for surface_id in volume.surface_ids: diff --git a/stem/model.py b/stem/model.py index 1f05aba80..e050653f5 100644 --- a/stem/model.py +++ b/stem/model.py @@ -1,6 +1,8 @@ -from typing import List +from typing import List, Sequence, Dict, Any from stem.model_part import ModelPart, BodyModelPart +from stem.soil_material import * +from stem.structural_material import * from gmsh_utils import gmsh_IO @@ -21,37 +23,96 @@ def __init__(self): self.solver = None self.geometry = None self.mesh = None + self.gmsh_io = gmsh_IO.GmshIO() self.body_model_parts: List[BodyModelPart] = [] self.process_model_parts: List[ModelPart] = [] + self.extrusion_length: Optional[Sequence[float]] = None - def add_soil_layer(self, coordinates, material_parameters, name, extrusion_length=None): + def get_geometry_from_geo_data(self, geo_data: Dict[str, Any]): + """ + Get the geometry from the geo_data and set the nodes and elements attributes. + + Args: + - geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io + + """ + + print("Getting geometry from geo data is not implemented yet") + + def add_soil_layer(self, coordinates: Sequence[Sequence[float]], + material_parameters: Union[SoilMaterial, StructuralMaterial], name: str, + ): """ Adds a soil layer to the model. Args: - - coordinates (np.array): The coordinates of the soil layer. - - material_parameters (dict): A dictionary containing the material parameters. + - coordinates (Sequence[Sequence[float]]): The coordinates of the soil layer. + - material_parameters (Union[:class:`stem.soil_material.SoilMaterial`, \ + :class:`stem.structural_material.StructuralMaterial`]): The material parameters of the soil layer. + - name (str): The name of the soil layer. """ - gmsh_io = gmsh_IO.GmshIO() - if self.ndim == 2: - gmsh_io.make_geometry_2d(coordinates, name) - if self.ndim == 3 and extrusion_length is None: - raise ValueError("extrusion_length must be specified for 3D models") + # check if extrusion length is specified in 3D + if self.ndim == 3: + if self.extrusion_length is None: + raise ValueError("Extrusion length must be specified for 3D models") + else: + extrusion_length = self.extrusion_length + else: + # in 2D extrusion length is not needed + extrusion_length = [0, 0, 0] + + #todo check if this function in gmsh io can be improved + self.gmsh_io.generate_geometry([coordinates], extrusion_length, self.ndim, "", [name]) + # create body model part body_model_part = BodyModelPart() body_model_part.name = name body_model_part.material = material_parameters - body_model_part.get_geometry_from_geo_data(gmsh_io.geo_data, name) + # set the geometry of the body model part + body_model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, name) self.body_model_parts.append(body_model_part) + def synchronise_geometry(self): + """ + Synchronise the geometry of the model with the geometry of the model parts. + + """ + + # synchronize gmsh and extract geo data + self.gmsh_io.synchronize_gmsh() + self.gmsh_io.extract_geo_data() + + # collect all model parts + all_model_parts: List[Union[BodyModelPart, ModelPart]] = [] + all_model_parts.extend(self.body_model_parts) + all_model_parts.extend(self.process_model_parts) + + # Get the geometry from the geo_data for each model part + for model_part in all_model_parts: + model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, model_part.name) + + # get the complete geometry + self.get_geometry_from_geo_data(self.gmsh_io.geo_data) if __name__ == '__main__': - coordinates = [[0, 0,0], [1, 0,0], [1, 1,0], [0, 1,0]] + coordinates = [[0, 0,0], [1, 0,0], [1, 1,0], [0, 1, 0]] + + soil_formulation = OnePhaseSoil(2,IS_DRAINED=True,DENSITY_SOLID=2650, POROSITY=0.3) + constitutive_law = LinearElasticSoil(YOUNG_MODULUS=100e6, POISSON_RATIO=0.3) + + soil_material = SoilMaterial(name="soil", soil_formulation=soil_formulation, constitutive_law=constitutive_law, + retention_parameters=SaturatedBelowPhreaticLevelLaw()) + + model = Model() + model.ndim = 2 + model.add_soil_layer(coordinates, soil_material, "soil") + + a=1+1 diff --git a/tests/test_model.py b/tests/test_model.py new file mode 100644 index 000000000..1ae2d3895 --- /dev/null +++ b/tests/test_model.py @@ -0,0 +1,199 @@ +from typing import Tuple + +import pytest +from gmsh_utils.gmsh_IO import GmshIO + +from stem.model import * +from stem.geometry import * + + +class TestGeometry: + + @pytest.fixture + def expected_geo_data_0D(self): + """ + Expected geometry data for a 0D geometry group. The group is a geometry of a point + + Returns: + - expected_geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io + """ + expected_points = {1: [0, 0, 0], 2: [0.5, 0, 0]} + return {"points": expected_points} + + @pytest.fixture + def expected_geometry_single_layer_2D(self): + + geometry= Geometry() + + geometry.points = [Point.create([0,0,0], 1), + Point.create([1,0,0], 2), + Point.create([1,1,0], 3), + Point.create([0,1,0], 4)] + + geometry.lines = [Line.create([1,2], 1), + Line.create([2,3], 2), + Line.create([3,4], 3), + Line.create([4,1], 4)] + + geometry.surfaces = [Surface.create([1,2,3,4], 1)] + + geometry.volumes = [] + + return geometry + + + @pytest.fixture + def expected_geometry_two_layers_2D(self): + + geometry_1 = Geometry() + + geometry_1.points = [Point.create([0, 0, 0], 1), + Point.create([1, 0, 0], 2), + Point.create([1, 1, 0], 3), + Point.create([0, 1, 0], 4)] + + geometry_1.lines = [Line.create([1, 2], 1), + Line.create([2, 3], 2), + Line.create([3, 4], 3), + Line.create([4, 1], 4)] + + geometry_1.surfaces = [Surface.create([1, 2, 3, 4], 1)] + + geometry_1.volumes = [] + + geometry_2 = Geometry() + geometry_2.points = [Point.create([1, 1, 0], 3), + Point.create([0, 1, 0], 4), + Point.create([0, 2, 0], 5), + Point.create([1, 2, 0], 6)] + + geometry_2.lines = [Line.create([3, 4], 3), + Line.create([4, 5], 5), + Line.create([5, 6], 6), + Line.create([6, 3], 7)] + + geometry_2.surfaces = [Surface.create([3, 5, 6, 7], 2)] + + geometry_2.volumes = [] + + return geometry_1, geometry_2 + + @pytest.fixture + def create_default_2d_soil_material(self): + # define soil material + ndim=2 + soil_formulation = OnePhaseSoil(ndim, IS_DRAINED=True, DENSITY_SOLID=2650, POROSITY=0.3) + constitutive_law = LinearElasticSoil(YOUNG_MODULUS=100e6, POISSON_RATIO=0.3) + soil_material = SoilMaterial(name="soil", soil_formulation=soil_formulation, constitutive_law=constitutive_law, + retention_parameters=SaturatedBelowPhreaticLevelLaw()) + return soil_material + + def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geometry, + create_default_2d_soil_material: SoilMaterial): + """ + Test if a single soil layer is added correctly to the model in a 2D space. A single soil layer is generated + and a single soil material is created and added to the model. + + Args: + - expected_geometry_single_layer_2D (Geometry): expected geometry of the model + - create_default_2d_soil_material (SoilMaterial): default soil material + + """ + + ndim = 2 + + layer_coordinates = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)] + + # define soil material + soil_material = create_default_2d_soil_material + + # create model + model = Model() + model.ndim = ndim + + # add soil layer + model.add_soil_layer(layer_coordinates, soil_material, "soil1") + + # check if layer is added correctly + assert len(model.body_model_parts) == 1 + assert model.body_model_parts[0].name == "soil1" + assert model.body_model_parts[0].material == soil_material + + # check if geometry is added correctly + generated_geometry = model.body_model_parts[0].geometry + expected_geometry = expected_geometry_single_layer_2D + + # check if points are added correctly + for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + + def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry], + create_default_2d_soil_material: SoilMaterial): + """ + Test if multiple soil layers are added correctly to the model in a 2D space. Multiple soil layers are generated + and multiple soil materials are created and added to the model. + + """ + + ndim = 2 + + layer1_coordinates = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)] + layer2_coordinates = [(1, 1, 0), (0, 1, 0), (0, 2, 0), (1, 2, 0)] + + # define soil materials + soil_material1 = create_default_2d_soil_material + soil_material1.name = "soil1" + + soil_material2 = create_default_2d_soil_material + soil_material2.name = "soil2" + + # create model + model = Model() + model.ndim = ndim + + # add soil layers + model.add_soil_layer(layer1_coordinates, soil_material1, "layer1") + model.add_soil_layer(layer2_coordinates, soil_material2, "layer2") + + # check if layers are added correctly + assert len(model.body_model_parts) == 2 + assert model.body_model_parts[0].name == "layer1" + assert model.body_model_parts[0].material == soil_material1 + assert model.body_model_parts[1].name == "layer2" + assert model.body_model_parts[1].material == soil_material2 + + # check if geometry is added correctly for each layer + for i in range(len(model.body_model_parts)): + generated_geometry = model.body_model_parts[i].geometry + expected_geometry = expected_geometry_two_layers_2D[i] + + # check if points are added correctly + for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + + + From e29e362b263e7d254c3fdc52f246c0a113703ad5 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 11 Jul 2023 21:27:18 +0200 Subject: [PATCH 016/116] added a todo refering to an issue --- stem/model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stem/model.py b/stem/model.py index e050653f5..63d5ac246 100644 --- a/stem/model.py +++ b/stem/model.py @@ -33,6 +33,8 @@ def get_geometry_from_geo_data(self, geo_data: Dict[str, Any]): """ Get the geometry from the geo_data and set the nodes and elements attributes. + #todo implement this function [#58] + Args: - geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io @@ -64,7 +66,7 @@ def add_soil_layer(self, coordinates: Sequence[Sequence[float]], # in 2D extrusion length is not needed extrusion_length = [0, 0, 0] - #todo check if this function in gmsh io can be improved + # todo check if this function in gmsh io can be improved self.gmsh_io.generate_geometry([coordinates], extrusion_length, self.ndim, "", [name]) # create body model part From 0c8b780cb86518d72bd64159ff707db0a79feb27 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 11 Jul 2023 21:52:47 +0200 Subject: [PATCH 017/116] added function and test to create geometry from geo_data --- stem/geometry.py | 37 +++++++++++++++++++++++++++++++++++++ stem/model.py | 18 ++++++++++-------- tests/test_geometry.py | 29 +++++++++++++++++++++++++++++ tests/test_model.py | 6 ++---- 4 files changed, 78 insertions(+), 12 deletions(-) diff --git a/stem/geometry.py b/stem/geometry.py index 37b09e231..c4424a33a 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -358,6 +358,43 @@ def __create_surface(geo_data: Dict[str, Any], surface_id: int): return surface, lines, points + @classmethod + def create_geometry_from_geo_data(cls, geo_data: Dict[str,Any]): + """ + Creates the geometry from gmsh geo_data + + Args: + - geo_data (Dict[str, Any]): A dictionary containing the geometry data as provided by gmsh_utils. + + Returns: + - geometry (:class:`Geometry`): The geometry object. + """ + + # initialise geometry lists + points = [] + lines = [] + surfaces = [] + volumes = [] + + # add volumes to geometry + for key, value in geo_data["volumes"].items(): + volumes.append(Volume.create(value,key)) + + # add surfaces to geometry + for key, value in geo_data["surfaces"].items(): + surfaces.append(Surface.create(value, key)) + + # add lines to geometry + for key, value in geo_data["lines"].items(): + lines.append(Line.create(value,key)) + + # add points to geometry + for key, value in geo_data["points"].items(): + points.append(Point.create(value,key)) + + # create the geometry class + return cls(points, lines, surfaces, volumes) + @classmethod def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: str): """ diff --git a/stem/model.py b/stem/model.py index 63d5ac246..346c4b54b 100644 --- a/stem/model.py +++ b/stem/model.py @@ -1,27 +1,31 @@ from typing import List, Sequence, Dict, Any +from gmsh_utils import gmsh_IO + from stem.model_part import ModelPart, BodyModelPart from stem.soil_material import * from stem.structural_material import * +from stem.geometry import Geometry -from gmsh_utils import gmsh_IO class Model: """ A class to represent the main model. Attributes: + - ndim (int): Number of dimensions of the model - project_parameters (dict): A dictionary containing the project parameters. - solver (Solver): The solver used to solve the problem. + - geometry (Optional[:class:`Geometry`]) The geometry of the whole model. - body_model_parts (list): A list containing the body model parts. - process_model_parts (list): A list containing the process model parts. """ - def __init__(self): - self.ndim = None + def __init__(self, ndim: int): + self.ndim: int = ndim self.project_parameters = None self.solver = None - self.geometry = None + self.geometry: Optional[Geometry] = None self.mesh = None self.gmsh_io = gmsh_IO.GmshIO() self.body_model_parts: List[BodyModelPart] = [] @@ -31,16 +35,14 @@ def __init__(self): def get_geometry_from_geo_data(self, geo_data: Dict[str, Any]): """ - Get the geometry from the geo_data and set the nodes and elements attributes. - - #todo implement this function [#58] + Get the geometry from the geo_data as generated by gmsh_io. Args: - geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io """ - print("Getting geometry from geo data is not implemented yet") + self.geometry = Geometry.create_geometry_from_geo_data(geo_data) def add_soil_layer(self, coordinates: Sequence[Sequence[float]], material_parameters: Union[SoilMaterial, StructuralMaterial], name: str, diff --git a/tests/test_geometry.py b/tests/test_geometry.py index ff6906ab6..388cc6d98 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -182,4 +182,33 @@ def test_create_3d_geometry_from_gmsh_group(self, expected_geo_data_3D: Dict[str for volume in geometry.volumes: assert volume.surface_ids == expected_geo_data_3D["volumes"][volume.id] + def test_create_geometry_from_geo_data(self, expected_geo_data_3D): + """ + Test the creation of a 3D geometry from a geo_data dictionary. + + Args: + - expected_geo_data_3D (Dict[int, Any]): expected geometry data for a 3D geometry group. + + """ + + geo_data = expected_geo_data_3D + + # Create the geometry from the gmsh group + geometry = Geometry().create_geometry_from_geo_data(geo_data) + + # Assert that the geometry is created correctly + assert len(geometry.points) == len(expected_geo_data_3D["points"]) + for point in geometry.points: + assert pytest.approx(point.coordinates) == expected_geo_data_3D["points"][point.id] + + assert len(geometry.lines) == len(expected_geo_data_3D["lines"]) + for line in geometry.lines: + assert line.point_ids == expected_geo_data_3D["lines"][line.id] + assert len(geometry.surfaces) == len(expected_geo_data_3D["surfaces"]) + for surface in geometry.surfaces: + assert surface.line_ids == expected_geo_data_3D["surfaces"][surface.id] + + assert len(geometry.volumes) == len(expected_geo_data_3D["volumes"]) + for volume in geometry.volumes: + assert volume.surface_ids == expected_geo_data_3D["volumes"][volume.id] \ No newline at end of file diff --git a/tests/test_model.py b/tests/test_model.py index 1ae2d3895..b29ce4b55 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -108,8 +108,7 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome soil_material = create_default_2d_soil_material # create model - model = Model() - model.ndim = ndim + model = Model(ndim) # add soil layer model.add_soil_layer(layer_coordinates, soil_material, "soil1") @@ -160,8 +159,7 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl soil_material2.name = "soil2" # create model - model = Model() - model.ndim = ndim + model = Model(ndim) # add soil layers model.add_soil_layer(layer1_coordinates, soil_material1, "layer1") From f1d015f2c0d9414aa4c2f471b47dec48f9719f0b Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 10:23:58 +0200 Subject: [PATCH 018/116] added function to create geometry from geo_file --- stem/model.py | 57 +++++++++++++++++++++++++++++++-------------- tests/test_model.py | 6 ++--- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/stem/model.py b/stem/model.py index 346c4b54b..81c76d0b9 100644 --- a/stem/model.py +++ b/stem/model.py @@ -16,9 +16,10 @@ class Model: - ndim (int): Number of dimensions of the model - project_parameters (dict): A dictionary containing the project parameters. - solver (Solver): The solver used to solve the problem. - - geometry (Optional[:class:`Geometry`]) The geometry of the whole model. + - geometry (Optional[:class:`stem.geometry.Geometry`]) The geometry of the whole model. - body_model_parts (list): A list containing the body model parts. - process_model_parts (list): A list containing the process model parts. + - extrusion_length(Optional[Sequence[float]]): The extrusion length in x,y and z direction """ def __init__(self, ndim: int): @@ -44,11 +45,47 @@ def get_geometry_from_geo_data(self, geo_data: Dict[str, Any]): self.geometry = Geometry.create_geometry_from_geo_data(geo_data) - def add_soil_layer(self, coordinates: Sequence[Sequence[float]], + def add_all_layers_from_geo_file(self, geo_file_name: str, body_names: Sequence[str]): + """ + Add all physical groups from a geo file to the model. The physical groups with the names in body_names are + added as body model parts, the other physical groups are added as process model parts. + + Args: + - geo_file_name (str): name of the geo file + - body_names (Sequence[str]): names of the physical groups which should be added as body model parts + + """ + + # read the geo file and generate the geo_data dictionary + self.gmsh_io.read_gmsh_geo(geo_file_name) + + # Reset the gmsh instance with the geo data, as read from the geo file + self.gmsh_io.generate_geo_from_geo_data() + + geo_data = self.gmsh_io.geo_data + + # Create geometry and model part for each physical group in the gmsh geo_data + for group_name in geo_data["physical_groups"].keys(): + if group_name in body_names: + model_part = BodyModelPart() + else: + model_part = ModelPart() + + model_part.name = group_name + model_part.get_geometry_from_geo_data(geo_data, group_name) + + # add model part to either body model parts or process model part + if isinstance(model_part, BodyModelPart): + self.body_model_parts.append(model_part) + else: + self.process_model_parts.append(model_part) + + def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], material_parameters: Union[SoilMaterial, StructuralMaterial], name: str, ): """ - Adds a soil layer to the model. + Adds a soil layer to the model by giving a sequence of 2D coordinates. In 3D the 2D geometry is extruded in + the direction of the extrusion_length Args: - coordinates (Sequence[Sequence[float]]): The coordinates of the soil layer. @@ -103,20 +140,6 @@ def synchronise_geometry(self): # get the complete geometry self.get_geometry_from_geo_data(self.gmsh_io.geo_data) -if __name__ == '__main__': - coordinates = [[0, 0,0], [1, 0,0], [1, 1,0], [0, 1, 0]] - - soil_formulation = OnePhaseSoil(2,IS_DRAINED=True,DENSITY_SOLID=2650, POROSITY=0.3) - constitutive_law = LinearElasticSoil(YOUNG_MODULUS=100e6, POISSON_RATIO=0.3) - - soil_material = SoilMaterial(name="soil", soil_formulation=soil_formulation, constitutive_law=constitutive_law, - retention_parameters=SaturatedBelowPhreaticLevelLaw()) - - model = Model() - model.ndim = 2 - model.add_soil_layer(coordinates, soil_material, "soil") - - a=1+1 diff --git a/tests/test_model.py b/tests/test_model.py index b29ce4b55..918d26ce7 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -111,7 +111,7 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome model = Model(ndim) # add soil layer - model.add_soil_layer(layer_coordinates, soil_material, "soil1") + model.add_soil_layer_by_coordinates(layer_coordinates, soil_material, "soil1") # check if layer is added correctly assert len(model.body_model_parts) == 1 @@ -162,8 +162,8 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl model = Model(ndim) # add soil layers - model.add_soil_layer(layer1_coordinates, soil_material1, "layer1") - model.add_soil_layer(layer2_coordinates, soil_material2, "layer2") + model.add_soil_layer_by_coordinates(layer1_coordinates, soil_material1, "layer1") + model.add_soil_layer_by_coordinates(layer2_coordinates, soil_material2, "layer2") # check if layers are added correctly assert len(model.body_model_parts) == 2 From 7a11f0fc82832d10e17b55b1e079a74b91ab4ae2 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 11:37:01 +0200 Subject: [PATCH 019/116] added test for creating geometry from geo_file --- stem/model.py | 4 +- tests/test_model.py | 131 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/stem/model.py b/stem/model.py index 81c76d0b9..cbddd728e 100644 --- a/stem/model.py +++ b/stem/model.py @@ -34,7 +34,7 @@ def __init__(self, ndim: int): self.extrusion_length: Optional[Sequence[float]] = None - def get_geometry_from_geo_data(self, geo_data: Dict[str, Any]): + def __get_geometry_from_geo_data(self, geo_data: Dict[str, Any]): """ Get the geometry from the geo_data as generated by gmsh_io. @@ -138,7 +138,7 @@ def synchronise_geometry(self): model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, model_part.name) # get the complete geometry - self.get_geometry_from_geo_data(self.gmsh_io.geo_data) + self.__get_geometry_from_geo_data(self.gmsh_io.geo_data) diff --git a/tests/test_model.py b/tests/test_model.py index 918d26ce7..673534d2a 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -88,6 +88,92 @@ def create_default_2d_soil_material(self): retention_parameters=SaturatedBelowPhreaticLevelLaw()) return soil_material + @pytest.fixture + def expected_geometry_two_layers_3D(self): + """ + Expected geometry data for a 3D geometry. The geometry is 2 stacked blocks, where the top and bottom blocks + are in different groups. + """ + + geometry_1 = Geometry() + geometry_1.volumes = [Volume.create([-10, 39, 26, 30, 34, 38], 1)] + geometry_1.surfaces = [Surface.create([5, 6, 7, 8], 10), + Surface.create([19, 20, 21, 22], 39), + Surface.create([5, 25, -19, -24], 26), + Surface.create([6, 29, -20, -25], 30), + Surface.create([7, 33, -21, -29], 34), + Surface.create([8, 24, -22, -33], 38) + ] + geometry_1.lines = [Line.create([1, 2], 5), + Line.create([2, 3], 6), + Line.create([3, 4], 7), + Line.create([4, 1], 8), + Line.create([13, 14], 19), + Line.create([14, 18], 20), + Line.create([18, 22], 21), + Line.create([22, 13], 22), + Line.create([2, 14], 25), + Line.create([1, 13], 24), + Line.create([3, 18], 29), + Line.create([4, 22], 33)] + + geometry_1.points = [Point.create([0., 0., 0.], 1), + Point.create([0.5, 0., 0.], 2), + Point.create([0.5, 1., 0.], 3), + Point.create([0., 1., 0.], 4), + Point.create([0., 0., -0.5], 13), + Point.create([0.5, 0., -0.5], 14), + Point.create([0.5, 1., -0.5], 18), + Point.create([0., 1., -0.5], 22)] + + + geometry_2 = Geometry() + geometry_2.volumes = [Volume.create([-17, 61, -48, -34, -56, -60], 2)] + + geometry_2.surfaces = [Surface.create([-13, -7, -15, -14],17), + Surface.create([41, -21, 43, 44], 61), + Surface.create([-13, 33, -41, -46], 48), + Surface.create([7, 33, -21, -29], 34), + Surface.create([-15, 55, -43, -29], 56), + Surface.create([-14, 46, -44, -55], 60)] + geometry_2.lines = [Line.create([4, 11], 13), + Line.create([3, 4], 7), + Line.create([12, 3], 15), + Line.create([11, 12], 14), + Line.create([23, 22], 41), + Line.create([18, 22], 21), + Line.create([18, 32], 43), + Line.create([32, 23], 44), + Line.create([4, 22], 33), + Line.create([11, 23], 46), + Line.create([3, 18], 29), + Line.create([12, 32], 55)] + + geometry_2.points = [Point.create([0., 1., 0.], 4), + Point.create([0., 2., 0.], 11), + Point.create([0.5, 1., 0.], 3), + Point.create([0.5, 2., 0.], 12), + Point.create([0., 2., -0.5], 23), + Point.create([0., 1., -0.5], 22), + Point.create([0.5, 1., -0.5], 18), + Point.create([0.5, 2., -0.5], 32)] + + expected_points = {1: [0., 0., 0.], 2: [0.5, 0., 0.], 3: [0.5, 1., 0.], 4: [0., 1., 0.], 11: [0., 2., 0.], + 12: [0.5, 2., 0.], 13: [0., 0., -0.5], 14: [0.5, 0., -0.5], 18: [0.5, 1., -0.5], + 22: [0., 1., -0.5], 23: [0., 2., -0.5], 32: [0.5, 2., -0.5]} + expected_lines = {5: [1, 2], 6: [2, 3], 7: [3, 4], 8: [4, 1], 13: [4, 11], 14: [11, 12], 15: [12, 3], + 19: [13, 14], 20: [14, 18], 21: [18, 22], 22: [22, 13], 24: [1, 13], 25: [2, 14], + 29: [3, 18], 33: [4, 22], 41: [23, 22], 43: [18, 32], 44: [32, 23], 46: [11, 23], + 55: [12, 32]} + expected_surfaces = {10: [5, 6, 7, 8], 17: [-13, -7, -15, -14], 26: [5, 25, -19, -24], 30: [6, 29, -20, -25], + 34: [7, 33, -21, -29], 38: [8, 24, -22, -33], 39: [19, 20, 21, 22], + 48: [-13, 33, -41, -46], 56: [-15, 55, -43, -29], 60: [-14, 46, -44, -55], + 61: [41, -21, 43, 44]} + expected_volumes = {1: [-10, 39, 26, 30, 34, 38], 2: [-17, 61, -48, -34, -56, -60]} + + + return geometry_1, geometry_2 + def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geometry, create_default_2d_soil_material: SoilMaterial): """ @@ -192,6 +278,51 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids + def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tuple[Geometry, Geometry]): + """ + Tests if all layers are added correctly to the model in a 3D space. A geo file is read and all layers are + added to the model. + + """ + + geo_file_name = "tests/test_data/gmsh_utils_column_3D_tetra4.geo" + + # create model + model = Model(ndim=3) + model.add_all_layers_from_geo_file(geo_file_name, ["group_1"]) + + # check if body model parts are added correctly + assert len(model.body_model_parts) == 1 + assert model.body_model_parts[0].name == "group_1" + + # check if process model part is added correctly + assert len(model.process_model_parts) == 1 + assert model.process_model_parts[0].name == "group_2" + + # check if geometry is added correctly + all_model_parts = [] + all_model_parts.extend(model.body_model_parts) + all_model_parts.extend(model.process_model_parts) + + # check if geometry is added correctly for each layer + for i in range(len(all_model_parts)): + generated_geometry = all_model_parts[i].geometry + expected_geometry = expected_geometry_two_layers_3D[i] + + # check if points are added correctly + for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids From c9176adbf4f45080ecb793ec31dad53439beb60e Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 11:53:07 +0200 Subject: [PATCH 020/116] corrected model tests --- tests/test_model.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index 673534d2a..7f9763198 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -7,7 +7,7 @@ from stem.geometry import * -class TestGeometry: +class TestModel: @pytest.fixture def expected_geo_data_0D(self): @@ -23,17 +23,17 @@ def expected_geo_data_0D(self): @pytest.fixture def expected_geometry_single_layer_2D(self): - geometry= Geometry() + geometry = Geometry() - geometry.points = [Point.create([0,0,0], 1), - Point.create([1,0,0], 2), - Point.create([1,1,0], 3), - Point.create([0,1,0], 4)] + geometry.points = [Point.create([0, 0, 0], 1), + Point.create([1, 0, 0], 2), + Point.create([1, 1, 0], 3), + Point.create([0, 1, 0], 4)] - geometry.lines = [Line.create([1,2], 1), - Line.create([2,3], 2), - Line.create([3,4], 3), - Line.create([4,1], 4)] + geometry.lines = [Line.create([1, 2], 1), + Line.create([2, 3], 2), + Line.create([3, 4], 3), + Line.create([4, 1], 4)] geometry.surfaces = [Surface.create([1,2,3,4], 1)] @@ -41,7 +41,6 @@ def expected_geometry_single_layer_2D(self): return geometry - @pytest.fixture def expected_geometry_two_layers_2D(self): @@ -223,6 +222,8 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids + # finalize gmsh + model.gmsh_io.finalize_gmsh() def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): @@ -278,6 +279,9 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids + # finalize gmsh + model.gmsh_io.finalize_gmsh() + def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tuple[Geometry, Geometry]): """ Tests if all layers are added correctly to the model in a 3D space. A geo file is read and all layers are From 7d8faade9472a58cf8b039544d3a1c31347dd1c9 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 11:59:21 +0200 Subject: [PATCH 021/116] solved mypy issues in geometry.py --- stem/geometry.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/stem/geometry.py b/stem/geometry.py index c4424a33a..e3dd01d7c 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -39,7 +39,7 @@ def __init__(self, id: int): id (int): The id of the point. """ self.__id: int = id - self.coordinates: List[float] = [] + self.coordinates: Sequence[float] = [] @classmethod def create(cls, coordinates: Sequence[float], id: int): @@ -102,7 +102,7 @@ def __init__(self, id: int): id (int): The id of the line. """ self.__id: int = id - self.point_ids: List[int] = [] + self.point_ids: Sequence[int] = [] @classmethod def create(cls, point_ids: Sequence[int], id: int): @@ -158,7 +158,7 @@ class Surface(GeometricalObjectABC): """ def __init__(self, id: int): self.__id: int = id - self.line_ids: List[int] = [] + self.line_ids: Sequence[int] = [] @property def id(self) -> int: @@ -199,6 +199,7 @@ def create(cls, line_ids: Sequence[int], id: int): surface.line_ids = line_ids return surface + class Volume(GeometricalObjectABC): """ A class to represent a volume in a three-dimensional space. @@ -213,7 +214,7 @@ class Volume(GeometricalObjectABC): """ def __init__(self, id: int): self.__id: int = id - self.surface_ids: List[int] = [] + self.surface_ids: Sequence[int] = [] @property def id(self) -> int: @@ -254,6 +255,7 @@ def create(cls, surface_ids: Sequence[int], id: int): volume.surface_ids = surface_ids return volume + class Geometry: """ A class to represent a collection of geometric objects in a zero-, one-, two- or three-dimensional space. From 88ae88623f12c7f4e83594d9260222c01fed4161 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 13:19:49 +0200 Subject: [PATCH 022/116] solved mypy issues in model.py --- stem/model.py | 17 +++++++++++++---- stem/model_part.py | 9 +++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/stem/model.py b/stem/model.py index cbddd728e..f97c3217d 100644 --- a/stem/model.py +++ b/stem/model.py @@ -1,4 +1,4 @@ -from typing import List, Sequence, Dict, Any +from typing import List, Sequence, Dict, Any, Optional, Union from gmsh_utils import gmsh_IO @@ -17,8 +17,8 @@ class Model: - project_parameters (dict): A dictionary containing the project parameters. - solver (Solver): The solver used to solve the problem. - geometry (Optional[:class:`stem.geometry.Geometry`]) The geometry of the whole model. - - body_model_parts (list): A list containing the body model parts. - - process_model_parts (list): A list containing the process model parts. + - body_model_parts (List[BodyModelPart]): A list containing the body model parts. + - process_model_parts (List[ModelPart]): A list containing the process model parts. - extrusion_length(Optional[Sequence[float]]): The extrusion length in x,y and z direction """ @@ -65,12 +65,17 @@ def add_all_layers_from_geo_file(self, geo_file_name: str, body_names: Sequence[ geo_data = self.gmsh_io.geo_data # Create geometry and model part for each physical group in the gmsh geo_data + model_part: Union[ModelPart, BodyModelPart] for group_name in geo_data["physical_groups"].keys(): + + # create model part, if the group name is in the body names, create a body model part, otherwise a process + # model part if group_name in body_names: model_part = BodyModelPart() else: model_part = ModelPart() + # set the name and geometry of the model part model_part.name = group_name model_part.get_geometry_from_geo_data(geo_data, group_name) @@ -135,7 +140,11 @@ def synchronise_geometry(self): # Get the geometry from the geo_data for each model part for model_part in all_model_parts: - model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, model_part.name) + # Check if all model parts have a name + if model_part.name is None: + raise ValueError("All model parts must have a name") + else: + model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, model_part.name) # get the complete geometry self.__get_geometry_from_geo_data(self.gmsh_io.geo_data) diff --git a/stem/model_part.py b/stem/model_part.py index 8ba1d5402..3ed8235fb 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -3,7 +3,8 @@ from stem.soil_material import SoilMaterial from stem.structural_material import StructuralMaterial -from stem.geometry import Geometry, Volume, Surface, Line, Point +from stem.geometry import Geometry + class ModelPart: """ @@ -11,7 +12,7 @@ class ModelPart: like excavation. Attributes: - - name (str): name of the model part + - name (Optional[str]): name of the model part - nodes (np.array or None): node id followed by node coordinates in an array - elements (np.array or None): element id followed by connectivities in an array - conditions (np.array or None): condition id followed by connectivities in an array @@ -19,7 +20,7 @@ class ModelPart: - parameters (dict): dictionary containing the model part parameters """ def __init__(self): - self.name = None + self.name: Optional[str] = None self.nodes = None self.elements = None self.conditions = None @@ -43,7 +44,7 @@ class BodyModelPart(ModelPart): """ This class contains model parts which are part of the body, e.g. a soil layer or track components. - Inheritance: + Inheritance: - :class:`ModelPart` Attributes: From 5f7d4f8067b688bd4b91008cc868d4bab61d06f3 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 13:54:11 +0200 Subject: [PATCH 023/116] added test for synchronising geometry --- stem/model.py | 3 +- tests/test_model.py | 113 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/stem/model.py b/stem/model.py index f97c3217d..330216f9d 100644 --- a/stem/model.py +++ b/stem/model.py @@ -125,7 +125,8 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], def synchronise_geometry(self): """ - Synchronise the geometry of the model with the geometry of the model parts. + Synchronise the geometry of all model parts and synchronise the geometry of the whole model. This function + recalculates all ids and connectivities of all geometrical entities. """ diff --git a/tests/test_model.py b/tests/test_model.py index 7f9763198..dc79d0c09 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -77,6 +77,44 @@ def expected_geometry_two_layers_2D(self): return geometry_1, geometry_2 + @pytest.fixture + def expected_geometry_two_layers_2D_after_sync(self): + + geometry_1 = Geometry() + + geometry_1.points = [Point.create([0, 0, 0], 1), + Point.create([1, 0, 0], 2), + Point.create([1, 1, 0], 3), + Point.create([0.5, 1, 0], 4), + Point.create([0, 1, 0], 5)] + + geometry_1.lines = [Line.create([1, 2], 1), + Line.create([2, 3], 2), + Line.create([3, 4], 3), + Line.create([4, 5], 4), + Line.create([5, 1], 5)] + + geometry_1.surfaces = [Surface.create([1, 2, 3, 4, 5], 1)] + + geometry_1.volumes = [] + + geometry_2 = Geometry() + geometry_2.points = [Point.create([1, 1, 0], 3), + Point.create([0.5, 1, 0], 4), + Point.create([0.5, 2, 0], 6), + Point.create([1, 2, 0], 7)] + + geometry_2.lines = [Line.create([3, 4], 3), + Line.create([4, 6], 6), + Line.create([6, 7], 7), + Line.create([7, 3], 8)] + + geometry_2.surfaces = [Surface.create([3, 6, 7, 8], 2)] + + geometry_2.volumes = [] + + return geometry_1, geometry_2 + @pytest.fixture def create_default_2d_soil_material(self): # define soil material @@ -157,20 +195,6 @@ def expected_geometry_two_layers_3D(self): Point.create([0.5, 1., -0.5], 18), Point.create([0.5, 2., -0.5], 32)] - expected_points = {1: [0., 0., 0.], 2: [0.5, 0., 0.], 3: [0.5, 1., 0.], 4: [0., 1., 0.], 11: [0., 2., 0.], - 12: [0.5, 2., 0.], 13: [0., 0., -0.5], 14: [0.5, 0., -0.5], 18: [0.5, 1., -0.5], - 22: [0., 1., -0.5], 23: [0., 2., -0.5], 32: [0.5, 2., -0.5]} - expected_lines = {5: [1, 2], 6: [2, 3], 7: [3, 4], 8: [4, 1], 13: [4, 11], 14: [11, 12], 15: [12, 3], - 19: [13, 14], 20: [14, 18], 21: [18, 22], 22: [22, 13], 24: [1, 13], 25: [2, 14], - 29: [3, 18], 33: [4, 22], 41: [23, 22], 43: [18, 32], 44: [32, 23], 46: [11, 23], - 55: [12, 32]} - expected_surfaces = {10: [5, 6, 7, 8], 17: [-13, -7, -15, -14], 26: [5, 25, -19, -24], 30: [6, 29, -20, -25], - 34: [7, 33, -21, -29], 38: [8, 24, -22, -33], 39: [19, 20, 21, 22], - 48: [-13, 33, -41, -46], 56: [-15, 55, -43, -29], 60: [-14, 46, -44, -55], - 61: [41, -21, 43, 44]} - expected_volumes = {1: [-10, 39, 26, 30, 34, 38], 2: [-17, 61, -48, -34, -56, -60]} - - return geometry_1, geometry_2 def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geometry, @@ -329,4 +353,65 @@ def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tup assert generated_surface.line_ids == expected_surface.line_ids + def test_synchronise_geometry(self, expected_geometry_two_layers_2D_after_sync: Tuple[Geometry, Geometry], + create_default_2d_soil_material: SoilMaterial): + """ + Test if the geometry is synchronised correctly after adding a new layer to the model. Where the new layer + overlaps with the existing layer, the existing layer is cut and the overlapping part is removed. + + Args: + - expected_geometry_two_layers_2D_after_sync (Tuple[Geometry, Geometry]): The expected geometry after \ + synchronising the geometry. + - create_default_2d_soil_material (SoilMaterial): A default soil material. + + """ + + # define layer coordinates + ndim = 2 + layer1_coordinates = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)] + layer2_coordinates = [(1, 1, 0), (0.5, 1, 0), (0.5, 2, 0), (1, 2, 0)] + + # define soil materials + soil_material1 = create_default_2d_soil_material + soil_material1.name = "soil1" + + soil_material2 = create_default_2d_soil_material + soil_material2.name = "soil2" + + # create model + model = Model(ndim) + + # add soil layers + model.add_soil_layer_by_coordinates(layer1_coordinates, soil_material1, "layer1") + model.add_soil_layer_by_coordinates(layer2_coordinates, soil_material2, "layer2") + + # synchronise geometry and recalculates the ids + model.synchronise_geometry() + + # check if geometry is added correctly for each layer + for i in range(len(model.body_model_parts)): + generated_geometry = model.body_model_parts[i].geometry + expected_geometry = expected_geometry_two_layers_2D_after_sync[i] + + # check if points are added correctly + for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + # finalize gmsh + model.gmsh_io.finalize_gmsh() + + + + From eb0035fa14887e4c8f65407fc9119ede77da5153 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 14:34:49 +0200 Subject: [PATCH 024/116] cleanup --- tests/test_geometry.py | 22 +++---- tests/test_model.py | 129 +++++++++++++++++++++++++++++++---------- 2 files changed, 108 insertions(+), 43 deletions(-) diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 388cc6d98..1a509a933 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -12,7 +12,7 @@ def expected_geo_data_0D(self): Expected geometry data for a 0D geometry group. The group is a geometry of a point Returns: - - expected_geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io + - Dict[str, Any]: dictionary containing the geometry data as generated by the gmsh_io """ expected_points = {1: [0, 0, 0], 2: [0.5, 0, 0]} return {"points": expected_points} @@ -24,7 +24,7 @@ def expected_geo_data_1D(self): Expected geometry data for a 1D geometry group. The group is a geometry of a line Returns: - - expected_geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io + - Dict[str, Any]: dictionary containing the geometry data as generated by the gmsh_io """ expected_points = {4: [0, 1.0, 0], 11: [0, 2.0, 0], 12: [0.5, 2.0, 0]} expected_lines = {13: [4, 11], 14: [11, 12]} @@ -38,7 +38,7 @@ def expected_geo_data_2D(self): Expected geometry data for a 2D geometry group. The group is a geometry of a square. Returns: - - expected_geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io + - Dict[str, Any]: dictionary containing the geometry data as generated by the gmsh_io """ expected_points = {3: [0.5, 1, 0], 4: [0, 1, 0], 11: [0, 2, 0], 12: [0.5, 2.0, 0]} expected_lines = { 7: [3, 4], 13: [4, 11], 14: [11, 12], 15: [12, 3]} @@ -54,7 +54,7 @@ def expected_geo_data_3D(self): Expected geometry data for a 3D geometry group. The group is a geometry of a cubic block Returns: - - expected_geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io + - Dict[str, Any]: dictionary containing the geometry data as generated by the gmsh_io """ expected_points = {3: [0.5, 1., 0.], 4: [0., 1., 0.], 11: [0., 2., 0.], 12: [0.5, 2., 0.], 18: [0.5, 1., -0.5], @@ -70,12 +70,12 @@ def expected_geo_data_3D(self): "surfaces": expected_surfaces, "volumes": expected_volumes} - def test_create_0d_geometry_from_gmsh_group(self, expected_geo_data_0D): + def test_create_0d_geometry_from_gmsh_group(self, expected_geo_data_0D: Dict[str, Any]): """ Test the creation of a 0D geometry from a gmsh group. Args: - - expected_geo_data_0D (Dict[int, Any]): expected geometry data for a 0D geometry group. + - expected_geo_data_0D (Dict[str, Any]): expected geometry data for a 0D geometry group. """ # Read the gmsh geo file @@ -91,7 +91,7 @@ def test_create_0d_geometry_from_gmsh_group(self, expected_geo_data_0D): for point in geometry.points: assert pytest.approx(point.coordinates) == expected_geo_data_0D["points"][point.id] - def test_create_1d_geometry_from_gmsh_group(self, expected_geo_data_1D): + def test_create_1d_geometry_from_gmsh_group(self, expected_geo_data_1D: Dict[str, Any]): """ Test the creation of a 1D geometry from a gmsh group. @@ -117,7 +117,7 @@ def test_create_1d_geometry_from_gmsh_group(self, expected_geo_data_1D): for line in geometry.lines: assert line.point_ids == expected_geo_data_1D["lines"][line.id] - def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D): + def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D: Dict[str, Any]): """ Test the creation of a 2D geometry from a gmsh group. @@ -153,7 +153,7 @@ def test_create_3d_geometry_from_gmsh_group(self, expected_geo_data_3D: Dict[str Test the creation of a 3D geometry from a gmsh group. Args: - - expected_geo_data_3D (Dict[int, Any]): expected geometry data for a 3D geometry group. + - expected_geo_data_3D (Dict[str, Any]): expected geometry data for a 3D geometry group. """ @@ -182,12 +182,12 @@ def test_create_3d_geometry_from_gmsh_group(self, expected_geo_data_3D: Dict[str for volume in geometry.volumes: assert volume.surface_ids == expected_geo_data_3D["volumes"][volume.id] - def test_create_geometry_from_geo_data(self, expected_geo_data_3D): + def test_create_geometry_from_geo_data(self, expected_geo_data_3D: Dict[str, Any]): """ Test the creation of a 3D geometry from a geo_data dictionary. Args: - - expected_geo_data_3D (Dict[int, Any]): expected geometry data for a 3D geometry group. + - expected_geo_data_3D (Dict[str, Any]): expected geometry data for a 3D geometry group. """ diff --git a/tests/test_model.py b/tests/test_model.py index dc79d0c09..3fdb2dc55 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,7 +1,6 @@ from typing import Tuple import pytest -from gmsh_utils.gmsh_IO import GmshIO from stem.model import * from stem.geometry import * @@ -15,13 +14,19 @@ def expected_geo_data_0D(self): Expected geometry data for a 0D geometry group. The group is a geometry of a point Returns: - - expected_geo_data (Dict[str, Any]): dictionary containing the geometry data as generated by the gmsh_io + - Dict[str, Any]: dictionary containing the geometry data as generated by the gmsh_io """ expected_points = {1: [0, 0, 0], 2: [0.5, 0, 0]} return {"points": expected_points} @pytest.fixture def expected_geometry_single_layer_2D(self): + """ + Sets expected geometry data for a 2D geometry group. The group is a geometry of a square. + + Returns: + - :class:`stem.geometry.Geometry`: geometry of a 2D square + """ geometry = Geometry() @@ -43,6 +48,14 @@ def expected_geometry_single_layer_2D(self): @pytest.fixture def expected_geometry_two_layers_2D(self): + """ + Sets expected geometries for 2 attached 2D squares. + + Returns: + - Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`]: \ + geometries of 2 attached 2D squares + + """ geometry_1 = Geometry() @@ -79,7 +92,15 @@ def expected_geometry_two_layers_2D(self): @pytest.fixture def expected_geometry_two_layers_2D_after_sync(self): + """ + Sets expected geometry of two model parts and the whole model after synchronising the geometry. + + Returns: + - Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`, \ + :class:`stem.geometry.Geometry`]: geometries of 2 attached 2D squares and the whole model + """ + # create expected geometry layer 1 geometry_1 = Geometry() geometry_1.points = [Point.create([0, 0, 0], 1), @@ -98,6 +119,7 @@ def expected_geometry_two_layers_2D_after_sync(self): geometry_1.volumes = [] + # create expected geometry layer 2 geometry_2 = Geometry() geometry_2.points = [Point.create([1, 1, 0], 3), Point.create([0.5, 1, 0], 4), @@ -113,10 +135,39 @@ def expected_geometry_two_layers_2D_after_sync(self): geometry_2.volumes = [] - return geometry_1, geometry_2 + # create expected full geometry + full_geometry = Geometry() + full_geometry.points = [Point.create([0, 0, 0], 1), + Point.create([1, 0, 0], 2), + Point.create([1, 1, 0], 3), + Point.create([0.5, 1, 0], 4), + Point.create([0, 1, 0], 5), + Point.create([0.5, 2, 0], 6), + Point.create([1, 2, 0], 7)] + + full_geometry.lines = [Line.create([1, 2], 1), + Line.create([2, 3], 2), + Line.create([3, 4], 3), + Line.create([4, 5], 4), + Line.create([5, 1], 5), + Line.create([4, 6], 6), + Line.create([6, 7], 7), + Line.create([7, 3], 8)] + + full_geometry.surfaces = [Surface.create([1, 2, 3, 4, 5], 1), + Surface.create([3, 6, 7, 8], 2)] + + return geometry_1, geometry_2, full_geometry @pytest.fixture def create_default_2d_soil_material(self): + """ + Create a default soil material for a 2D geometry. + + Returns: + :class:`stem.models.soil.SoilMaterial`: default soil material + + """ # define soil material ndim=2 soil_formulation = OnePhaseSoil(ndim, IS_DRAINED=True, DENSITY_SOLID=2650, POROSITY=0.3) @@ -130,6 +181,9 @@ def expected_geometry_two_layers_3D(self): """ Expected geometry data for a 3D geometry. The geometry is 2 stacked blocks, where the top and bottom blocks are in different groups. + + Returns: + Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`]: expected geometry data """ geometry_1 = Geometry() @@ -139,8 +193,8 @@ def expected_geometry_two_layers_3D(self): Surface.create([5, 25, -19, -24], 26), Surface.create([6, 29, -20, -25], 30), Surface.create([7, 33, -21, -29], 34), - Surface.create([8, 24, -22, -33], 38) - ] + Surface.create([8, 24, -22, -33], 38)] + geometry_1.lines = [Line.create([1, 2], 5), Line.create([2, 3], 6), Line.create([3, 4], 7), @@ -155,24 +209,24 @@ def expected_geometry_two_layers_3D(self): Line.create([4, 22], 33)] geometry_1.points = [Point.create([0., 0., 0.], 1), - Point.create([0.5, 0., 0.], 2), - Point.create([0.5, 1., 0.], 3), - Point.create([0., 1., 0.], 4), - Point.create([0., 0., -0.5], 13), - Point.create([0.5, 0., -0.5], 14), - Point.create([0.5, 1., -0.5], 18), - Point.create([0., 1., -0.5], 22)] - + Point.create([0.5, 0., 0.], 2), + Point.create([0.5, 1., 0.], 3), + Point.create([0., 1., 0.], 4), + Point.create([0., 0., -0.5], 13), + Point.create([0.5, 0., -0.5], 14), + Point.create([0.5, 1., -0.5], 18), + Point.create([0., 1., -0.5], 22)] geometry_2 = Geometry() geometry_2.volumes = [Volume.create([-17, 61, -48, -34, -56, -60], 2)] geometry_2.surfaces = [Surface.create([-13, -7, -15, -14],17), Surface.create([41, -21, 43, 44], 61), - Surface.create([-13, 33, -41, -46], 48), - Surface.create([7, 33, -21, -29], 34), - Surface.create([-15, 55, -43, -29], 56), - Surface.create([-14, 46, -44, -55], 60)] + Surface.create([-13, 33, -41, -46], 48), + Surface.create([7, 33, -21, -29], 34), + Surface.create([-15, 55, -43, -29], 56), + Surface.create([-14, 46, -44, -55], 60)] + geometry_2.lines = [Line.create([4, 11], 13), Line.create([3, 4], 7), Line.create([12, 3], 15), @@ -187,13 +241,13 @@ def expected_geometry_two_layers_3D(self): Line.create([12, 32], 55)] geometry_2.points = [Point.create([0., 1., 0.], 4), - Point.create([0., 2., 0.], 11), - Point.create([0.5, 1., 0.], 3), - Point.create([0.5, 2., 0.], 12), - Point.create([0., 2., -0.5], 23), - Point.create([0., 1., -0.5], 22), - Point.create([0.5, 1., -0.5], 18), - Point.create([0.5, 2., -0.5], 32)] + Point.create([0., 2., 0.], 11), + Point.create([0.5, 1., 0.], 3), + Point.create([0.5, 2., 0.], 12), + Point.create([0., 2., -0.5], 23), + Point.create([0., 1., -0.5], 22), + Point.create([0.5, 1., -0.5], 18), + Point.create([0.5, 2., -0.5], 32)] return geometry_1, geometry_2 @@ -204,8 +258,8 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome and a single soil material is created and added to the model. Args: - - expected_geometry_single_layer_2D (Geometry): expected geometry of the model - - create_default_2d_soil_material (SoilMaterial): default soil material + - expected_geometry_single_layer_2D (:class:`stem.geometry.Geometry`): expected geometry of the model + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): default soil material """ @@ -255,6 +309,11 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl Test if multiple soil layers are added correctly to the model in a 2D space. Multiple soil layers are generated and multiple soil materials are created and added to the model. + Args: + - expected_geometry_two_layers_2D (Tuple[:class:`stem.geometry.Geometry`, :class:`stem.geometry.Geometry`]): \ + expected geometry of the model + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): default soil material + """ ndim = 2 @@ -311,6 +370,10 @@ def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tup Tests if all layers are added correctly to the model in a 3D space. A geo file is read and all layers are added to the model. + Args: + - expected_geometry_two_layers_3D (Tuple[:class:`stem.geometry.Geometry`, :class:`stem.geometry.Geometry`]): \ + expected geometry of the model + """ geo_file_name = "tests/test_data/gmsh_utils_column_3D_tetra4.geo" @@ -352,7 +415,6 @@ def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tup assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - def test_synchronise_geometry(self, expected_geometry_two_layers_2D_after_sync: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): """ @@ -360,9 +422,10 @@ def test_synchronise_geometry(self, expected_geometry_two_layers_2D_after_sync: overlaps with the existing layer, the existing layer is cut and the overlapping part is removed. Args: - - expected_geometry_two_layers_2D_after_sync (Tuple[Geometry, Geometry]): The expected geometry after \ + - expected_geometry_two_layers_2D_after_sync (Tuple[:class:`stem.geometry.Geometry`, \ + :class:`stem.geometry.Geometry`, :class:`stem.geometry.Geometry`]): The expected geometry after \ synchronising the geometry. - - create_default_2d_soil_material (SoilMaterial): A default soil material. + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. """ @@ -388,10 +451,12 @@ def test_synchronise_geometry(self, expected_geometry_two_layers_2D_after_sync: # synchronise geometry and recalculates the ids model.synchronise_geometry() + # collect all generated geometries + generated_geometries = [model.body_model_parts[0].geometry, model.body_model_parts[1].geometry, model.geometry] + # check if geometry is added correctly for each layer - for i in range(len(model.body_model_parts)): - generated_geometry = model.body_model_parts[i].geometry - expected_geometry = expected_geometry_two_layers_2D_after_sync[i] + for generated_geometry, expected_geometry in zip(generated_geometries, + expected_geometry_two_layers_2D_after_sync): # check if points are added correctly for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): From 4213f54fe2439b5b126476c401da53afce3f5985 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 14:54:36 +0200 Subject: [PATCH 025/116] fixed test --- tests/test_model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 3fdb2dc55..75123c041 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -169,7 +169,7 @@ def create_default_2d_soil_material(self): """ # define soil material - ndim=2 + ndim = 2 soil_formulation = OnePhaseSoil(ndim, IS_DRAINED=True, DENSITY_SOLID=2650, POROSITY=0.3) constitutive_law = LinearElasticSoil(YOUNG_MODULUS=100e6, POISSON_RATIO=0.3) soil_material = SoilMaterial(name="soil", soil_formulation=soil_formulation, constitutive_law=constitutive_law, @@ -415,6 +415,9 @@ def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tup assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids + # finalize gmsh + model.gmsh_io.finalize_gmsh() + def test_synchronise_geometry(self, expected_geometry_two_layers_2D_after_sync: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): """ From 60294816ff88a1432c444d0fa40cdf6276ae5c42 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 15:04:48 +0200 Subject: [PATCH 026/116] corrected docstrings in geometry.py --- stem/geometry.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/stem/geometry.py b/stem/geometry.py index e3dd01d7c..d527ccc27 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -29,7 +29,7 @@ class Point(GeometricalObjectABC): Attributes: - __id (int): A unique identifier for the point. - - coordinates (List[float]): An iterable of floats representing the x, y and z coordinates of the point. + - coordinates (Sequence[float]): An sequence of floats representing the x, y and z coordinates of the point. """ def __init__(self, id: int): """ @@ -47,11 +47,11 @@ def create(cls, coordinates: Sequence[float], id: int): Creates a point object from a list of coordinates and an id. Args: - - coordinates (List[float]): An iterable of floats representing the x, y and z coordinates of the point. + - coordinates (Sequence[float]): An iterable of floats representing the x, y and z coordinates of the point. - id (int): The id of the point. Returns: - - Point: A point object. + - :class:`Point`: A point object. """ point = cls(id) @@ -90,7 +90,7 @@ class Line(GeometricalObjectABC): Attributes: - id (int): A unique identifier for the line. - - point_ids (List[int]): An Iterable of two integers representing the ids of the points that make up the\ + - point_ids (Sequence[int]): A sequence of two integers representing the ids of the points that make up the\ line. """ @@ -110,12 +110,12 @@ def create(cls, point_ids: Sequence[int], id: int): Creates a line object from a list of point ids and an id. Args: - - point_ids (List[int]): An Iterable of two integers representing the ids of the points that make up the\ + - point_ids (Sequence[int]): A sequence of two integers representing the ids of the points that make up the\ line. - id (int): The id of the line. Returns: - - Line: A line object. + - :class:`Line`: A line object. """ line = cls(id) @@ -153,7 +153,7 @@ class Surface(GeometricalObjectABC): Attributes: - __id (int): A unique identifier for the surface. - - line_ids (List[int]): An Iterable of three or more integers representing the ids of the lines that make\ + - line_ids (Sequence[int]): A sequence of three or more integers representing the ids of the lines that make\ up the surface. """ def __init__(self, id: int): @@ -187,12 +187,12 @@ def create(cls, line_ids: Sequence[int], id: int): Creates a surface object from a list of line ids and an id. Args: - - line_ids (List[int]): An Iterable of three or more integers representing the ids of the lines that make\ + - line_ids (Sequence[int]): A sequence of three or more integers representing the ids of the lines that make\ up the surface. - id (int): The id of the surface. Returns: - - Surface: A surface object. + - :class:`Surface`: A surface object. """ surface = cls(id) @@ -209,7 +209,7 @@ class Volume(GeometricalObjectABC): Attributes: - __id (int): A unique identifier for the volume. - - surface_ids (List[int]): An Iterable of four or more integers representing the ids of the surfaces that\ + - surface_ids (Sequence[int]): A sequence of four or more integers representing the ids of the surfaces that\ make up the volume. """ def __init__(self, id: int): @@ -243,12 +243,12 @@ def create(cls, surface_ids: Sequence[int], id: int): Creates a volume object from a list of surface ids and an id. Args: - - surface_ids (List[int]): An Iterable of four or more integers representing the ids of the surfaces that\ + - surface_ids (Sequence[int]): A sequence of four or more integers representing the ids of the surfaces that\ make up the volume. - id (int): The id of the volume. Returns: - - Volume: A volume object. + - :class:`Volume`: A volume object. """ volume = cls(id) @@ -282,7 +282,7 @@ def __get_unique_entities_by_ids(entities: Sequence[GeometricalObjectABC]): - entities (Sequence[:class:`GeometricalObjectABC`]): An Sequence of geometrical entities. Returns: - - unique_entities (List[:class:`GeometricalObjectABC`): A list of unique geometrical entities entities. + - Sequence[:class:`GeometricalObjectABC`]: A sequence of unique geometrical entities entities. """ unique_entity_ids = [] @@ -303,7 +303,7 @@ def __set_point(geo_data: Dict[str, Any], point_id: int): - point_id (int): The id of the line to create. Returns: - - point (:class:`Point`): The point object. + - :class:`Point`: The point object. """ # create point @@ -319,7 +319,7 @@ def __set_line(geo_data: Dict[str,Any], line_id: int): - line_id (int): The id of the line to create. Returns: - - line (:class:`Line`): The line object. + - Tuple[:class:`Line`, Sequence[:class:`Point`]]: The line object and the points that make up the line. """ # Initialise point list @@ -342,7 +342,8 @@ def __create_surface(geo_data: Dict[str, Any], surface_id: int): - surface_id (int): The id of the surface to create. Returns: - - surface (:class:`Surface`): The surface object. + - Tuple[:class:`Surface`, Sequence[:class:`Line`], Sequence[:class:`Point`]]: The surface object, \ + the lines that make up the surface and the points that make up the lines. """ # Initialise point and line lists @@ -369,7 +370,7 @@ def create_geometry_from_geo_data(cls, geo_data: Dict[str,Any]): - geo_data (Dict[str, Any]): A dictionary containing the geometry data as provided by gmsh_utils. Returns: - - geometry (:class:`Geometry`): The geometry object. + - :class:`Geometry`: The geometry object. """ # initialise geometry lists @@ -407,7 +408,7 @@ def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: s - group_name (str): The name of the group to create the geometry from. Returns: - - geometry (:class:`Geometry`): A Geometry object containing the geometric objects in the group. + - :class:`Geometry`: A Geometry object containing the geometric objects in the group. """ # initialize point, line, surface and volume lists From 6e0a47bdfbb6f5ace54453b19c8c9822084b012a Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 12 Jul 2023 15:08:03 +0200 Subject: [PATCH 027/116] added finalize gmsh to model destructor --- stem/model.py | 7 +++++++ tests/test_model.py | 17 ----------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/stem/model.py b/stem/model.py index 330216f9d..05f2a645d 100644 --- a/stem/model.py +++ b/stem/model.py @@ -34,6 +34,13 @@ def __init__(self, ndim: int): self.extrusion_length: Optional[Sequence[float]] = None + def __del__(self): + """ + Destructor of the Model class. Finalizes the gmsh_io instance. + + """ + self.gmsh_io.finalize_gmsh() + def __get_geometry_from_geo_data(self, geo_data: Dict[str, Any]): """ Get the geometry from the geo_data as generated by gmsh_io. diff --git a/tests/test_model.py b/tests/test_model.py index 75123c041..82d6d40fc 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -300,9 +300,6 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - # finalize gmsh - model.gmsh_io.finalize_gmsh() - def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): """ @@ -362,9 +359,6 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - # finalize gmsh - model.gmsh_io.finalize_gmsh() - def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tuple[Geometry, Geometry]): """ Tests if all layers are added correctly to the model in a 3D space. A geo file is read and all layers are @@ -415,9 +409,6 @@ def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tup assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - # finalize gmsh - model.gmsh_io.finalize_gmsh() - def test_synchronise_geometry(self, expected_geometry_two_layers_2D_after_sync: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): """ @@ -475,11 +466,3 @@ def test_synchronise_geometry(self, expected_geometry_two_layers_2D_after_sync: for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - - # finalize gmsh - model.gmsh_io.finalize_gmsh() - - - - - From e37a68511fc247a29b65ec7855af6d11163ea089 Mon Sep 17 00:00:00 2001 From: aronnoordam <51492202+aronnoordam@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:10:07 +0200 Subject: [PATCH 028/116] typo --- stem/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/geometry.py b/stem/geometry.py index d527ccc27..c78ac7972 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -29,7 +29,7 @@ class Point(GeometricalObjectABC): Attributes: - __id (int): A unique identifier for the point. - - coordinates (Sequence[float]): An sequence of floats representing the x, y and z coordinates of the point. + - coordinates (Sequence[float]): A sequence of floats representing the x, y and z coordinates of the point. """ def __init__(self, id: int): """ From 9debbcda4140fe2c92b1ea1afd1597604598dc96 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 14:06:40 +0200 Subject: [PATCH 029/116] Testing separate private function for mypy --- stem/IO/kratos_water_boundaries_io.py | 74 +++++++++++++++++---------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index 8521298df..ccf746270 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -4,20 +4,55 @@ class KratosWaterBoundariesIO: + """ + Class to create the water boundary process dictionary for the ProjectParameters.json file in Kratos + + + """ def __init__(self, domain: str): + """ + Constructor of KratosWaterBoundariesIO class + + Args: + domain: Name of the Kratos domain + + + """ self.domain = domain - def __water_boundary_dict(self, name: str, type: str, water_boundary: WaterBoundaryParameters) -> Dict[str, Any]: + def __create_phreatic_line_dict(self, name: str, type: str, water_boundary: PhreaticLine) -> Dict[str, Any]: + boundary_dict_phreatic_line: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": f"{self.domain}.{name}", + "variable_name": "WATER_PRESSURE", + "is_fixed": water_boundary.is_fixed, + "table": [0, 0], + "fluid_pressure_type": type, + "gravity_direction": water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.out_of_plane_direction, + "specific_weight": water_boundary.specific_weight, + "first_reference_coordinate": water_boundary.first_reference_coordinate, + "second_reference_coordinate": water_boundary.second_reference_coordinate, + "value": water_boundary.value, + } + } + return boundary_dict_phreatic_line + + def __create_water_boundary_dict(self, name: str, type: str, water_boundary: WaterBoundaryParameters) -> Dict[str, Any]: """ Creates a dictionary containing the water boundary parameters - Attributes: + Args: - name: name of the water boundary - type: type of the water boundary - water_boundary: water boundary object - Returns: None at the moment + Returns: + - Dict[str, Any]: dictionary containing the water boundary parameters """ if isinstance(water_boundary, PhreaticMultiLineBoundary): @@ -59,38 +94,23 @@ def __water_boundary_dict(self, name: str, type: str, water_boundary: WaterBound } return boundary_dict_interpolate elif isinstance(water_boundary, PhreaticLine): - boundary_dict_phreatic_line: Dict[str, Any] = { - "python_module": "apply_scalar_constraint_table_process", - "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", - "process_name": "ApplyScalarConstraintTableProcess", - "Parameters": { - "model_part_name": f"{self.domain}.{name}", - "variable_name": "WATER_PRESSURE", - "is_fixed": water_boundary.is_fixed, - "table": [0, 0], - "fluid_pressure_type": type, - "gravity_direction": water_boundary.gravity_direction, - "out_of_plane_direction": water_boundary.out_of_plane_direction, - "specific_weight": water_boundary.specific_weight, - "first_reference_coordinate": water_boundary.first_reference_coordinate, - "second_reference_coordinate": water_boundary.second_reference_coordinate, - "value": water_boundary.value, - } - } + temp_phreatic_line: PhreaticLine = water_boundary + boundary_dict_phreatic_line: Dict[str, Any] = self.__create_phreatic_line_dict(name, type, temp_phreatic_line) return boundary_dict_phreatic_line else: raise NotImplementedError("This type of boundary is not implemented") - def create_water_boundary_dict(self, water_boundary: WaterBoundary): + def create_water_boundary_dict(self, water_boundary: WaterBoundary) -> Dict[str, Any]: """ Creates a dictionary containing the water boundary parameters - Attributes: + Args: - water_boundary: water boundary object - Returns: dictionary containing the water boundary parameters + Returns: + - Dict[str, Any]: dictionary containing the water boundary parameters """ - return self.__water_boundary_dict(water_boundary.name, - water_boundary.water_boundary.type, - water_boundary.water_boundary) + return self.__create_water_boundary_dict(water_boundary.name, + water_boundary.water_boundary.type, + water_boundary.water_boundary) From bd9de505ffe23c51af849926cbb416f380bf4711 Mon Sep 17 00:00:00 2001 From: morettid Date: Thu, 13 Jul 2023 14:12:50 +0200 Subject: [PATCH 030/116] adjust testing for expected vs actual --- stem/IO/kratos_output_io.py | 1 + tests/test_default_materials.py | 2 +- tests/test_kratos_additional_processes_io.py | 2 +- tests/test_kratos_boundaries_io.py | 2 +- tests/test_kratos_loads_io.py | 2 +- tests/test_kratos_material_io.py | 4 ++-- tests/test_kratos_outputs_io.py | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/stem/IO/kratos_output_io.py b/stem/IO/kratos_output_io.py index b3bd525df..998563c0c 100644 --- a/stem/IO/kratos_output_io.py +++ b/stem/IO/kratos_output_io.py @@ -208,6 +208,7 @@ def __create_json_output_dict( "gauss_points_output_variables": [ op.name for op in output_parameters.gauss_point_results ], + "time_frequency": output_parameters.time_frequency, }, } return output_dict diff --git a/tests/test_default_materials.py b/tests/test_default_materials.py index 0182eabf6..eec2c5d06 100644 --- a/tests/test_default_materials.py +++ b/tests/test_default_materials.py @@ -40,5 +40,5 @@ def test_default_structural_materials(self): # compare json files using custom dictionary comparison TestUtils.assert_dictionary_almost_equal( - test_dict, expected_material_parameters_json + expected_material_parameters_json, test_dict ) diff --git a/tests/test_kratos_additional_processes_io.py b/tests/test_kratos_additional_processes_io.py index 98a96ed97..a283a46e0 100644 --- a/tests/test_kratos_additional_processes_io.py +++ b/tests/test_kratos_additional_processes_io.py @@ -50,5 +50,5 @@ def test_create_boundary_condition_dictionaries(self): # assert the objects to be equal TestUtils.assert_dictionary_almost_equal( - test_dictionary, expected_load_parameters_json + expected_load_parameters_json, test_dictionary ) \ No newline at end of file diff --git a/tests/test_kratos_boundaries_io.py b/tests/test_kratos_boundaries_io.py index 83b927ccd..95ccff749 100644 --- a/tests/test_kratos_boundaries_io.py +++ b/tests/test_kratos_boundaries_io.py @@ -68,5 +68,5 @@ def test_create_boundary_condition_dictionaries(self): # assert the objects to be equal TestUtils.assert_dictionary_almost_equal( - test_dictionary, expected_load_parameters_json + expected_load_parameters_json, test_dictionary ) \ No newline at end of file diff --git a/tests/test_kratos_loads_io.py b/tests/test_kratos_loads_io.py index f2d038891..9878359ce 100644 --- a/tests/test_kratos_loads_io.py +++ b/tests/test_kratos_loads_io.py @@ -69,5 +69,5 @@ def test_create_load_process_dict(self): # assert the objects to be equal TestUtils.assert_dictionary_almost_equal( - test_dictionary, expected_load_parameters_json + expected_load_parameters_json, test_dictionary ) diff --git a/tests/test_kratos_material_io.py b/tests/test_kratos_material_io.py index 4697c80ca..442d150ea 100644 --- a/tests/test_kratos_material_io.py +++ b/tests/test_kratos_material_io.py @@ -142,7 +142,7 @@ def test_write_soil_material_dict(self): # compare json files using custom dictionary comparison TestUtils.assert_dictionary_almost_equal( - test_dict, expected_material_parameters_json + expected_material_parameters_json, test_dict ) def test_write_structural_material_dict(self): @@ -216,5 +216,5 @@ def test_write_structural_material_dict(self): # compare json files using custom dictionary comparison TestUtils.assert_dictionary_almost_equal( - test_dict, expected_material_parameters_json + expected_material_parameters_json, test_dict ) diff --git a/tests/test_kratos_outputs_io.py b/tests/test_kratos_outputs_io.py index bf238651b..cd126a4af 100644 --- a/tests/test_kratos_outputs_io.py +++ b/tests/test_kratos_outputs_io.py @@ -131,5 +131,5 @@ def test_create_output_process_dictionary(self): # assert the objects to be equal TestUtils.assert_dictionary_almost_equal( - test_output, expected_load_parameters_json + expected_load_parameters_json, test_output ) From 3aa693ae14b9b61a2745f658e22f7b30ca3ca7c1 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 14:15:10 +0200 Subject: [PATCH 031/116] Testing separate private function for mypy --- stem/IO/kratos_water_boundaries_io.py | 118 ++++++++++++++++++-------- 1 file changed, 83 insertions(+), 35 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index ccf746270..c453c5b26 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -22,6 +22,19 @@ def __init__(self, domain: str): self.domain = domain def __create_phreatic_line_dict(self, name: str, type: str, water_boundary: PhreaticLine) -> Dict[str, Any]: + """ + Creates a dictionary containing the phreatic line parameters + + + Args: + - name: Name of the boundary + - type: Type of the boundary + - water_boundary: Phreatic line boundary object + + Returns: + - Dict[str, Any]: dictionary containing the phreatic line parameters + + """ boundary_dict_phreatic_line: Dict[str, Any] = { "python_module": "apply_scalar_constraint_table_process", "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", @@ -42,6 +55,71 @@ def __create_phreatic_line_dict(self, name: str, type: str, water_boundary: Phre } return boundary_dict_phreatic_line + def __create_phreatic_multi_line_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary) -> Dict[str, Any]: + """ + Creates a dictionary containing the phreatic multi line parameters + + Args: + - name: Name of the boundary + - type: Type of the boundary + - water_boundary: Multi line phreatic line boundary object + + Returns: + - Dict[str, Any]: dictionary containing the phreatic line parameters + + """ + parameters: Dict[str, Any] = { + "model_part_name": f"{self.domain}.{name}", + "variable_name": "WATER_PRESSURE", + "table": [0, 0, 0], + "value": water_boundary.water_pressure, + "is_fixed": water_boundary.is_fixed, + "gravity_direction": water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.out_of_plane_direction, + "fluid_pressure_type": type, + "specific_weight": water_boundary.specific_weight, + "x_coordinates": water_boundary.x_coordinates, + "y_coordinates": water_boundary.y_coordinates, + "z_coordinates": water_boundary.z_coordinates, + } + boundary_dict_multi_line: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": parameters, + } + return boundary_dict_multi_line + + def __create_interpolation_line_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary) -> Dict[str, Any]: + """ + Creates a dictionary containing the interpolation line parameters + + Args: + - name: Name of the boundary + - type: Type of the boundary + - water_boundary: Interpolation line boundary object + + Returns: + - Dict[str, Any]: dictionary containing the phreatic line parameters + + """ + + boundary_dict_interpolate: Dict[str, Any] = { + "python_module": "apply_scalar_constraint_table_process", + "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", + "process_name": "ApplyScalarConstraintTableProcess", + "Parameters": { + "model_part_name": f"{self.domain}.{name}", + "variable_name": "WATER_PRESSURE", + "is_fixed": water_boundary.is_fixed, + "table": 0, + "fluid_pressure_type": type, + "gravity_direction": water_boundary.gravity_direction, + "out_of_plane_direction": water_boundary.out_of_plane_direction, + } + } + return boundary_dict_interpolate + def __create_water_boundary_dict(self, name: str, type: str, water_boundary: WaterBoundaryParameters) -> Dict[str, Any]: """ Creates a dictionary containing the water boundary parameters @@ -56,43 +134,13 @@ def __create_water_boundary_dict(self, name: str, type: str, water_boundary: Wat """ if isinstance(water_boundary, PhreaticMultiLineBoundary): - parameters: Dict[str, Any] = { - "model_part_name": f"{self.domain}.{name}", - "variable_name": "WATER_PRESSURE", - "table": [0, 0, 0], - "value": water_boundary.water_pressure, - "is_fixed": water_boundary.is_fixed, - "gravity_direction": water_boundary.gravity_direction, - "out_of_plane_direction": water_boundary.out_of_plane_direction, - "fluid_pressure_type": type, - "specific_weight": water_boundary.specific_weight, - "x_coordinates": water_boundary.x_coordinates, - "y_coordinates": water_boundary.y_coordinates, - "z_coordinates": water_boundary.z_coordinates, - } - boundary_dict_multi_line: Dict[str, Any] = { - "python_module": "apply_scalar_constraint_table_process", - "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", - "process_name": "ApplyScalarConstraintTableProcess", - "Parameters": parameters, - } + temp_phreatic_multi_line: PhreaticMultiLineBoundary = water_boundary + boundary_dict_multi_line: Dict[str, Any] = self.__create_phreatic_multi_line_dict(name, type, temp_phreatic_multi_line) return boundary_dict_multi_line elif isinstance(water_boundary, InterpolateLineBoundary): - boundary_dict_interpolate: Dict[str, Any] = { - "python_module": "apply_scalar_constraint_table_process", - "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", - "process_name": "ApplyScalarConstraintTableProcess", - "Parameters": { - "model_part_name": f"{self.domain}.{name}", - "variable_name": "WATER_PRESSURE", - "is_fixed": water_boundary.is_fixed, - "table": 0, - "fluid_pressure_type": type, - "gravity_direction": water_boundary.gravity_direction, - "out_of_plane_direction": water_boundary.out_of_plane_direction, - } - } - return boundary_dict_interpolate + temp_interpolate_line: InterpolateLineBoundary = water_boundary + boundary_dict_interpolate_line: Dict[str, Any] = self.__create_interpolation_line_dict(name, type, temp_interpolate_line) + return boundary_dict_interpolate_line elif isinstance(water_boundary, PhreaticLine): temp_phreatic_line: PhreaticLine = water_boundary boundary_dict_phreatic_line: Dict[str, Any] = self.__create_phreatic_line_dict(name, type, temp_phreatic_line) From f66c240e210d50f2ad199102dca44d21b9828322 Mon Sep 17 00:00:00 2001 From: aronnoordam <51492202+aronnoordam@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:15:54 +0200 Subject: [PATCH 032/116] Update geometry.py --- stem/geometry.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stem/geometry.py b/stem/geometry.py index c78ac7972..73fab018e 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -44,7 +44,7 @@ def __init__(self, id: int): @classmethod def create(cls, coordinates: Sequence[float], id: int): """ - Creates a point object from a list of coordinates and an id. + Creates a point object from a list of coordinates and a point id. Args: - coordinates (Sequence[float]): An iterable of floats representing the x, y and z coordinates of the point. @@ -107,7 +107,7 @@ def __init__(self, id: int): @classmethod def create(cls, point_ids: Sequence[int], id: int): """ - Creates a line object from a list of point ids and an id. + Creates a line object from a list of point ids and a line id. Args: - point_ids (Sequence[int]): A sequence of two integers representing the ids of the points that make up the\ @@ -184,7 +184,7 @@ def id(self, value: int): @classmethod def create(cls, line_ids: Sequence[int], id: int): """ - Creates a surface object from a list of line ids and an id. + Creates a surface object from a list of line ids and a surface id. Args: - line_ids (Sequence[int]): A sequence of three or more integers representing the ids of the lines that make\ @@ -240,7 +240,7 @@ def id(self, value: int): @classmethod def create(cls, surface_ids: Sequence[int], id: int): """ - Creates a volume object from a list of surface ids and an id. + Creates a volume object from a list of surface ids and a volume id. Args: - surface_ids (Sequence[int]): A sequence of four or more integers representing the ids of the surfaces that\ From 76215f69b7c1c2842ddbec764f91d59066f65ef8 Mon Sep 17 00:00:00 2001 From: aronnoordam <51492202+aronnoordam@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:19:44 +0200 Subject: [PATCH 033/116] Update model.py --- stem/model.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stem/model.py b/stem/model.py index 05f2a645d..162b15d2c 100644 --- a/stem/model.py +++ b/stem/model.py @@ -15,11 +15,11 @@ class Model: Attributes: - ndim (int): Number of dimensions of the model - project_parameters (dict): A dictionary containing the project parameters. - - solver (Solver): The solver used to solve the problem. + - solver (:class:`stem.solver.Solver`): The solver used to solve the problem. - geometry (Optional[:class:`stem.geometry.Geometry`]) The geometry of the whole model. - - body_model_parts (List[BodyModelPart]): A list containing the body model parts. - - process_model_parts (List[ModelPart]): A list containing the process model parts. - - extrusion_length(Optional[Sequence[float]]): The extrusion length in x,y and z direction + - body_model_parts (List[:class:`stem.model_part.BodyModelPart`]): A list containing the body model parts. + - process_model_parts (List[:class:`stem.model_part.ModelPart`]): A list containing the process model parts. + - extrusion_length (Optional[Sequence[float]]): The extrusion length in x, y and z direction """ def __init__(self, ndim: int): @@ -100,7 +100,7 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], the direction of the extrusion_length Args: - - coordinates (Sequence[Sequence[float]]): The coordinates of the soil layer. + - coordinates (Sequence[Sequence[float]]): The plane coordinates of the soil layer. - material_parameters (Union[:class:`stem.soil_material.SoilMaterial`, \ :class:`stem.structural_material.StructuralMaterial`]): The material parameters of the soil layer. - name (str): The name of the soil layer. From 69471dcbed296c8f9c22b9263346d1907a211dce Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 14:27:18 +0200 Subject: [PATCH 034/116] Corrected according to reviewer's commends --- stem/IO/kratos_water_boundaries_io.py | 21 +++++++++----- stem/water_boundaries.py | 40 +++++++++++++-------------- tests/test_water_boundaries.py | 1 + 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/stem/IO/kratos_water_boundaries_io.py b/stem/IO/kratos_water_boundaries_io.py index c453c5b26..5bd94a5a9 100644 --- a/stem/IO/kratos_water_boundaries_io.py +++ b/stem/IO/kratos_water_boundaries_io.py @@ -1,6 +1,7 @@ from typing import Dict, Any -from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary, WaterBoundaryParameters, PhreaticLine +from stem.water_boundaries import WaterBoundary, PhreaticMultiLineBoundary, InterpolateLineBoundary, \ + WaterBoundaryParameters, PhreaticLine class KratosWaterBoundariesIO: @@ -55,7 +56,8 @@ def __create_phreatic_line_dict(self, name: str, type: str, water_boundary: Phre } return boundary_dict_phreatic_line - def __create_phreatic_multi_line_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary) -> Dict[str, Any]: + def __create_phreatic_multi_line_dict(self, name: str, type: str, water_boundary: PhreaticMultiLineBoundary) -> \ + Dict[str, Any]: """ Creates a dictionary containing the phreatic multi line parameters @@ -90,7 +92,8 @@ def __create_phreatic_multi_line_dict(self, name: str, type: str, water_boundary } return boundary_dict_multi_line - def __create_interpolation_line_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary) -> Dict[str, Any]: + def __create_interpolation_line_dict(self, name: str, type: str, water_boundary: InterpolateLineBoundary) -> Dict[ + str, Any]: """ Creates a dictionary containing the interpolation line parameters @@ -120,7 +123,8 @@ def __create_interpolation_line_dict(self, name: str, type: str, water_boundary: } return boundary_dict_interpolate - def __create_water_boundary_dict(self, name: str, type: str, water_boundary: WaterBoundaryParameters) -> Dict[str, Any]: + def __create_water_boundary_dict(self, name: str, type: str, water_boundary: WaterBoundaryParameters) -> Dict[ + str, Any]: """ Creates a dictionary containing the water boundary parameters @@ -135,15 +139,18 @@ def __create_water_boundary_dict(self, name: str, type: str, water_boundary: Wat """ if isinstance(water_boundary, PhreaticMultiLineBoundary): temp_phreatic_multi_line: PhreaticMultiLineBoundary = water_boundary - boundary_dict_multi_line: Dict[str, Any] = self.__create_phreatic_multi_line_dict(name, type, temp_phreatic_multi_line) + boundary_dict_multi_line: Dict[str, Any] = self.__create_phreatic_multi_line_dict(name, type, + temp_phreatic_multi_line) return boundary_dict_multi_line elif isinstance(water_boundary, InterpolateLineBoundary): temp_interpolate_line: InterpolateLineBoundary = water_boundary - boundary_dict_interpolate_line: Dict[str, Any] = self.__create_interpolation_line_dict(name, type, temp_interpolate_line) + boundary_dict_interpolate_line: Dict[str, Any] = self.__create_interpolation_line_dict(name, type, + temp_interpolate_line) return boundary_dict_interpolate_line elif isinstance(water_boundary, PhreaticLine): temp_phreatic_line: PhreaticLine = water_boundary - boundary_dict_phreatic_line: Dict[str, Any] = self.__create_phreatic_line_dict(name, type, temp_phreatic_line) + boundary_dict_phreatic_line: Dict[str, Any] = self.__create_phreatic_line_dict(name, type, + temp_phreatic_line) return boundary_dict_phreatic_line else: raise NotImplementedError("This type of boundary is not implemented") diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index bb2bb889f..68b77a98f 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -9,7 +9,7 @@ class WaterBoundaryParameters(ABC): """ Abstract base class for load water boundary parameters - Attributes: + Args: - surfaces_assigment (List[str]): List of surfaces to which the water boundary is assigned. - is_fixed (bool): True if the water boundary is fixed, False otherwise. - gravity_direction (int): Direction of the gravity vector. @@ -17,10 +17,10 @@ class WaterBoundaryParameters(ABC): """ - surfaces_assigment: List[str] = field(default_factory=lambda: [""]) - is_fixed: bool = True - gravity_direction: int = 1 - out_of_plane_direction: int = 2 + surfaces_assigment: List[str] + is_fixed: bool + gravity_direction: int + out_of_plane_direction: int @dataclass @@ -28,26 +28,25 @@ class PhreaticMultiLineBoundary(WaterBoundaryParameters): """ Class containing the load parameters for a phreatic line boundary condition - Attributes: + Args: - x_coordinates (List[float]): X coordinates of the phreatic line [m]. - y_coordinates (List[float]): Y coordinates of the phreatic line [m]. - z_coordinates (List[float]): Z coordinates of the phreatic line [m]. - - specific_weight (float): Specific weight of the water [kN/m3]. + - specific_weight (float): Specific weight of the water. + - water_pressure (float): Water pressure. """ - x_coordinates: List[float] = field(default_factory=lambda: [0.0]) - y_coordinates: List[float] = field(default_factory=lambda: [0.0]) + x_coordinates: List[float] + y_coordinates: List[float] + specific_weight: float + water_pressure: float z_coordinates: List[float] = field(default_factory=lambda: [0.0]) - specific_weight: float = 9.81 - water_pressure: float = 0.0 def __post_init__(self): """ Post initialization method of the class. It checks that the coordinates are of the same length. - Returns: None - """ # Check that the coordinates are of the same length @@ -71,7 +70,6 @@ class InterpolateLineBoundary(WaterBoundaryParameters): """ Class containing the boundary parameters for a interpolate line boundary condition. - """ pass @@ -86,7 +84,7 @@ class PhreaticLine(WaterBoundaryParameters): Class containing the boundary parameters for phreatic line boundary condition. This condition is should only contain two points. - Attributes: + Args: - first_reference_coordinate (List[float]): First reference coordinate of the phreatic line [m]. - second_reference_coordinate (List[float]): Second reference coordinate of the phreatic line [m]. - specific_weight (float): Specific weight of the water . @@ -94,10 +92,10 @@ class PhreaticLine(WaterBoundaryParameters): """ - first_reference_coordinate: List[float] = field(default_factory=lambda: [0.0]) - second_reference_coordinate: List[float] = field(default_factory=lambda: [0.0]) - specific_weight: float = 9.81 - value: float = 0.0 + first_reference_coordinate: List[float] + second_reference_coordinate: List[float] + specific_weight: float + value: float @property def type(self): @@ -114,7 +112,7 @@ class WaterBoundary: """ - def __init__(self, water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary, PhreaticLine], name: str): + def __init__(self, water_boundary_parameters: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary, PhreaticLine], name: str): """ Constructor of the class @@ -123,7 +121,7 @@ def __init__(self, water_boundary: Union[InterpolateLineBoundary, PhreaticMultiL """ - self.water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary, PhreaticLine] = water_boundary + self.water_boundary: Union[InterpolateLineBoundary, PhreaticMultiLineBoundary, PhreaticLine] = water_boundary_parameters self.type: str = self.water_boundary.type self.name: str = name diff --git a/tests/test_water_boundaries.py b/tests/test_water_boundaries.py index 290cdc7c4..44bc88f7f 100644 --- a/tests/test_water_boundaries.py +++ b/tests/test_water_boundaries.py @@ -2,6 +2,7 @@ from stem.water_boundaries import * + class TestWaterBoundaries: def test_raise_errors_for_water_boundaries(self): From f242e1f47bfbf6ba4467f04aa2ffabe5ed1c3969 Mon Sep 17 00:00:00 2001 From: aronnoordam <51492202+aronnoordam@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:29:06 +0200 Subject: [PATCH 035/116] Update model_part.py --- stem/model_part.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/stem/model_part.py b/stem/model_part.py index 3ed8235fb..351b8955a 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -13,20 +13,20 @@ class ModelPart: Attributes: - name (Optional[str]): name of the model part - - nodes (np.array or None): node id followed by node coordinates in an array - - elements (np.array or None): element id followed by connectivities in an array - - conditions (np.array or None): condition id followed by connectivities in an array + - nodes (None): node id followed by node coordinates in an array + - elements (None): element id followed by connectivities in an array + - conditions (None): condition id followed by connectivities in an array - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part - - parameters (dict): dictionary containing the model part parameters + - parameters (Dict[Any,Any]): dictionary containing the model part parameters """ def __init__(self): self.name: Optional[str] = None - self.nodes = None - self.elements = None - self.conditions = None + self.nodes = None # todo define type + self.elements = None # todo define type + self.conditions = None # todo define type self.geometry: Optional[Geometry] = None - self.parameters = {} + self.parameters = {} # todo define type def get_geometry_from_geo_data(self, geo_data: Dict[str, Any], name: str): """ @@ -49,10 +49,10 @@ class BodyModelPart(ModelPart): Attributes: - name (str): name of the model part - - nodes (np.array or None): node id followed by node coordinates in an array - - elements (np.array or None): element id followed by connectivities in an array - - conditions (np.array or None): condition id followed by connectivities in an array - - parameters (dict): dictionary containing the model part parameters + - nodes (None): node id followed by node coordinates in an array + - elements (None): element id followed by connectivities in an array + - conditions (None): condition id followed by connectivities in an array + - parameters (Dict[str, Any]): dictionary containing the model part parameters - material (Union[:class:`stem.soil_material.SoilMaterial`, \ :class:`stem.structural_material.StructuralMaterial`]): material of the model part """ From ed709c68cc7429fe26a20960135246b2ff3d9ef5 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 14:31:05 +0200 Subject: [PATCH 036/116] Corrected unit test --- tests/test_water_boundaries.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/test_water_boundaries.py b/tests/test_water_boundaries.py index 44bc88f7f..eb38ed2a3 100644 --- a/tests/test_water_boundaries.py +++ b/tests/test_water_boundaries.py @@ -7,12 +7,38 @@ class TestWaterBoundaries: def test_raise_errors_for_water_boundaries(self): - pytest.raises(ValueError, PhreaticMultiLineBoundary, x_coordinates=[0, 1, 2], y_coordinates=[0, 1, 2, 3]) + pytest.raises(ValueError, + PhreaticMultiLineBoundary, + x_coordinates=[0, 1, 2], + y_coordinates=[0, 1, 2, 3], + surfaces_assigment=["surface_1", "surface_2", "surface_3"], + is_fixed=True, + gravity_direction=1, + out_of_plane_direction=2, + specific_weight=9.81, + water_pressure=1000) - pytest.raises(ValueError, PhreaticMultiLineBoundary, x_coordinates=[0, 1, 2, 3, 4], y_coordinates=[0, 1, 2, 3]) + pytest.raises(ValueError, + PhreaticMultiLineBoundary, + x_coordinates=[0, 1, 2, 3, 4], + y_coordinates=[0, 1, 2, 3], + surfaces_assigment=["surface_1", "surface_2", "surface_3"], + is_fixed=True, + gravity_direction=1, + out_of_plane_direction=2, + specific_weight=9.81, + water_pressure=1000 + ) pytest.raises(ValueError, PhreaticMultiLineBoundary, x_coordinates=[0, 1, 2, 3], y_coordinates=[0, 1, 2, 3], - z_coordinates=[0, 1, 2, 3, 4]) \ No newline at end of file + z_coordinates=[0, 1, 2, 3, 4], + surfaces_assigment=["surface_1", "surface_2", "surface_3"], + is_fixed=True, + gravity_direction=1, + out_of_plane_direction=2, + specific_weight=9.81, + water_pressure=1000 + ) \ No newline at end of file From 5b42cf6cbe45ca692a32a865f65cb34baa038f0d Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 14:33:47 +0200 Subject: [PATCH 037/116] Corrected unit test --- tests/test_kratos_water_boundaries_io.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_kratos_water_boundaries_io.py b/tests/test_kratos_water_boundaries_io.py index 7348e0eb4..abe64d181 100644 --- a/tests/test_kratos_water_boundaries_io.py +++ b/tests/test_kratos_water_boundaries_io.py @@ -22,6 +22,7 @@ def test_create_water_boundary_process_dict(self): x_coordinates=[-40.0, -11.4, 0.0, 9.0, 21.5, 95.0], y_coordinates=[0.44, 0.44, 3.0, 3.0, -0.5, -0.5], surfaces_assigment=["domain a", "domain b", "domain c"], + specific_weight=10000.0, ) water_boundary = WaterBoundary(multi_line_boundary, name="water_soils_1") # use the kratos io to create the dictionary @@ -29,6 +30,9 @@ def test_create_water_boundary_process_dict(self): # set the interpolation type interpolation_type = InterpolateLineBoundary( surfaces_assigment=["domain d"], + is_fixed=True, + gravity_direction=1, + out_of_plane_direction=2, ) water_boundary_interpolate = WaterBoundary(interpolation_type, name="water_soils_2") # check phreatic line @@ -40,6 +44,7 @@ def test_create_water_boundary_process_dict(self): first_reference_coordinate=[0.0,1.0,0.0], second_reference_coordinate=[1.0,0.5,0.0], specific_weight=10000.0, + surfaces_assigment=["domain e"] ) water_boundary_phreatic_line = WaterBoundary(phreatic_line, name="water_soils_3") From 362b18b9bc649143b71102dc54331cd6ff9cbf59 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 14:34:41 +0200 Subject: [PATCH 038/116] Typo corrected --- tests/test_kratos_water_boundaries_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_kratos_water_boundaries_io.py b/tests/test_kratos_water_boundaries_io.py index abe64d181..c1af77702 100644 --- a/tests/test_kratos_water_boundaries_io.py +++ b/tests/test_kratos_water_boundaries_io.py @@ -43,7 +43,7 @@ def test_create_water_boundary_process_dict(self): value=0, first_reference_coordinate=[0.0,1.0,0.0], second_reference_coordinate=[1.0,0.5,0.0], - specific_weight=10000.0, + specific_weight=9.81, surfaces_assigment=["domain e"] ) water_boundary_phreatic_line = WaterBoundary(phreatic_line, name="water_soils_3") From 25899659e1862f9b1980bebcd6d20150d60ec65a Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 14:40:32 +0200 Subject: [PATCH 039/116] Typo corrected --- tests/test_kratos_water_boundaries_io.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_kratos_water_boundaries_io.py b/tests/test_kratos_water_boundaries_io.py index c1af77702..634f26fc9 100644 --- a/tests/test_kratos_water_boundaries_io.py +++ b/tests/test_kratos_water_boundaries_io.py @@ -22,7 +22,7 @@ def test_create_water_boundary_process_dict(self): x_coordinates=[-40.0, -11.4, 0.0, 9.0, 21.5, 95.0], y_coordinates=[0.44, 0.44, 3.0, 3.0, -0.5, -0.5], surfaces_assigment=["domain a", "domain b", "domain c"], - specific_weight=10000.0, + specific_weight=9.81, ) water_boundary = WaterBoundary(multi_line_boundary, name="water_soils_1") # use the kratos io to create the dictionary @@ -43,14 +43,14 @@ def test_create_water_boundary_process_dict(self): value=0, first_reference_coordinate=[0.0,1.0,0.0], second_reference_coordinate=[1.0,0.5,0.0], - specific_weight=9.81, + specific_weight=10000.0, surfaces_assigment=["domain e"] ) water_boundary_phreatic_line = WaterBoundary(phreatic_line, name="water_soils_3") # check the dictionary # read the expected dictionary from the json - with open("tests/test_data/expected_water_lines.json") as json_file: + with open("test_data/expected_water_lines.json") as json_file: expected_water_boundary_json = json.load(json_file) # compare the dictionaries TestUtils.assert_dictionary_almost_equal(expected_water_boundary_json['test'][0], From 7d6a909980c9c893e2e50dd3e0c336a8b493a776 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Thu, 13 Jul 2023 15:00:27 +0200 Subject: [PATCH 040/116] Path to file corrected --- tests/test_kratos_water_boundaries_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_kratos_water_boundaries_io.py b/tests/test_kratos_water_boundaries_io.py index 634f26fc9..5a6870e8e 100644 --- a/tests/test_kratos_water_boundaries_io.py +++ b/tests/test_kratos_water_boundaries_io.py @@ -50,7 +50,7 @@ def test_create_water_boundary_process_dict(self): # check the dictionary # read the expected dictionary from the json - with open("test_data/expected_water_lines.json") as json_file: + with open("tests/test_data/expected_water_lines.json") as json_file: expected_water_boundary_json = json.load(json_file) # compare the dictionaries TestUtils.assert_dictionary_almost_equal(expected_water_boundary_json['test'][0], From d7f3baf28c6dbfe7403d9a84bffb9fd1184148a0 Mon Sep 17 00:00:00 2001 From: noordam Date: Thu, 13 Jul 2023 15:26:54 +0200 Subject: [PATCH 041/116] added test_add_all_layers_from_geo_file_2D --- tests/test_data/gmsh_utils_two_blocks_2D.geo | 35 +++++++++++++++ tests/test_model.py | 47 +++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/test_data/gmsh_utils_two_blocks_2D.geo diff --git a/tests/test_data/gmsh_utils_two_blocks_2D.geo b/tests/test_data/gmsh_utils_two_blocks_2D.geo new file mode 100644 index 000000000..0a6847470 --- /dev/null +++ b/tests/test_data/gmsh_utils_two_blocks_2D.geo @@ -0,0 +1,35 @@ +// Gmsh project: created with gmsh-3.0.6-Windows64 + +// Create 2D square mesh +Mesh.ElementOrder = 1; +Point(1) = {0, 0, 0}; +Point(2) = {1, 0, 0}; +Point(3) = {1, 1, 0}; +Point(4) = {0, 1, 0}; + +// create lines +Line(1) = {1, 2}; +Line(2) = {2, 3}; +Line(3) = {3, 4}; +Line(4) = {4, 1}; + +// create surface +Line Loop(1) = {1, 2, 3, 4}; +Plane Surface(1) = 1; + +// create new points of second surface +Point(5) = {0, 2, 0}; +Point(6) = {1, 2, 0}; + +// create new lines +Line(5) = {4, 5}; +Line(6) = {5, 6}; +Line(7) = {6, 3}; + +// create second surface +Line Loop(2) = {3, 5, 6, 7}; +Plane Surface(2) = 2; + +// Define the physical groups +Physical Surface("group_1") = 1; +Physical Surface("group_2") = 2; diff --git a/tests/test_model.py b/tests/test_model.py index 82d6d40fc..2c3783385 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -359,7 +359,52 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - def test_add_all_layers_from_geo_file(self, expected_geometry_two_layers_3D: Tuple[Geometry, Geometry]): + def test_add_all_layers_from_geo_file_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry]): + """ + Tests if all layers are added correctly to the model in a 2D space. A geo file is read and all layers are + added to the model. + + Args: + - expected_geometry_two_layers_2D (Tuple[:class:`stem.geometry.Geometry`, :class:`stem.geometry.Geometry`]): \ + expected geometry of the model + + """ + + geo_file_name = "tests/test_data/gmsh_utils_two_blocks_2D.geo" + + # create model + model = Model(ndim=2) + model.add_all_layers_from_geo_file(geo_file_name, ["group_1"]) + + # check if body model parts are added correctly + assert len(model.body_model_parts) == 1 + assert model.body_model_parts[0].name == "group_1" + + # check if process model part is added correctly + assert len(model.process_model_parts) == 1 + assert model.process_model_parts[0].name == "group_2" + + # check if geometry is added correctly for each layer + for i in range(len(model.body_model_parts)): + generated_geometry = model.body_model_parts[i].geometry + expected_geometry = expected_geometry_two_layers_2D[i] + + # check if points are added correctly + for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + def test_add_all_layers_from_geo_file_3D(self, expected_geometry_two_layers_3D: Tuple[Geometry, Geometry]): """ Tests if all layers are added correctly to the model in a 3D space. A geo file is read and all layers are added to the model. From 65dca64d76ccfda03c383418cc100f33c1332da0 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 14 Jul 2023 16:55:25 +0200 Subject: [PATCH 042/116] added test for synchronizing 3D geometry --- .../expected_geometry_after_sync_3D.pickle | Bin 0 -> 3069 bytes tests/test_model.py | 163 +++++++++++++++++- 2 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 tests/test_data/expected_geometry_after_sync_3D.pickle diff --git a/tests/test_data/expected_geometry_after_sync_3D.pickle b/tests/test_data/expected_geometry_after_sync_3D.pickle new file mode 100644 index 0000000000000000000000000000000000000000..f0ab7a6157c715a9e120150e742065b7934bcd3e GIT binary patch literal 3069 zcmai#?Q0xW6vi{z*`2rDO_L@K2tq-SRxoIxP$>8Ug@rjS6bc2wu*PKc1e2^Lo4z1@ z@%ti3NBjc>K@bXsLO%(Gg5U@L20u$nLr@go;&bQB-N~8TwP6W6zx(XDXU;v(J-7WY z_mipOZ#h3P>g)`&&7}{s!DcqxzMhZ5p8a|!KlxdH1CrLqgY~W9P7XPHqvl2Pl#f^A zegXRZ^|hS5QL;K1Y_F|vtqil`7d_c(YRdhR?Y_^E?7?lC<_s#w58-Ps>|1xeYoYndT(In4NneO9Nm5qPRz)%ymnHq%BsQ)AnHE_=sTyUe zTINcut}t8!{Gz(l;B-E&2yPaBK@r%SUs9x9GyGenm&aB%g{ZboCdus;pyl0CUS6xc z=S1&Jk{^nm@hhehmC0c$?jgyFz?S^BA}|=IiojZYL6JFYp0;_1D%ug@3Dx+l@PHDn zUl87w9;)IT72c`yc^ki@<17mA)-RW1HjcSmGF~>-sQd)C@JGnMBBAtdM04q$X|bnH z??<9ILJR2=qj>BnVo@BWMd{FA97Q6EkY;I~j+r;9GHAO89m1~D%xf=SNvinu;^QL5x9>(R|FQ|uM~lYI5#I<>w6C!dlTbJ zKBqJofc%CcuIpS!xvsQD9LNt8fmiwdGu-IGjoJ&Ccg*;0 Date: Fri, 14 Jul 2023 17:31:37 +0200 Subject: [PATCH 043/116] added test for add_soil_layer_by_coordinates for a single 3D geometry --- tests/test_model.py | 168 +++++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 72 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index 37676d0f3..6ee4e2d38 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -47,6 +47,50 @@ def expected_geometry_single_layer_2D(self): return geometry + @pytest.fixture + def expected_geometry_single_layer_3D(self): + """ + Sets expected geometry data for a 2D geometry group. The group is a geometry of a square. + + Returns: + - :class:`stem.geometry.Geometry`: geometry of a 2D square + """ + + geometry = Geometry() + + geometry.points = [Point.create([0, 0, 0], 1), + Point.create([0, 0, 1], 5), + Point.create([1, 0, 1], 6), + Point.create([1, 0, 0], 2), + Point.create([1, 1, 1], 7), + Point.create([1, 1, 0], 3), + Point.create([0, 1, 1], 8), + Point.create([0, 1, 0], 4)] + + geometry.lines = [Line.create([1, 5], 5), + Line.create([5, 6], 7), + Line.create([2, 6], 6), + Line.create([1, 2], 1), + Line.create([6, 7], 9), + Line.create([3, 7], 8), + Line.create([2, 3], 2), + Line.create([7, 8], 11), + Line.create([4, 8], 10), + Line.create([3, 4], 3), + Line.create([8, 5], 12), + Line.create([4, 1], 4)] + + geometry.surfaces = [Surface.create([5, 7, -6, -1], 2), + Surface.create([6, 9, -8, -2], 3), + Surface.create([8,11, -10, -3], 4), + Surface.create([10, 12, -5, -4], 5), + Surface.create([1, 2, 3, 4], 1), + Surface.create([7, 9, 11, 12], 6)] + + geometry.volumes = [Volume.create([-2, -3, -4, -5, -1, 6], 1)] + + return geometry + @pytest.fixture def expected_geometry_two_layers_2D(self): """ @@ -269,102 +313,77 @@ def expected_geometry_two_layers_3D(self): return geometry_1, geometry_2 - @pytest.fixture - def expected_geometry_two_layers_3D_after_sync(self): + def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geometry, + create_default_2d_soil_material: SoilMaterial): """ - Expected geometry data for a 3D geometry. The geometry is 2 stacked blocks, where the top and bottom blocks - are in different groups. + Test if a single soil layer is added correctly to the model in a 2D space. A single soil layer is generated + and a single soil material is created and added to the model. + + Args: + - expected_geometry_single_layer_2D (:class:`stem.geometry.Geometry`): expected geometry of the model + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): default soil material - Returns: - Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`]: expected geometry data """ - geometry_1 = Geometry() - geometry_1.volumes = [Volume.create([-10, 39, 26, 30, 34, 38], 1)] - geometry_1.surfaces = [Surface.create([5, 6, 7, 8], 10), - Surface.create([19, 20, 21, 22], 39), - Surface.create([5, 25, -19, -24], 26), - Surface.create([6, 29, -20, -25], 30), - Surface.create([7, 33, -21, -29], 34), - Surface.create([8, 24, -22, -33], 38)] + ndim = 2 - geometry_1.lines = [Line.create([1, 2], 5), - Line.create([2, 3], 6), - Line.create([3, 4], 7), - Line.create([4, 1], 8), - Line.create([13, 14], 19), - Line.create([14, 18], 20), - Line.create([18, 22], 21), - Line.create([22, 13], 22), - Line.create([2, 14], 25), - Line.create([1, 13], 24), - Line.create([3, 18], 29), - Line.create([4, 22], 33)] + layer_coordinates = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)] - geometry_1.points = [Point.create([0., 0., 0.], 1), - Point.create([0.5, 0., 0.], 2), - Point.create([0.5, 1., 0.], 3), - Point.create([0., 1., 0.], 4), - Point.create([0., 0., -0.5], 13), - Point.create([0.5, 0., -0.5], 14), - Point.create([0.5, 1., -0.5], 18), - Point.create([0., 1., -0.5], 22)] + # define soil material + soil_material = create_default_2d_soil_material - geometry_2 = Geometry() - geometry_2.volumes = [Volume.create([-17, 61, -48, -34, -56, -60], 2)] + # create model + model = Model(ndim) - geometry_2.surfaces = [Surface.create([-13, -7, -15, -14], 17), - Surface.create([41, -21, 43, 44], 61), - Surface.create([-13, 33, -41, -46], 48), - Surface.create([7, 33, -21, -29], 34), - Surface.create([-15, 55, -43, -29], 56), - Surface.create([-14, 46, -44, -55], 60)] + # add soil layer + model.add_soil_layer_by_coordinates(layer_coordinates, soil_material, "soil1") - geometry_2.lines = [Line.create([4, 11], 13), - Line.create([3, 4], 7), - Line.create([12, 3], 15), - Line.create([11, 12], 14), - Line.create([23, 22], 41), - Line.create([18, 22], 21), - Line.create([18, 32], 43), - Line.create([32, 23], 44), - Line.create([4, 22], 33), - Line.create([11, 23], 46), - Line.create([3, 18], 29), - Line.create([12, 32], 55)] + # check if layer is added correctly + assert len(model.body_model_parts) == 1 + assert model.body_model_parts[0].name == "soil1" + assert model.body_model_parts[0].material == soil_material - geometry_2.points = [Point.create([0., 1., 0.], 4), - Point.create([0., 2., 0.], 11), - Point.create([0.5, 1., 0.], 3), - Point.create([0.5, 2., 0.], 12), - Point.create([0., 2., -0.5], 23), - Point.create([0., 1., -0.5], 22), - Point.create([0.5, 1., -0.5], 18), - Point.create([0.5, 2., -0.5], 32)] + # check if geometry is added correctly + generated_geometry = model.body_model_parts[0].geometry + expected_geometry = expected_geometry_single_layer_2D - return geometry_1, geometry_2 + # check if points are added correctly + for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geometry, - create_default_2d_soil_material: SoilMaterial): + # check if lines are added correctly + for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + def test_add_single_soil_layer_3D(self, expected_geometry_single_layer_3D: Geometry, + create_default_3d_soil_material: SoilMaterial): """ - Test if a single soil layer is added correctly to the model in a 2D space. A single soil layer is generated + Test if a single soil layer is added correctly to the model in a 3D space. A single soil layer is generated and a single soil material is created and added to the model. Args: - - expected_geometry_single_layer_2D (:class:`stem.geometry.Geometry`): expected geometry of the model - - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): default soil material + - expected_geometry_single_layer_3D (:class:`stem.geometry.Geometry`): expected geometry of the model + - create_default_3d_soil_material (:class:`stem.soil_material.SoilMaterial`): default soil material """ - ndim = 2 + ndim = 3 layer_coordinates = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)] # define soil material - soil_material = create_default_2d_soil_material + soil_material = create_default_3d_soil_material # create model model = Model(ndim) + model.extrusion_length = [0, 0, 1] # add soil layer model.add_soil_layer_by_coordinates(layer_coordinates, soil_material, "soil1") @@ -376,7 +395,7 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome # check if geometry is added correctly generated_geometry = model.body_model_parts[0].geometry - expected_geometry = expected_geometry_single_layer_2D + expected_geometry = expected_geometry_single_layer_3D # check if points are added correctly for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): @@ -393,6 +412,11 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids + # check if volumes are added correctly + for generated_volume, expected_volume in zip(generated_geometry.volumes, expected_geometry.volumes): + assert generated_volume.id == expected_volume.id + assert generated_volume.surface_ids == expected_volume.surface_ids + def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): """ @@ -632,7 +656,7 @@ def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMate # create model model = Model(ndim) - model.extrusion_length = [0,0,1] + model.extrusion_length = [0, 0, 1] # add soil layers model.add_soil_layer_by_coordinates(layer1_coordinates, soil_material1, "layer1") From 46a8d6a85729e52186c9dbf85e709838efc5f51d Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 18 Jul 2023 10:02:13 +0200 Subject: [PATCH 044/116] added test for adding multiple soil layers in 3D --- tests/test_model.py | 156 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 148 insertions(+), 8 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index 6ee4e2d38..70ecfe4d4 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -239,10 +239,88 @@ def create_default_3d_soil_material(self): return soil_material @pytest.fixture - def expected_geometry_two_layers_3D(self): + def expected_geometry_two_layers_3D_extruded(self): """ - Expected geometry data for a 3D geometry. The geometry is 2 stacked blocks, where the top and bottom blocks - are in different groups. + Expected geometry data for a 3D geometry create from 2D extrusion. The geometry is 2 stacked blocks, where the + top and bottom blocks are in different groups. + + Returns: + Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`]: expected geometry data + """ + + geometry_1 = Geometry() + + geometry_1.points = [Point.create([0, 0, 0], 1), + Point.create([0, 0, 1], 2), + Point.create([1, 0, 1], 4), + Point.create([1, 0, 0], 3), + Point.create([1, 1, 1], 6), + Point.create([1, 1, 0], 5), + Point.create([0, 1, 1], 8), + Point.create([0, 1, 0], 7)] + + geometry_1.lines = [Line.create([1, 2], 1), + Line.create([2, 4], 4), + Line.create([3, 4], 2), + Line.create([1, 3], 3), + Line.create([4, 6], 7), + Line.create([5, 6], 5), + Line.create([3, 5], 6), + Line.create([6, 8], 10), + Line.create([7, 8], 8), + Line.create([5, 7], 9), + Line.create([8, 2], 12), + Line.create([7, 1], 11)] + + geometry_1.surfaces = [Surface.create([1, 4, -2, -3], 1), + Surface.create([2, 7, -5, -6], 2), + Surface.create([5, 10, -8, -9], 3), + Surface.create([8, 12, -1, -11], 4), + Surface.create([3, 6, 9, 11], 5), + Surface.create([4, 7, 10, 12], 6)] + + geometry_1.volumes = [Volume.create([-1, -2, -3, -4, -5, 6], 1)] + + geometry_2 = Geometry() + + geometry_2.points = [Point.create([1., 1., 0.], 5), + Point.create([1., 1., 1.], 6), + Point.create([0.0, 1., 1.], 8), + Point.create([0, 1., 0.], 7), + Point.create([0., 2., 1], 10), + Point.create([0., 2., 0], 9), + Point.create([1, 2., 1], 12), + Point.create([1, 2., 0], 11)] + + geometry_2.lines = [Line.create([5, 6], 5), + Line.create([6, 8], 10), + Line.create([7, 8], 8), + Line.create([5, 7], 9), + Line.create([8, 10], 15), + Line.create([9, 10], 13), + Line.create([7, 9], 14), + Line.create([10, 12], 18), + Line.create([11, 12], 16), + Line.create([9, 11], 17), + Line.create([12, 6], 20), + Line.create([11, 5], 19)] + + geometry_2.surfaces = [Surface.create([5, 10, -8, -9], 3), + Surface.create([8, 15, -13, -14], 7), + Surface.create([13, 18, -16, -17], 8), + Surface.create([16, 20, -5, -19], 9), + Surface.create([9, 14, 17, 19], 10), + Surface.create([10, 15, 18, 20], 11)] + + geometry_2.volumes = [Volume.create([3, 7, 8, 9, 10, -11], 2)] + + return geometry_1, geometry_2 + + @pytest.fixture + def expected_geometry_two_layers_3D_geo_file(self): + """ + Expected geometry data for a 3D geometry create in a geo file. The geometry is 2 stacked blocks, where the top + and bottom blocks are in different groups. Returns: Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`]: expected geometry data @@ -476,6 +554,69 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids + def test_add_multiple_soil_layers_3D(self, expected_geometry_two_layers_3D_extruded: Tuple[Geometry, Geometry], + create_default_3d_soil_material: SoilMaterial): + """ + Test if multiple soil layers are added correctly to the model in a 3D space. Multiple soil layers are generated + and multiple soil materials are created and added to the model. + + Args: + - expected_geometry_two_layers_3D_extruded (Tuple[:class:`stem.geometry.Geometry`, \ + :class:`stem.geometry.Geometry`]): expected geometry of the model which is created by extruding \ + a 2D geometry + - create_default_3d_soil_material (:class:`stem.soil_material.SoilMaterial`): default soil material + + """ + + ndim = 3 + + layer1_coordinates = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)] + layer2_coordinates = [(1, 1, 0), (0, 1, 0), (0, 2, 0), (1, 2, 0)] + + # define soil materials + soil_material1 = create_default_3d_soil_material + soil_material1.name = "soil1" + + soil_material2 = create_default_3d_soil_material + soil_material2.name = "soil2" + + # create model + model = Model(ndim) + model.extrusion_length = [0, 0, 1] + + # add soil layers + model.add_soil_layer_by_coordinates(layer1_coordinates, soil_material1, "layer1") + model.add_soil_layer_by_coordinates(layer2_coordinates, soil_material2, "layer2") + + model.synchronise_geometry() + + # check if layers are added correctly + assert len(model.body_model_parts) == 2 + assert model.body_model_parts[0].name == "layer1" + assert model.body_model_parts[0].material == soil_material1 + assert model.body_model_parts[1].name == "layer2" + assert model.body_model_parts[1].material == soil_material2 + + # check if geometry is added correctly for each layer + for i in range(len(model.body_model_parts)): + generated_geometry = model.body_model_parts[i].geometry + expected_geometry = expected_geometry_two_layers_3D_extruded[i] + + # check if points are added correctly + for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + def test_add_all_layers_from_geo_file_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry]): """ Tests if all layers are added correctly to the model in a 2D space. A geo file is read and all layers are @@ -521,14 +662,14 @@ def test_add_all_layers_from_geo_file_2D(self, expected_geometry_two_layers_2D: assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - def test_add_all_layers_from_geo_file_3D(self, expected_geometry_two_layers_3D: Tuple[Geometry, Geometry]): + def test_add_all_layers_from_geo_file_3D(self, expected_geometry_two_layers_3D_geo_file: Tuple[Geometry, Geometry]): """ Tests if all layers are added correctly to the model in a 3D space. A geo file is read and all layers are added to the model. Args: - - expected_geometry_two_layers_3D (Tuple[:class:`stem.geometry.Geometry`, :class:`stem.geometry.Geometry`]): \ - expected geometry of the model + - expected_geometry_two_layers_3D_geo_file (Tuple[:class:`stem.geometry.Geometry`, \ + :class:`stem.geometry.Geometry`]): expected geometry of the model """ @@ -554,7 +695,7 @@ def test_add_all_layers_from_geo_file_3D(self, expected_geometry_two_layers_3D: # check if geometry is added correctly for each layer for i in range(len(all_model_parts)): generated_geometry = all_model_parts[i].geometry - expected_geometry = expected_geometry_two_layers_3D[i] + expected_geometry = expected_geometry_two_layers_3D_geo_file[i] # check if points are added correctly for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): @@ -629,7 +770,6 @@ def test_synchronise_geometry_2D(self, expected_geometry_two_layers_2D_after_syn assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids - def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMaterial): """ Test if the geometry is synchronised correctly in 3D after adding a new layer to the model. Where the new layer From 969c1c74702274741a73c7ca01fdb8140857ef1a Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 18 Jul 2023 11:03:31 +0200 Subject: [PATCH 045/116] added gmsh vakantie_branch to requirements --- requirements.txt | 2 +- requirements_dev.txt | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 184da3639..73c519b40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ numpy==1.24.2 -gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@main +gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@vakantie_branch diff --git a/requirements_dev.txt b/requirements_dev.txt index d6af7035a..4bf5a2509 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,5 @@ numpy==1.24.2 -gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@main +gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@vakantie_branch pytest==7.2.2 pytest-cov==4.0.0 tox==4.4.11 diff --git a/setup.cfg b/setup.cfg index 75bd09b72..e66946d5b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,7 @@ packages = include_package_data = True install_requires = numpy>=1.24 - gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@main + gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@vakantie_branch python_requires = >=3.8 [options.extras_require] From 160d28f09e095ce7b806ea785e7d12fef346eb6c Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 18 Jul 2023 11:13:46 +0200 Subject: [PATCH 046/116] changed call to generate_geometry --- stem/model.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/stem/model.py b/stem/model.py index 162b15d2c..fe15d8502 100644 --- a/stem/model.py +++ b/stem/model.py @@ -107,18 +107,16 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], """ + gmsh_input = {name: {"coordinates": coordinates, "ndim": self.ndim}} # check if extrusion length is specified in 3D if self.ndim == 3: if self.extrusion_length is None: raise ValueError("Extrusion length must be specified for 3D models") - else: - extrusion_length = self.extrusion_length - else: - # in 2D extrusion length is not needed - extrusion_length = [0, 0, 0] + + gmsh_input[name]["extrusion_length"] = self.extrusion_length # todo check if this function in gmsh io can be improved - self.gmsh_io.generate_geometry([coordinates], extrusion_length, self.ndim, "", [name]) + self.gmsh_io.generate_geometry(gmsh_input, "") # create body model part body_model_part = BodyModelPart() From 02fccb96406950892bcd2ca2d070254511cdd3b5 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 18 Jul 2023 11:03:31 +0200 Subject: [PATCH 047/116] added gmsh vakantie_branch to requirements --- requirements.txt | 2 +- requirements_dev.txt | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 184da3639..73c519b40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ numpy==1.24.2 -gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@main +gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@vakantie_branch diff --git a/requirements_dev.txt b/requirements_dev.txt index d6af7035a..4bf5a2509 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,5 @@ numpy==1.24.2 -gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@main +gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@vakantie_branch pytest==7.2.2 pytest-cov==4.0.0 tox==4.4.11 diff --git a/setup.cfg b/setup.cfg index 75bd09b72..e66946d5b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,7 @@ packages = include_package_data = True install_requires = numpy>=1.24 - gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@main + gmsh_utils @ git+https://github.com/StemVibrations/gmsh_utils@vakantie_branch python_requires = >=3.8 [options.extras_require] From bc4e7f217ccbf52302812588b0a1d3de490c74a1 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 18 Jul 2023 11:13:46 +0200 Subject: [PATCH 048/116] changed call to generate_geometry --- stem/model.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/stem/model.py b/stem/model.py index 162b15d2c..fe15d8502 100644 --- a/stem/model.py +++ b/stem/model.py @@ -107,18 +107,16 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], """ + gmsh_input = {name: {"coordinates": coordinates, "ndim": self.ndim}} # check if extrusion length is specified in 3D if self.ndim == 3: if self.extrusion_length is None: raise ValueError("Extrusion length must be specified for 3D models") - else: - extrusion_length = self.extrusion_length - else: - # in 2D extrusion length is not needed - extrusion_length = [0, 0, 0] + + gmsh_input[name]["extrusion_length"] = self.extrusion_length # todo check if this function in gmsh io can be improved - self.gmsh_io.generate_geometry([coordinates], extrusion_length, self.ndim, "", [name]) + self.gmsh_io.generate_geometry(gmsh_input, "") # create body model part body_model_part = BodyModelPart() From 6c88023b7f39dca10492be8312bde8044f139b84 Mon Sep 17 00:00:00 2001 From: noordam Date: Thu, 20 Jul 2023 09:54:09 +0200 Subject: [PATCH 049/116] added function to get mesh from group --- stem/mesh.py | 99 ++++++++++++++++++++++++++++++++++++---------- stem/model.py | 60 +++++++++++++++++++++++++--- stem/model_part.py | 26 ++++++++---- 3 files changed, 151 insertions(+), 34 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index 6ea3323b4..d0df790e3 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -1,4 +1,6 @@ -from typing import Dict, List, Tuple, Union, Any +from typing import Dict, List, Tuple, Union, Any, Optional +from enum import Enum + import numpy as np import numpy.typing as npt @@ -18,6 +20,11 @@ def __init__(self, id, coordinates): self.id = id self.coordinates = coordinates +# class ElementType(Enum): +# +# +# def get_element_type_from + class Element: """ Class containing information about an element @@ -34,22 +41,6 @@ def __init__(self, id: int, element_type: str, node_ids: Union[List[int], npt.ND self.node_ids: Union[List[int], npt.NDArray[np.int64]] = node_ids -class Condition: - """ - Class containing information about a condition - - Attributes: - - id (int): condition id - - element_type (str): element type - - node_ids (Union[List[int], npt.NDArray[np.int64]]): node ids - - """ - def __init__(self, id: int, element_type: str, node_ids: Union[List[int], npt.NDArray[np.int64]]): - self.id: int = id - self.element_type: str = element_type - self.node_ids: Union[List[int], npt.NDArray[np.int64]] = node_ids - - class Mesh: """ Class containing information about the mesh @@ -66,11 +57,9 @@ class Mesh: """ def __init__(self, ndim: int): - self.ndim: int = ndim + self.ndim: Optional[int] = None self.nodes = None self.elements = None - self.conditions = None - @classmethod def read_mesh_from_gmsh(cls, mesh_file_name: str) -> None: @@ -78,6 +67,76 @@ def read_mesh_from_gmsh(cls, mesh_file_name: str) -> None: # file. pass + @classmethod + def create_mesh_from_mesh_data(cls, mesh_data: Dict[str, Any]): + """ + Creates a mesh object from mesh data + + Args: + - mesh_data (Dict[str, Any]): dictionary of mesh data + + Returns: + - :class:`Mesh`: mesh object + """ + + # create mesh object + + node_data = mesh_data["nodes"] + element_data = mesh_data["elements"] + + nodes = [] + for node_id, coordinates in node_data.items(): + node = Node(node_id, coordinates) + nodes.append(node) + + elements = [] + for element_type, element_type_data in element_data.items(): + for element_id, element_node in element_type_data.items(): + element = Element(element_id, element_type, element_node) + elements.append(element) + + mesh = cls(mesh_data["ndim"]) + mesh.nodes = nodes + mesh.elements = elements + + return mesh + + @classmethod + def create_mesh_from_gmsh_group(cls, mesh_data, group_name): + """ + Creates a mesh object from gmsh group + + Args: + - mesh_data (Dict[str, Any]): dictionary of mesh data + - group_name (str): name of the group + + Returns: + - :class:`Mesh`: mesh object + """ + # create mesh object + group_data = mesh_data["physical_groups"][group_name] + + group_element_ids = group_data["element_ids"] + group_node_ids = group_data["node_ids"] + group_element_type = group_data["element_type"] + + element_type_data = mesh_data["elements"][group_element_type] + + # create element per element id + elements = [Element(element_id, group_element_type, element_type_data[element_id]) + for element_id in group_element_ids] + + # create node per node id + nodes = [Node(node_id, mesh_data["nodes"][node_id]) for node_id in group_node_ids] + + # add nodes and elements to mesh object + mesh = cls(mesh_data["ndim"]) + mesh.nodes = nodes + mesh.elements = elements + + return mesh + + def prepare_data_for_kratos(self, mesh_data: Dict[str, Any]) \ -> Tuple[npt.NDArray[np.float64], npt.NDArray[np.int64]]: """ diff --git a/stem/model.py b/stem/model.py index fe15d8502..5eff9129d 100644 --- a/stem/model.py +++ b/stem/model.py @@ -1,4 +1,5 @@ from typing import List, Sequence, Dict, Any, Optional, Union +from enum import Enum from gmsh_utils import gmsh_IO @@ -6,6 +7,35 @@ from stem.soil_material import * from stem.structural_material import * from stem.geometry import Geometry +from stem.mesh import Mesh + + +class ElementShape(Enum): + """ + Enum class for the element shape. + """ + TRIANGLE = "triangle" + QUADRILATURAL = "quadrilateral" + + +@dataclass +class MeshSettings: + """ + A class to represent the mesh settings. + + Attributes: + - element_size (float): The element size. + - element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. + - element_shape (:class:`stem.model.ElementShape`): The element shape. TRIANGLE for triangular elements and + tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. + """ + element_size: float + element_order: int = 1 + element_shape: ElementShape = ElementShape.TRIANGLE # todo implement possibility to choose in gmsh utils + + def __post_init__(self): + if self.element_order not in [1, 2]: + raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") class Model: @@ -27,7 +57,8 @@ def __init__(self, ndim: int): self.project_parameters = None self.solver = None self.geometry: Optional[Geometry] = None - self.mesh = None + self.mesh = Optional[Mesh] = None + self.mesh_settings: MeshSettings = field(default_factory=MeshSettings) self.gmsh_io = gmsh_IO.GmshIO() self.body_model_parts: List[BodyModelPart] = [] self.process_model_parts: List[ModelPart] = [] @@ -78,12 +109,11 @@ def add_all_layers_from_geo_file(self, geo_file_name: str, body_names: Sequence[ # create model part, if the group name is in the body names, create a body model part, otherwise a process # model part if group_name in body_names: - model_part = BodyModelPart() + model_part = BodyModelPart(group_name) else: - model_part = ModelPart() + model_part = ModelPart(group_name) # set the name and geometry of the model part - model_part.name = group_name model_part.get_geometry_from_geo_data(geo_data, group_name) # add model part to either body model parts or process model part @@ -119,8 +149,7 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], self.gmsh_io.generate_geometry(gmsh_input, "") # create body model part - body_model_part = BodyModelPart() - body_model_part.name = name + body_model_part = BodyModelPart(name) body_model_part.material = material_parameters # set the geometry of the body model part @@ -155,7 +184,26 @@ def synchronise_geometry(self): # get the complete geometry self.__get_geometry_from_geo_data(self.gmsh_io.geo_data) + def generate_mesh(self): + """ + Generate the mesh for the whole model. + """ + + self.gmsh_io.generate_mesh(self.ndim, element_size=self.mesh_settings.element_size, + order=self.mesh_settings.element_order) + + # collect all model parts + all_model_parts: List[Union[BodyModelPart, ModelPart]] = [] + all_model_parts.extend(self.body_model_parts) + all_model_parts.extend(self.process_model_parts) + + for model_part in all_model_parts: + # Check if all model parts have a name + if model_part.name is None: + raise ValueError("All model parts must have a name") + else: + model_part.mesh = Mesh.create_mesh_from_gmsh_group(self.gmsh_io.mesh_data, model_part.name) diff --git a/stem/model_part.py b/stem/model_part.py index 351b8955a..9b269f10a 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -4,6 +4,7 @@ from stem.structural_material import StructuralMaterial from stem.geometry import Geometry +from stem.mesh import Mesh class ModelPart: @@ -12,20 +13,23 @@ class ModelPart: like excavation. Attributes: - - name (Optional[str]): name of the model part + - name (str): name of the model part - nodes (None): node id followed by node coordinates in an array - elements (None): element id followed by connectivities in an array - conditions (None): condition id followed by connectivities in an array - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part - parameters (Dict[Any,Any]): dictionary containing the model part parameters """ - def __init__(self): - self.name: Optional[str] = None - self.nodes = None # todo define type - self.elements = None # todo define type - self.conditions = None # todo define type + def __init__(self, name: str): + """ + Initialize the model part + Args: + - name (str): name of the model part + """ + self.name: str = name self.geometry: Optional[Geometry] = None + self.mesh: Optional[Mesh] = None self.parameters = {} # todo define type def get_geometry_from_geo_data(self, geo_data: Dict[str, Any], name: str): @@ -57,7 +61,13 @@ class BodyModelPart(ModelPart): :class:`stem.structural_material.StructuralMaterial`]): material of the model part """ - def __init__(self): - super().__init__() + def __init__(self, name: str): + """ + Initialize the body model part + + Args: + - name (str): name of the body model part + """ + super().__init__(name) self.material: Optional[Union[SoilMaterial, StructuralMaterial]] = None From fd8c73ced54d60ed4d884c8cd0861802ddd75087 Mon Sep 17 00:00:00 2001 From: noordam Date: Thu, 20 Jul 2023 09:54:32 +0200 Subject: [PATCH 050/116] removed obsolete function --- stem/mesh.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index d0df790e3..60da5e032 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -61,12 +61,6 @@ def __init__(self, ndim: int): self.nodes = None self.elements = None - @classmethod - def read_mesh_from_gmsh(cls, mesh_file_name: str) -> None: - #todo implement this method to read mesh from gmsh file and create a mesh object with the data read from the - # file. - pass - @classmethod def create_mesh_from_mesh_data(cls, mesh_data: Dict[str, Any]): """ From ed25784df421e26511c52ea24ae74a2ce9a6bc4f Mon Sep 17 00:00:00 2001 From: noordam Date: Thu, 20 Jul 2023 10:25:11 +0200 Subject: [PATCH 051/116] added tests for creating mesh per group --- stem/mesh.py | 11 ++- tests/test_mesh.py | 197 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 6 deletions(-) create mode 100644 tests/test_mesh.py diff --git a/stem/mesh.py b/stem/mesh.py index 60da5e032..658e805d9 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -20,10 +20,6 @@ def __init__(self, id, coordinates): self.id = id self.coordinates = coordinates -# class ElementType(Enum): -# -# -# def get_element_type_from class Element: """ @@ -96,7 +92,7 @@ def create_mesh_from_mesh_data(cls, mesh_data: Dict[str, Any]): return mesh @classmethod - def create_mesh_from_gmsh_group(cls, mesh_data, group_name): + def create_mesh_from_gmsh_group(cls, mesh_data: Dict[str, Any], group_name: str): """ Creates a mesh object from gmsh group @@ -107,6 +103,10 @@ def create_mesh_from_gmsh_group(cls, mesh_data, group_name): Returns: - :class:`Mesh`: mesh object """ + + if group_name not in mesh_data["physical_groups"]: + raise ValueError(f"Group {group_name} not found in mesh data") + # create mesh object group_data = mesh_data["physical_groups"][group_name] @@ -157,7 +157,6 @@ def prepare_data_for_kratos(self, mesh_data: Dict[str, Any]) \ return nodes, all_elements - def write_mesh_to_kratos_structure(self, mesh_data: Dict[str, Any], filename: str) -> None: """ Writes mesh data to the structure which can be read by Kratos diff --git a/tests/test_mesh.py b/tests/test_mesh.py new file mode 100644 index 000000000..74e5cb423 --- /dev/null +++ b/tests/test_mesh.py @@ -0,0 +1,197 @@ +import pytest +from gmsh_utils.gmsh_IO import GmshIO + +from stem.mesh import * + + +class TestMesh: + + def test_create_0d_mesh_from_gmsh_group(self): + """ + Test the creation of a 0D mesh from a gmsh group. + + """ + + # Set up the mesh data + mesh_data = {"ndim": 0, + "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0]}, + "elements": {"POINT_1N": {1: [1], 2: [2]}}, + "physical_groups": {"points_group": {'element_ids': [1, 2], + "node_ids": [1, 2], + "element_type": "POINT_1N"}}} + + # Create the mesh from the gmsh group + generated_mesh = Mesh.create_mesh_from_gmsh_group(mesh_data, "points_group") + + # set expected mesh + expected_nodes = [Node(1, [0, 0, 0]), Node(2, [0.5, 0, 0])] + expected_elements = [Element(1, "POINT_1N", [1]), Element(2, "POINT_1N", [2])] + expected_mesh = Mesh(0) + expected_mesh.nodes = expected_nodes + expected_mesh.elements = expected_elements + + # Check the generated mesh + assert generated_mesh.ndim == expected_mesh.ndim + + # Check the nodes + for generated_node, expected_node in zip(generated_mesh.nodes, expected_mesh.nodes): + assert generated_node.id == expected_node.id + assert pytest.approx(generated_node.coordinates) == expected_node.coordinates + + # Check the elements + for generated_element, expected_element in zip(generated_mesh.elements, expected_mesh.elements): + assert generated_element.id == expected_element.id + assert generated_element.element_type == expected_element.element_type + assert generated_element.node_ids == expected_element.node_ids + + def test_create_1d_mesh_from_gmsh_group(self): + """ + Test the creation of a 1D mesh from a gmsh group. + + """ + + # Set up the mesh data + mesh_data = {"ndim": 1, + "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0], 3: [1, 0, 0]}, + "elements": {"LINE_2N": {1: [1, 2], 2: [2, 3]}}, + "physical_groups": {"lines_group": {'element_ids': [1, 2], + "node_ids": [1, 2, 3], + "element_type": "LINE_2N"}}} + + # Create the mesh from the gmsh group + generated_mesh = Mesh.create_mesh_from_gmsh_group(mesh_data, "lines_group") + + # set expected mesh + expected_nodes = [Node(1, [0, 0, 0]), Node(2, [0.5, 0, 0]), Node(3, [1, 0, 0])] + expected_elements = [Element(1, "LINE_2N", [1, 2]), Element(2, "LINE_2N", [2, 3])] + expected_mesh = Mesh(1) + expected_mesh.nodes = expected_nodes + expected_mesh.elements = expected_elements + + # Check the generated mesh + assert generated_mesh.ndim == expected_mesh.ndim + + # Check the nodes + for generated_node, expected_node in zip(generated_mesh.nodes, expected_mesh.nodes): + assert generated_node.id == expected_node.id + assert pytest.approx(generated_node.coordinates) == expected_node.coordinates + + # Check the elements + for generated_element, expected_element in zip(generated_mesh.elements, expected_mesh.elements): + assert generated_element.id == expected_element.id + assert generated_element.element_type == expected_element.element_type + assert generated_element.node_ids == expected_element.node_ids + + def test_create_2d_mesh_from_gmsh_group(self): + """ + Test the creation of a 2D mesh from a gmsh group. + + """ + + # Set up the mesh data + mesh_data = {"ndim": 2, + "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0], 3: [1, 0, 0], + 4: [0, 0.5, 0], 5: [0.5, 0.5, 0], 6: [1, 0.5, 0]}, + "elements": {"TRIANGLE_3N": {1: [1, 2, 4], 2: [2, 3, 5], 3: [3, 6, 5]}}, + "physical_groups": {"triangles_group": {'element_ids': [1, 2, 3], + "node_ids": [1, 2, 3, 4, 5, 6], + "element_type": "TRIANGLE_3N"}}} + + # Create the mesh from the gmsh group + generated_mesh = Mesh.create_mesh_from_gmsh_group(mesh_data, "triangles_group") + + # set expected mesh + expected_nodes = [Node(1, [0, 0, 0]), Node(2, [0.5, 0, 0]), Node(3, [1, 0, 0]), + Node(4, [0, 0.5, 0]), Node(5, [0.5, 0.5, 0]), Node(6, [1, 0.5, 0])] + expected_elements = [Element(1, "TRIANGLE_3N", [1, 2, 4]), + Element(2, "TRIANGLE_3N", [2, 3, 5]), + Element(3, "TRIANGLE_3N", [3, 6, 5])] + + expected_mesh = Mesh(2) + expected_mesh.nodes = expected_nodes + expected_mesh.elements = expected_elements + + # Check the generated mesh + assert generated_mesh.ndim == expected_mesh.ndim + + # Check the nodes + for generated_node, expected_node in zip(generated_mesh.nodes, expected_mesh.nodes): + assert generated_node.id == expected_node.id + assert pytest.approx(generated_node.coordinates) == expected_node.coordinates + + # Check the elements + for generated_element, expected_element in zip(generated_mesh.elements, expected_mesh.elements): + assert generated_element.id == expected_element.id + assert generated_element.element_type == expected_element.element_type + assert generated_element.node_ids == expected_element.node_ids + + def test_create_3d_mesh_from_gmsh_group(self): + """ + Test the creation of a 3D mesh from a gmsh group. + + """ + # Set up the mesh data + mesh_data = {"ndim": 3, + "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0], 3: [1, 0, 0], + 4: [0, 0.5, 0], 5: [0.5, 0.5, 0], 6: [1, 0.5, 0], + 7: [0, 0, 0.5], 8: [0.5, 0, 0.5], 9: [1, 0, 0.5], + 10: [0, 0.5, 0.5], 11: [0.5, 0.5, 0.5], 12: [1, 0.5, 0.5]}, + "elements": {"TETRAHEDRON_4N": {1: [1, 2, 4, 7], 2: [2, 3, 5, 8], 3: [3, 6, 5, 9], + 4: [4, 5, 7, 10], 5: [5, 6, 8, 11], 6: [6, 9, 11, 8]}}, + "physical_groups": {"tetrahedral_group": {'element_ids': [1, 2, 3, 4, 5, 6], + "node_ids": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + "element_type": "TETRAHEDRON_4N"}}} + + # Create the mesh from the gmsh group + generated_mesh = Mesh.create_mesh_from_gmsh_group(mesh_data, "tetrahedral_group") + + # set expected mesh + expected_nodes = [Node(1, [0, 0, 0]), Node(2, [0.5, 0, 0]), Node(3, [1, 0, 0]), + Node(4, [0, 0.5, 0]), Node(5, [0.5, 0.5, 0]), Node(6, [1, 0.5, 0]), + Node(7, [0, 0, 0.5]), Node(8, [0.5, 0, 0.5]), Node(9, [1, 0, 0.5]), + Node(10, [0, 0.5, 0.5]), Node(11, [0.5, 0.5, 0.5]), Node(12, [1, 0.5, 0.5])] + + expected_elements = [Element(1, "TETRAHEDRON_4N", [1, 2, 4, 7]), + Element(2, "TETRAHEDRON_4N", [2, 3, 5, 8]), + Element(3, "TETRAHEDRON_4N", [3, 6, 5, 9]), + Element(4, "TETRAHEDRON_4N", [4, 5, 7, 10]), + Element(5, "TETRAHEDRON_4N", [5, 6, 8, 11]), + Element(6, "TETRAHEDRON_4N", [6, 9, 11, 8])] + + expected_mesh = Mesh(3) + expected_mesh.nodes = expected_nodes + expected_mesh.elements = expected_elements + + # Check the generated mesh + assert generated_mesh.ndim == expected_mesh.ndim + + # Check the nodes + for generated_node, expected_node in zip(generated_mesh.nodes, expected_mesh.nodes): + assert generated_node.id == expected_node.id + assert pytest.approx(generated_node.coordinates) == expected_node.coordinates + + # Check the elements + for generated_element, expected_element in zip(generated_mesh.elements, expected_mesh.elements): + assert generated_element.id == expected_element.id + assert generated_element.element_type == expected_element.element_type + assert generated_element.node_ids == expected_element.node_ids + + def test_create_mesh_from_non_existing_group(self): + """ + Test the creation of a mesh from a non-existing gmsh group. + + """ + + # Set up the mesh data + mesh_data = {"ndim": 0, + "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0]}, + "elements": {"POINT_1N": {1: [1], 2: [2]}}, + "physical_groups": {"points_group": {'element_ids': [1, 2], + "node_ids": [1, 2], + "element_type": "POINT_1N"}}} + + # Create the mesh from the gmsh group + with pytest.raises(ValueError): + Mesh.create_mesh_from_gmsh_group(mesh_data, "non_existing_group") + + From 81e7fae40b333f2c619a128e7ff7cc0b19a0bfab Mon Sep 17 00:00:00 2001 From: noordam Date: Thu, 20 Jul 2023 11:56:35 +0200 Subject: [PATCH 052/116] fixed test --- stem/mesh.py | 2 +- stem/model.py | 6 +-- tests/test_kratos_solver_io.py | 6 +-- tests/test_mesh.py | 2 +- tests/test_model.py | 89 +++++++++++++++++++++++++++++++++- 5 files changed, 94 insertions(+), 11 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index 658e805d9..904c8e803 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -53,7 +53,7 @@ class Mesh: """ def __init__(self, ndim: int): - self.ndim: Optional[int] = None + self.ndim: int = ndim self.nodes = None self.elements = None diff --git a/stem/model.py b/stem/model.py index 5eff9129d..e177a66e2 100644 --- a/stem/model.py +++ b/stem/model.py @@ -29,7 +29,7 @@ class MeshSettings: - element_shape (:class:`stem.model.ElementShape`): The element shape. TRIANGLE for triangular elements and tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. """ - element_size: float + element_size: float = -1 element_order: int = 1 element_shape: ElementShape = ElementShape.TRIANGLE # todo implement possibility to choose in gmsh utils @@ -57,8 +57,8 @@ def __init__(self, ndim: int): self.project_parameters = None self.solver = None self.geometry: Optional[Geometry] = None - self.mesh = Optional[Mesh] = None - self.mesh_settings: MeshSettings = field(default_factory=MeshSettings) + self.mesh: Optional[Mesh] = None + self.mesh_settings: MeshSettings = MeshSettings() self.gmsh_io = gmsh_IO.GmshIO() self.body_model_parts: List[BodyModelPart] = [] self.process_model_parts: List[ModelPart] = [] diff --git a/tests/test_kratos_solver_io.py b/tests/test_kratos_solver_io.py index e09872473..7a2144638 100644 --- a/tests/test_kratos_solver_io.py +++ b/tests/test_kratos_solver_io.py @@ -48,11 +48,9 @@ def test_create_settings_dictionary(self): problem_data = Problem(problem_name="test", number_of_threads=2, settings=solver_settings) # create model parts - model_part1 = ModelPart() - model_part1.name = "ModelPart1" + model_part1 = ModelPart("ModelPart1") - body_model_part1 = BodyModelPart() - body_model_part1.name = "BodyModelPart1" + body_model_part1 = BodyModelPart("BodyModelPart1") model_parts = [model_part1, body_model_part1] diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 74e5cb423..0d4269033 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -1,5 +1,4 @@ import pytest -from gmsh_utils.gmsh_IO import GmshIO from stem.mesh import * @@ -130,6 +129,7 @@ def test_create_3d_mesh_from_gmsh_group(self): Test the creation of a 3D mesh from a gmsh group. """ + # Set up the mesh data mesh_data = {"ndim": 3, "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0], 3: [1, 0, 0], diff --git a/tests/test_model.py b/tests/test_model.py index 70ecfe4d4..00f8fcd95 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -780,7 +780,6 @@ def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMate """ - import json # define layer coordinates ndim = 3 @@ -833,4 +832,90 @@ def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMate # check if volumes are added correctly for generated_volume, expected_volume in zip(generated_geometry.volumes, expected_geometry.volumes): assert generated_volume.id == expected_volume.id - assert generated_volume.surface_ids == expected_volume.surface_ids \ No newline at end of file + assert generated_volume.surface_ids == expected_volume.surface_ids + def test_generate_mesh_with_only_a_body_model_part_2d(self, create_default_2d_soil_material: SoilMaterial): + """ + Test if the mesh is generated correctly in 2D if there is only one body model part. + + Args: + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + + """ + model = Model(2) + + # add soil material + soil_material = create_default_2d_soil_material + + # add soil layers + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)], soil_material, "layer1") + model.synchronise_geometry() + + # generate mesh + model.generate_mesh() + + mesh = model.body_model_parts[0].mesh + + assert mesh.ndim == 2 + + unique_element_ids = [] + # check if mesh is generated correctly, i.e. if the number of elements is correct and if the element type is + # correct and if the element ids are unique and if the number of nodes per element is correct + assert len(mesh.elements) == 162 + for element in mesh.elements: + assert element.element_type == "TRIANGLE_3N" + assert element.id not in unique_element_ids + assert len(element.node_ids) == 3 + unique_element_ids.append(element.id) + + # check if nodes are generated correctly, i.e. if there are nodes in the mesh and if the node ids are unique + # and if the number of coordinates per node is correct + unique_node_ids = [] + assert len(mesh.nodes) == 98 + for node in mesh.nodes: + assert node.id not in unique_node_ids + assert len(node.coordinates) == 3 + unique_node_ids.append(node.id) + + def test_generate_mesh_with_only_a_body_model_part_3d(self, create_default_3d_soil_material: SoilMaterial): + """ + Test if the mesh is generated correctly in 3D if there is only one body model part. + + Args: + - create_default_3d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + + """ + model = Model(3) + model.extrusion_length = [0, 0, 1] + + # add soil material + soil_material = create_default_3d_soil_material + + # add soil layers + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)], soil_material, "layer1") + model.synchronise_geometry() + + # generate mesh + model.generate_mesh() + + mesh = model.body_model_parts[0].mesh + + assert mesh.ndim == 3 + + unique_element_ids = [] + # check if mesh is generated correctly, i.e. if the number of elements is correct and if the element type is + # correct and if the element ids are unique and if the number of nodes per element is correct + assert len(mesh.elements) == 1120 + for element in mesh.elements: + assert element.element_type == "TETRAHEDRON_4N" + assert element.id not in unique_element_ids + assert len(element.node_ids) == 4 + unique_element_ids.append(element.id) + + # check if nodes are generated correctly, i.e. if there are nodes in the mesh and if the node ids are unique + # and if the number of coordinates per node is correct + unique_node_ids = [] + assert len(mesh.nodes) == 340 + for node in mesh.nodes: + assert node.id not in unique_node_ids + assert len(node.coordinates) == 3 + unique_node_ids.append(node.id) \ No newline at end of file From 4ba941b49c9818757b3dde32309e4bc5556fb01f Mon Sep 17 00:00:00 2001 From: noordam Date: Thu, 20 Jul 2023 13:52:12 +0200 Subject: [PATCH 053/116] solved mypy issues --- stem/mesh.py | 26 ++++++++++++-------------- stem/model.py | 1 + stem/model_part.py | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index 904c8e803..af1453706 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -1,5 +1,4 @@ -from typing import Dict, List, Tuple, Union, Any, Optional -from enum import Enum +from typing import Dict, List, Tuple, Sequence, Union, Any, Optional import numpy as np import numpy.typing as npt @@ -13,12 +12,12 @@ class Node: Attributes: - id (int): node id - - coordinates (np.array): node coordinates + - coordinates (Sequence[float]): node coordinates """ - def __init__(self, id, coordinates): - self.id = id - self.coordinates = coordinates + def __init__(self, id: int, coordinates: Sequence[float]): + self.id: int = id + self.coordinates: Sequence[float] = coordinates class Element: @@ -28,13 +27,13 @@ class Element: Attributes: - id (int): element id - element_type (str): element type - - node_ids (Union[List[int], npt.NDArray[np.int64]]): node ids + - node_ids (Sequence[int]): node ids """ - def __init__(self, id: int, element_type: str, node_ids: Union[List[int], npt.NDArray[np.int64]]): + def __init__(self, id: int, element_type: str, node_ids: Sequence[int]): self.id: int = id self.element_type: str = element_type - self.node_ids: Union[List[int], npt.NDArray[np.int64]] = node_ids + self.node_ids: Sequence[int] = node_ids class Mesh: @@ -46,16 +45,15 @@ class Mesh: Attributes: - ndim (int): number of dimensions of the mesh - - nodes (np.array or None): node id followed by node coordinates in an array - - elements (np.array or None): element id followed by connectivities in an array - - conditions (np.array or None): condition id followed by connectivities in an array + - nodes (List[Node]): node id followed by node coordinates in an array + - elements (List[Element]): element id followed by connectivities in an array """ def __init__(self, ndim: int): self.ndim: int = ndim - self.nodes = None - self.elements = None + self.nodes: List[Node] = [] + self.elements: List[Element] = [] @classmethod def create_mesh_from_mesh_data(cls, mesh_data: Dict[str, Any]): diff --git a/stem/model.py b/stem/model.py index e177a66e2..ed4d8b0f2 100644 --- a/stem/model.py +++ b/stem/model.py @@ -1,5 +1,6 @@ from typing import List, Sequence, Dict, Any, Optional, Union from enum import Enum +from dataclasses import dataclass from gmsh_utils import gmsh_IO diff --git a/stem/model_part.py b/stem/model_part.py index 9b269f10a..5113c4aab 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -30,7 +30,7 @@ def __init__(self, name: str): self.name: str = name self.geometry: Optional[Geometry] = None self.mesh: Optional[Mesh] = None - self.parameters = {} # todo define type + self.parameters: Dict[Any, Any] = {} # todo define type def get_geometry_from_geo_data(self, geo_data: Dict[str, Any], name: str): """ From a9a369b4f3d6ba60af3c94a77c0635dc2d2a3b05 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 13:19:15 +0200 Subject: [PATCH 054/116] added test which checks mesh of combined body model part and process model part --- stem/mesh.py | 2 +- tests/test_mesh.py | 17 ++++++--- tests/test_model.py | 90 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index af1453706..7b83cc42b 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -122,7 +122,7 @@ def create_mesh_from_gmsh_group(cls, mesh_data: Dict[str, Any], group_name: str) nodes = [Node(node_id, mesh_data["nodes"][node_id]) for node_id in group_node_ids] # add nodes and elements to mesh object - mesh = cls(mesh_data["ndim"]) + mesh = cls(group_data["ndim"]) mesh.nodes = nodes mesh.elements = elements diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 0d4269033..8cf139fe3 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -15,7 +15,8 @@ def test_create_0d_mesh_from_gmsh_group(self): mesh_data = {"ndim": 0, "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0]}, "elements": {"POINT_1N": {1: [1], 2: [2]}}, - "physical_groups": {"points_group": {'element_ids': [1, 2], + "physical_groups": {"points_group": {"ndim": 0, + 'element_ids': [1, 2], "node_ids": [1, 2], "element_type": "POINT_1N"}}} @@ -53,7 +54,8 @@ def test_create_1d_mesh_from_gmsh_group(self): mesh_data = {"ndim": 1, "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0], 3: [1, 0, 0]}, "elements": {"LINE_2N": {1: [1, 2], 2: [2, 3]}}, - "physical_groups": {"lines_group": {'element_ids': [1, 2], + "physical_groups": {"lines_group": {"ndim": 1, + 'element_ids': [1, 2], "node_ids": [1, 2, 3], "element_type": "LINE_2N"}}} @@ -92,7 +94,8 @@ def test_create_2d_mesh_from_gmsh_group(self): "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0], 3: [1, 0, 0], 4: [0, 0.5, 0], 5: [0.5, 0.5, 0], 6: [1, 0.5, 0]}, "elements": {"TRIANGLE_3N": {1: [1, 2, 4], 2: [2, 3, 5], 3: [3, 6, 5]}}, - "physical_groups": {"triangles_group": {'element_ids': [1, 2, 3], + "physical_groups": {"triangles_group": {"ndim": 2, + 'element_ids': [1, 2, 3], "node_ids": [1, 2, 3, 4, 5, 6], "element_type": "TRIANGLE_3N"}}} @@ -138,7 +141,8 @@ def test_create_3d_mesh_from_gmsh_group(self): 10: [0, 0.5, 0.5], 11: [0.5, 0.5, 0.5], 12: [1, 0.5, 0.5]}, "elements": {"TETRAHEDRON_4N": {1: [1, 2, 4, 7], 2: [2, 3, 5, 8], 3: [3, 6, 5, 9], 4: [4, 5, 7, 10], 5: [5, 6, 8, 11], 6: [6, 9, 11, 8]}}, - "physical_groups": {"tetrahedral_group": {'element_ids': [1, 2, 3, 4, 5, 6], + "physical_groups": {"tetrahedral_group": {"ndim": 3, + 'element_ids': [1, 2, 3, 4, 5, 6], "node_ids": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "element_type": "TETRAHEDRON_4N"}}} @@ -178,7 +182,7 @@ def test_create_3d_mesh_from_gmsh_group(self): def test_create_mesh_from_non_existing_group(self): """ - Test the creation of a mesh from a non-existing gmsh group. + Test the creation of a mesh from a non-existing gmsh group. Expected to raise a ValueError. """ @@ -186,7 +190,8 @@ def test_create_mesh_from_non_existing_group(self): mesh_data = {"ndim": 0, "nodes": {1: [0, 0, 0], 2: [0.5, 0, 0]}, "elements": {"POINT_1N": {1: [1], 2: [2]}}, - "physical_groups": {"points_group": {'element_ids': [1, 2], + "physical_groups": {"points_group": {"ndim": 0, + 'element_ids': [1, 2], "node_ids": [1, 2], "element_type": "POINT_1N"}}} diff --git a/tests/test_model.py b/tests/test_model.py index 00f8fcd95..2724a74de 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -210,7 +210,7 @@ def create_default_2d_soil_material(self): Create a default soil material for a 2D geometry. Returns: - :class:`stem.models.soil.SoilMaterial`: default soil material + - :class:`stem.soil_material.SoilMaterial`: default soil material """ # define soil material @@ -227,7 +227,7 @@ def create_default_3d_soil_material(self): Create a default soil material for a 3D geometry. Returns: - :class:`stem.models.soil.SoilMaterial`: default soil material + - :class:`stem.soil_material.SoilMaterial`: default soil material """ # define soil material @@ -245,7 +245,7 @@ def expected_geometry_two_layers_3D_extruded(self): top and bottom blocks are in different groups. Returns: - Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`]: expected geometry data + - Tuple[:class:`stem.geometry.Geometry`,:class:`stem.geometry.Geometry`]: expected geometry data """ geometry_1 = Geometry() @@ -780,7 +780,6 @@ def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMate """ - # define layer coordinates ndim = 3 layer1_coordinates = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)] @@ -833,6 +832,7 @@ def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMate for generated_volume, expected_volume in zip(generated_geometry.volumes, expected_geometry.volumes): assert generated_volume.id == expected_volume.id assert generated_volume.surface_ids == expected_volume.surface_ids + def test_generate_mesh_with_only_a_body_model_part_2d(self, create_default_2d_soil_material: SoilMaterial): """ Test if the mesh is generated correctly in 2D if there is only one body model part. @@ -918,4 +918,84 @@ def test_generate_mesh_with_only_a_body_model_part_3d(self, create_default_3d_so for node in mesh.nodes: assert node.id not in unique_node_ids assert len(node.coordinates) == 3 - unique_node_ids.append(node.id) \ No newline at end of file + unique_node_ids.append(node.id) + + def test_generate_mesh_with_body_and_process_model_part(self, create_default_2d_soil_material: SoilMaterial): + """ + Test if the mesh is generated correctly in the body model part and a process model part. + + Args: + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + """ + model = Model(2) + + # add soil material + soil_material = create_default_2d_soil_material + + # add soil layers + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)], soil_material, "layer1") + + # add process geometry + gmsh_process_input = {"process_0d": {"coordinates": [[0, 0.5, 0]], "ndim": 0}} + model.gmsh_io.generate_geometry(gmsh_process_input, "") + + # create process model part + process_model_part = ModelPart("process_0d") + + # set the geometry of the process model part + process_model_part.get_geometry_from_geo_data(model.gmsh_io.geo_data, "process_0d") + + # add process model part + model.process_model_parts.append(process_model_part) + + # synchronise geometry and generate mesh + model.synchronise_geometry() + model.generate_mesh() + + # check mesh of body model part + mesh_body = model.body_model_parts[0].mesh + + assert mesh_body.ndim == 2 + + unique_element_ids = [] + # check if mesh is generated correctly, i.e. if the number of elements is correct and if the element type is + # correct and if the element ids are unique and if the number of nodes per element is correct + assert len(mesh_body.elements) == 162 + for element in mesh_body.elements: + assert element.element_type == "TRIANGLE_3N" + assert element.id not in unique_element_ids + assert len(element.node_ids) == 3 + unique_element_ids.append(element.id) + + # check if nodes are generated correctly, i.e. if there are nodes in the mesh and if the node ids are unique + # and if the number of coordinates per node is correct + unique_body_node_ids = [] + assert len(mesh_body.nodes) == 98 + for node in mesh_body.nodes: + assert node.id not in unique_body_node_ids + assert len(node.coordinates) == 3 + unique_body_node_ids.append(node.id) + + # check process model part + mesh_process = model.process_model_parts[0].mesh + + assert mesh_process.ndim == 0 + + # check elements of process model part, i.e. if the number of elements is correct and if the element type is + # correct and if the element ids are unique and if the number of nodes per element is correct + assert len(mesh_process.elements) == 1 + for element in mesh_process.elements: + assert element.element_type == "POINT_1N" + assert element.id == 1 + assert element.id not in unique_element_ids + assert len(element.node_ids) == 1 + unique_element_ids.append(element.id) + + # check nodes of process model part, i.e. if there is 1 node in the mesh and if the node ids are present in the + # body mesh and if the number of coordinates per node is correct + assert len(mesh_process.nodes) == 1 + for node in mesh_process.nodes: + + # check if node is also available in the body mesh + assert node.id in unique_body_node_ids + assert len(node.coordinates) == 3 From 506130288da9f8ad54c406cf220d6ffca379cc99 Mon Sep 17 00:00:00 2001 From: morettid Date: Fri, 21 Jul 2023 13:31:42 +0200 Subject: [PATCH 055/116] Adding geometry for load application and fixes in testing for model Fixing errors when test fails and gmsh keeps running and is not closed. Added geometry generation for point, line, surface and moving point loads. Test still missing for surface loads. Removing defaults from the load parameters --- stem/load.py | 24 +-- stem/model.py | 98 ++++++++- stem/utils.py | 47 +++++ tests/test_kratos_loads_io.py | 4 +- tests/test_model.py | 383 +++++++++++++++++++++++----------- tests/test_utils.py | 49 +++++ tests/utils.py | 39 +++- 7 files changed, 508 insertions(+), 136 deletions(-) create mode 100644 stem/utils.py create mode 100644 tests/test_utils.py diff --git a/stem/load.py b/stem/load.py index a013a600f..3d172cfd3 100644 --- a/stem/load.py +++ b/stem/load.py @@ -24,8 +24,8 @@ class PointLoad(LoadParametersABC): - value (List[float]): Entity of the load in the 3 directions [N]. """ - active: List[bool] = field(default_factory=lambda: [True, True, True]) - value: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) + active: List[bool] + value: List[float] @dataclass @@ -37,8 +37,8 @@ class LineLoad(LoadParametersABC): - active (List[bool]): Activate/deactivate load for each direction. - value (List[float]): Entity of the load in the 3 directions [N]. """ - active: List[bool] = field(default_factory=lambda: [True, True, True]) - value: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) + active: List[bool] + value: List[float] @dataclass @@ -50,8 +50,8 @@ class SurfaceLoad(LoadParametersABC): - active (List[bool]): Activate/deactivate load for each direction. - value (List[float]): Entity of the load in the 3 directions [N]. """ - active: List[bool] = field(default_factory=lambda: [True, True, True]) - value: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) + active: List[bool] + value: List[float] @dataclass @@ -72,10 +72,10 @@ class MovingLoad(LoadParametersABC): - offset (float): Offset of the moving load [m]. """ - load: Union[List[float], List[str]] = field(default_factory=lambda: [0.0, 0.0, 0.0]) - direction: List[float] = field(default_factory=lambda: [1, 1, 1]) - velocity: Union[float, str] = 0.0 - origin: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) + load: Union[List[float], List[str]] + direction: List[float] + velocity: Union[float, str] + origin: List[float] offset: float = 0.0 @@ -92,5 +92,5 @@ class GravityLoad(LoadParametersABC): - value (List[float]): Entity of the gravity acceleration in the 3 directions [m/s^2]. Should be -9.81 only in the vertical direction """ - active: List[bool] = field(default_factory=lambda: [False, False, False]) - value: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) + active: List[bool] + value: List[float] diff --git a/stem/model.py b/stem/model.py index fe15d8502..c45db38f7 100644 --- a/stem/model.py +++ b/stem/model.py @@ -1,11 +1,16 @@ -from typing import List, Sequence, Dict, Any, Optional, Union +import collections +from typing import List, Sequence, Dict, Any, Optional, Union, get_args + +import numpy as np from gmsh_utils import gmsh_IO from stem.model_part import ModelPart, BodyModelPart from stem.soil_material import * from stem.structural_material import * +from stem.load import * from stem.geometry import Geometry +from stem.utils import is_point_between_points, is_collinear class Model: @@ -128,6 +133,97 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], self.body_model_parts.append(body_model_part) + def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_parameters: LoadParametersABC, name: str): + """ + Adds a load to the model by giving a sequence of 3D coordinates. For a 2D model, the third coordinate is + ignored. + + Args: + - coordinates (Sequence[Sequence[float]]): The coordinates of the load. + - parameters (:class:`stem.load.LoadParametersABC`): The parameters of the load. + - name (str): The name of the load part. + + """ + + self.__validate_coordinates(coordinates) + + if isinstance(load_parameters, PointLoad): + gmsh_input = {name: {"coordinates": coordinates, "ndim": 0}} + elif isinstance(load_parameters, get_args(Union[LineLoad, MovingLoad])): + gmsh_input = {name: {"coordinates": coordinates, "ndim": 1}} + elif isinstance(load_parameters, SurfaceLoad): + gmsh_input = {name: {"coordinates": coordinates, "ndim": 2}} + else: + # TODO: deal with Gravity loads + raise ValueError(f'Invalid load_parameters ({load_parameters.__class__.__name__}) object' + f' provided for the load {name}.Expected one of PointLoad, MovingLoad,' + f' LineLoad or SurfaceLoad.') + + self.gmsh_io.generate_geometry(gmsh_input, "") + + if isinstance(load_parameters, MovingLoad): + self.__validate_moving_load_parameters(coordinates, load_parameters) + + # create body model part + model_part = ModelPart() + model_part.name = name + model_part.parameters = load_parameters + + # set the geometry of the body model part + model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, name) + + self.process_model_parts.append(model_part) + + @staticmethod + def __validate_coordinates(coordinates: Sequence[Sequence[float]]): + """ + Validates the coordinates in input. + + Args: + - coordinates (Sequence[Sequence[float]]): The coordinates of the load. + + """ + + # check if coordinates is a sequence + if not isinstance(coordinates, collections.abc.Sequence): + raise ValueError(f"Coordinates are not a sequence!\n:{coordinates}.") + + # check if coordinates is a sequence + for coordinate in coordinates: + if not isinstance(coordinate, collections.abc.Sequence): + raise ValueError(f"Coordinate in coordinates is not a sequence!\n:{coordinate}.") + + if len(coordinate) > 3 or len(coordinate) < 1: + raise ValueError(f"Coordinate should be either 2D or 3D but {len(coordinate)} was given") + + + @staticmethod + def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], load_parameters: MovingLoad): + """ + Validates the coordinates in input for the moving load and the trajectory (collinearity of the + points and if the origin is between the point). + + Args: + - coordinates (Sequence[Sequence[float]]): The start-end coordinate of the moving load. + - parameters (:class:`stem.load.LoadParametersABC`): The parameters of the load. + + """ + + # check if coordinates is a sequence + if len(coordinates) != 2: + raise ValueError(f"For moving loads, start and ending points have to be specified, but the given " + f"coordinates are:\n :{coordinates}.") + + if not is_collinear( + point=load_parameters.origin, start_point=coordinates[0], end_point=coordinates[1] + ): + raise ValueError(f"Origin of the moving load and given points of the trajectory are not aligned!") + + if not is_point_between_points( + point=load_parameters.origin, start_point=coordinates[0], end_point=coordinates[1] + ): + raise ValueError(f"Point not in between given two points.") + def synchronise_geometry(self): """ Synchronise the geometry of all model parts and synchronise the geometry of the whole model. This function diff --git a/stem/utils.py b/stem/utils.py new file mode 100644 index 000000000..d510e9d31 --- /dev/null +++ b/stem/utils.py @@ -0,0 +1,47 @@ +from typing import Sequence + +import numpy as np + + +def is_collinear(point:Sequence, start_point:Sequence, end_point:Sequence): + """ + Check if point is aligned with the other two on a line. Points must have the same dimension (2D or 3D) + + Args: + point (Sequence): point to be tested + start_point (Sequence): first point on the line + end_point (Sequence): second point on the line + + Returns: + bool: whether the point is aligned or not + """ + + vec_1 = np.asarray(point) - np.asarray(start_point) + vec_2 = np.asarray(end_point) - np.asarray(start_point) + + cross_product = np.cross(vec_1, vec_2) + return np.sum(np.abs(cross_product)) < 1e-06 + + +def is_point_between_points(point, start_point, end_point): + """ + Check if point is between the other two. Points must have the same dimension (2D or 3D). + + Args: + point (Sequence): point to be tested + start_point (Sequence): first extreme on the line + end_point (Sequence): second extreme on the line + + Returns: + bool: whether the point is between the other two or not + """ + + # Calculate vectors between the points + vec_1 = np.asarray(point) - np.asarray(start_point) + vec_2 = np.asarray(end_point) - np.asarray(start_point) + + # Calculate the scalar projections of vector1 onto vector2 + scalar_projection = sum(v1 * v2 for v1, v2 in zip(vec_1, vec_2)) / sum(v ** 2 for v in vec_2) + + # Check if the scalar projection is between 0 and 1 (inclusive) + return 0 <= scalar_projection <= 1 \ No newline at end of file diff --git a/tests/test_kratos_loads_io.py b/tests/test_kratos_loads_io.py index 9878359ce..1be82affb 100644 --- a/tests/test_kratos_loads_io.py +++ b/tests/test_kratos_loads_io.py @@ -38,7 +38,7 @@ def test_create_load_process_dict(self): # collect the part names and parameters into a dictionary # TODO: change later when model part is implemented - all_boundary_parameters = { + all_load_parameters = { "test_point_load": point_load_parameters, "test_line_load": line_load_parameters, "test_surface_load": surface_load_parameters, @@ -56,7 +56,7 @@ def test_create_load_process_dict(self): # TODO: when model part are implemented, generate file through kratos_io boundaries_io = KratosLoadsIO(domain="PorousDomain") - for part_name, part_parameters in all_boundary_parameters.items(): + for part_name, part_parameters in all_load_parameters.items(): _parameters = boundaries_io.create_load_dict( part_name=part_name, parameters=part_parameters ) diff --git a/tests/test_model.py b/tests/test_model.py index 70ecfe4d4..6064b0867 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -2,9 +2,11 @@ import pickle import pytest +from gmsh_utils import gmsh_IO from stem.model import * from stem.geometry import * +from tests.utils import TestUtils class TestModel: @@ -47,6 +49,51 @@ def expected_geometry_single_layer_2D(self): return geometry + + @pytest.fixture + def expected_geometry_single_layer_3D(self): + """ + Sets expected geometry data for a 2D geometry group. The group is a geometry of a square. + + Returns: + - :class:`stem.geometry.Geometry`: geometry of a 2D square + """ + + geometry = Geometry() + + geometry.points = [Point.create([0, 0, 0], 1), + Point.create([0, 0, 1], 5), + Point.create([1, 0, 1], 6), + Point.create([1, 0, 0], 2), + Point.create([1, 1, 1], 7), + Point.create([1, 1, 0], 3), + Point.create([0, 1, 1], 8), + Point.create([0, 1, 0], 4)] + + geometry.lines = [Line.create([1, 5], 5), + Line.create([5, 6], 7), + Line.create([2, 6], 6), + Line.create([1, 2], 1), + Line.create([6, 7], 9), + Line.create([3, 7], 8), + Line.create([2, 3], 2), + Line.create([7, 8], 11), + Line.create([4, 8], 10), + Line.create([3, 4], 3), + Line.create([8, 5], 12), + Line.create([4, 1], 4)] + + geometry.surfaces = [Surface.create([5, 7, -6, -1], 2), + Surface.create([6, 9, -8, -2], 3), + Surface.create([8,11, -10, -3], 4), + Surface.create([10, 12, -5, -4], 5), + Surface.create([1, 2, 3, 4], 1), + Surface.create([7, 9, 11, 12], 6)] + + geometry.volumes = [Volume.create([-2, -3, -4, -5, -1, 6], 1)] + + return geometry + @pytest.fixture def expected_geometry_single_layer_3D(self): """ @@ -202,8 +249,36 @@ def expected_geometry_two_layers_2D_after_sync(self): full_geometry.surfaces = [Surface.create([1, 2, 3, 4, 5], 1), Surface.create([3, 6, 7, 8], 2)] + full_geometry.volumes = [] + return geometry_1, geometry_2, full_geometry + @pytest.fixture + def expected_geometry_line_load(self): + """ + Sets expected geometry data for a 2D geometry group. The group is a geometry of a square. + + Returns: + - :class:`stem.geometry.Geometry`: geometry of a 2D square + """ + + geometry = Geometry() + + geometry.points = [Point.create([0, 0, 0], 1), + Point.create([3, 0, 0], 2), + Point.create([4, -1, 0], 3), + Point.create([10, -1, 0], 4)] + + geometry.lines = [Line.create([1, 2], 1), + Line.create([2, 3], 2), + Line.create([3, 4], 3)] + + geometry.surfaces = [] + + geometry.volumes = [] + + return geometry + @pytest.fixture def create_default_2d_soil_material(self): """ @@ -238,6 +313,61 @@ def create_default_3d_soil_material(self): retention_parameters=SaturatedBelowPhreaticLevelLaw()) return soil_material + @pytest.fixture + def create_default_point_load_parameters(self): + """ + Create a default point load parameters. + + Returns: + :class:`stem.load.PointLoad`: default point load + + """ + # define soil material + return PointLoad(active=[False, True, False], value=[0, -200, 0]) + + @pytest.fixture + def create_default_line_load_parameters(self): + """ + Create a default line load parameters. + + Returns: + :class:`stem.load.PointLoad`: default point load + + """ + # define soil material + return LineLoad(active=[False, True, False], value=[0, -20, 0]) + + @pytest.fixture + def create_default_surface_load_parameters(self): + """ + Create a default surface load properties. + + Returns: + :class:`stem.load.SurfaceLoad`: default surface load + + """ + # define soil material + return SurfaceLoad(active=[False, True, False], value=[0, -2, 0]) + + @pytest.fixture + def create_default_moving_load_parameters(self): + """ + Create a default surface load properties. + + Returns: + :class:`stem.load.SurfaceLoad`: default surface load + + """ + # define soil material + return MovingLoad( + origin=[5.0, 0.0, 0.0], + load=[0.0, -10.0, 0.0], + velocity=5.0, + offset=3.0, + direction=[1, 1, 1] + ) + + @pytest.fixture def expected_geometry_two_layers_3D_extruded(self): """ @@ -391,6 +521,20 @@ def expected_geometry_two_layers_3D_geo_file(self): return geometry_1, geometry_2 + @pytest.fixture(autouse=True) + def close_gmsh(self): + """ + Initializer to close gmsh if it was not closed before. In case a test fails, the destroyer method is not called + on the Model object and gmsh keeps on running. Therefore, nodes, lines, surfaces and volumes ids are not + reset to one. This causes also the next test after the failed one to fail as well, which has nothing to do + the test themselves. + + Returns: + - None + + """ + gmsh_IO.GmshIO().finalize_gmsh() + def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geometry, create_default_2d_soil_material: SoilMaterial): """ @@ -426,19 +570,7 @@ def test_add_single_soil_layer_2D(self, expected_geometry_single_layer_2D: Geome expected_geometry = expected_geometry_single_layer_2D # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) def test_add_single_soil_layer_3D(self, expected_geometry_single_layer_3D: Geometry, create_default_3d_soil_material: SoilMaterial): @@ -476,24 +608,7 @@ def test_add_single_soil_layer_3D(self, expected_geometry_single_layer_3D: Geome expected_geometry = expected_geometry_single_layer_3D # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids - - # check if volumes are added correctly - for generated_volume, expected_volume in zip(generated_geometry.volumes, expected_geometry.volumes): - assert generated_volume.id == expected_volume.id - assert generated_volume.surface_ids == expected_volume.surface_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): @@ -539,20 +654,7 @@ def test_add_multiple_soil_layers_2D(self, expected_geometry_two_layers_2D: Tupl generated_geometry = model.body_model_parts[i].geometry expected_geometry = expected_geometry_two_layers_2D[i] - # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) def test_add_multiple_soil_layers_3D(self, expected_geometry_two_layers_3D_extruded: Tuple[Geometry, Geometry], create_default_3d_soil_material: SoilMaterial): @@ -602,20 +704,7 @@ def test_add_multiple_soil_layers_3D(self, expected_geometry_two_layers_3D_extru generated_geometry = model.body_model_parts[i].geometry expected_geometry = expected_geometry_two_layers_3D_extruded[i] - # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) def test_add_all_layers_from_geo_file_2D(self, expected_geometry_two_layers_2D: Tuple[Geometry, Geometry]): """ @@ -647,20 +736,7 @@ def test_add_all_layers_from_geo_file_2D(self, expected_geometry_two_layers_2D: generated_geometry = model.body_model_parts[i].geometry expected_geometry = expected_geometry_two_layers_2D[i] - # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) def test_add_all_layers_from_geo_file_3D(self, expected_geometry_two_layers_3D_geo_file: Tuple[Geometry, Geometry]): """ @@ -697,20 +773,7 @@ def test_add_all_layers_from_geo_file_3D(self, expected_geometry_two_layers_3D_g generated_geometry = all_model_parts[i].geometry expected_geometry = expected_geometry_two_layers_3D_geo_file[i] - # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) def test_synchronise_geometry_2D(self, expected_geometry_two_layers_2D_after_sync: Tuple[Geometry, Geometry], create_default_2d_soil_material: SoilMaterial): @@ -755,20 +818,7 @@ def test_synchronise_geometry_2D(self, expected_geometry_two_layers_2D_after_syn for generated_geometry, expected_geometry in zip(generated_geometries, expected_geometry_two_layers_2D_after_sync): - # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMaterial): """ @@ -815,22 +865,115 @@ def test_synchronise_geometry_3D(self, create_default_3d_soil_material: SoilMate for generated_geometry, expected_geometry in zip(generated_geometries, expected_geometry_two_layers_3D_after_sync): - # check if points are added correctly - for generated_point, expected_point in zip(generated_geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids - - # check if volumes are added correctly - for generated_volume, expected_volume in zip(generated_geometry.volumes, expected_geometry.volumes): - assert generated_volume.id == expected_volume.id - assert generated_volume.surface_ids == expected_volume.surface_ids \ No newline at end of file + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) + + def test_add_point_loads_to_2_points(self, create_default_point_load_parameters: PointLoad): + """ + Test if a single soil point load is added correctly to the model. Two points are generated + and a single load is created and added to the model. + + Args: + - create_default_point_load_properties (:class:`stem.load.PointLoad`): default point load parameters + + """ + + ndim = 3 + + point_coordinates = [(-0.5, 0, 0), (0.5, 0, 0)] + + # define soil material + load_parameters = create_default_point_load_parameters + + # create model + model = Model(ndim) + # add soil layer + model.add_load_by_coordinates(point_coordinates, load_parameters, "point_load_1") + + # check if layer is added correctly + assert len(model.process_model_parts) == 1 + assert model.process_model_parts[0].name == "point_load_1" + assert model.process_model_parts[0].parameters == load_parameters + + # check if geometry is added correctly + generated_geometry = model.process_model_parts[0].geometry + expected_geometry = Geometry( + points=[Point.create([-0.5, 0, 0], 1), Point.create([0.5, 0, 0], 2)], + lines=[], + surfaces=[], + volumes=[] + ) + + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) + + def test_add_line_load_to_3_edges(self, expected_geometry_line_load: Geometry, + create_default_line_load_parameters: PointLoad): + """ + Test if a line load is added correctly to the model when applied on 3 edges. 4 points are generated + and a single soil material is created and added to the model. + + Args: + - expected_geometry_line_load (:class:`stem.geometry.Geometry`): expected geometry of the model + - create_default_line_load_parameters (:class:`stem.load.LineLoad`): default line load parameters + + """ + + ndim = 3 + + point_coordinates = [(0, 0, 0), (3, 0, 0), (4, -1, 0), (10, -1, 0)] + + # define soil material + load_parameters = create_default_line_load_parameters + + # create model + model = Model(ndim) + # add soil layer + model.add_load_by_coordinates(point_coordinates, load_parameters, "line_load_1") + + # check if layer is added correctly + assert len(model.process_model_parts) == 1 + assert model.process_model_parts[0].name == "line_load_1" + assert model.process_model_parts[0].parameters == load_parameters + + # check if geometry is added correctly + generated_geometry = model.process_model_parts[0].geometry + expected_geometry = expected_geometry_line_load + + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) + + def test_add_moving_point_load(self, create_default_moving_load_parameters: MovingLoad): + """ + Test if a single soil point load is added correctly to the model. Two points are generated + and a single load is created and added to the model. + + Args: + - create_default_moving_load_parameters (:class:`stem.load.MovingLoad`): default moving load parameters + + """ + + ndim = 3 + + point_coordinates = [(0.0, 0, 0), (10, 0, 0)] + + # define soil material + load_parameters = create_default_moving_load_parameters + + # create model + model = Model(ndim) + # add soil layer + model.add_load_by_coordinates(point_coordinates, load_parameters, "moving_load_1") + + # check if layer is added correctly + assert len(model.process_model_parts) == 1 + assert model.process_model_parts[0].name == "moving_load_1" + assert model.process_model_parts[0].parameters == load_parameters + + # check if geometry is added correctly + generated_geometry = model.process_model_parts[0].geometry + expected_geometry = Geometry( + points=[Point.create([0.0, 0, 0], 1), Point.create([10.0, 0, 0], 2)], + lines=[Line.create([1, 2], 1)], + surfaces=[], + volumes=[] + ) + + TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 000000000..e790893b3 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,49 @@ + +from stem.utils import * + + +class TestUtilsStem: + + def test_collinearity_2d(self): + + p1 = np.array([0, 0]) + p2 = np.array([-2, -1]) + + p_test_1 = np.array([2, 1]) + p_test_2 = np.array([-5, 1]) + + assert is_collinear(point=p_test_1, start_point=p1, end_point=p2) + assert not is_collinear(point=p_test_2, start_point=p1, end_point=p2) + + def test_collinearity_3d(self): + + p1 = np.array([0, 0, 0]) + p2 = np.array([-2, -2, 2]) + + p_test_1 = np.array([2, 2, -2]) + p_test_2 = np.array([2, -2, 2]) + + assert is_collinear(point=p_test_1, start_point=p1, end_point=p2) + assert not is_collinear(point=p_test_2, start_point=p1, end_point=p2) + + def test_is_in_between_2d(self): + + p1 = np.array([0, 0]) + p2 = np.array([-2, -2]) + + p_test_1 = np.array([2, 2]) + p_test_2 = np.array([-1, -1]) + + assert not is_point_between_points(point=p_test_1, start_point=p1, end_point=p2) + assert is_point_between_points(point=p_test_2, start_point=p1, end_point=p2) + + def test_is_in_between_3d(self): + + p1 = np.array([0, 0, 0]) + p2 = np.array([-2, -2, 2]) + + p_test_1 = np.array([2, 2, -2]) + p_test_2 = np.array([-1, -1, 1]) + + assert not is_point_between_points(point=p_test_1, start_point=p1, end_point=p2) + assert is_point_between_points(point=p_test_2, start_point=p1, end_point=p2) \ No newline at end of file diff --git a/tests/utils.py b/tests/utils.py index 99044283f..a17bdef37 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,9 @@ from typing import Dict, Any import numpy.testing as npt +import pytest + +from stem.geometry import Geometry class TestUtils: @@ -36,4 +39,38 @@ def assert_dictionary_almost_equal(expected: Dict[Any, Any], actual: Dict[Any, A npt.assert_allclose(v_i, actual_i) else: - npt.assert_allclose(v, actual[k]) \ No newline at end of file + npt.assert_allclose(v, actual[k]) + + @staticmethod + def assert_almost_equal_geometries(expected_geometry: Geometry, actual_geometry:Geometry): + """ + Checks whether two Geometries are (almost) equal. + + Args: + expected_geometry (:class:`stem.geometry.Geometry`): expected geometry of the model + actual_geometry (:class:`stem.geometry.Geometry`): actual geometry of the model + + Returns: + + """ + # check if points are added correctly + for generated_point, expected_point in zip(actual_geometry.points, expected_geometry.points): + if generated_point.id != expected_point.id: + a=1+1 + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(actual_geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(actual_geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + # check if volumes are added correctly + for generated_volume, expected_volume in zip(actual_geometry.volumes, expected_geometry.volumes): + assert generated_volume.id == expected_volume.id + assert generated_volume.surface_ids == expected_volume.surface_ids From 68e207b8f779d9c7fa634871b6902cfe64e70a31 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 13:41:18 +0200 Subject: [PATCH 056/116] removed mesh from model and moved mesh settings --- stem/mesh.py | 66 ++++++++++++++++++++++++--------------------------- stem/model.py | 31 +----------------------- 2 files changed, 32 insertions(+), 65 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index 7b83cc42b..2ad519767 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -1,4 +1,6 @@ from typing import Dict, List, Tuple, Sequence, Union, Any, Optional +from enum import Enum +from dataclasses import dataclass import numpy as np import numpy.typing as npt @@ -6,6 +8,35 @@ from stem.IO.kratos_io import KratosIO +class ElementShape(Enum): + """ + Enum class for the element shape. TRIANGLE for triangular elements and tetrahedral elements, QUADRILATERAL for + quadrilateral elements and hexahedral elements. + + """ + TRIANGLE = "triangle" + QUADRILATURAL = "quadrilateral" + + +@dataclass +class MeshSettings: + """ + A class to represent the mesh settings. + + Attributes: + - element_size (float): The element size. + - element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. + - element_shape (:class:`stem.model.ElementShape`): The element shape. TRIANGLE for triangular elements and \ + tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. + """ + element_size: float = -1 + element_order: int = 1 + element_shape: ElementShape = ElementShape.TRIANGLE # todo implement possibility to choose in gmsh utils + + def __post_init__(self): + if self.element_order not in [1, 2]: + raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") + class Node: """ Class containing information about a node @@ -55,40 +86,6 @@ def __init__(self, ndim: int): self.nodes: List[Node] = [] self.elements: List[Element] = [] - @classmethod - def create_mesh_from_mesh_data(cls, mesh_data: Dict[str, Any]): - """ - Creates a mesh object from mesh data - - Args: - - mesh_data (Dict[str, Any]): dictionary of mesh data - - Returns: - - :class:`Mesh`: mesh object - """ - - # create mesh object - - node_data = mesh_data["nodes"] - element_data = mesh_data["elements"] - - nodes = [] - for node_id, coordinates in node_data.items(): - node = Node(node_id, coordinates) - nodes.append(node) - - elements = [] - for element_type, element_type_data in element_data.items(): - for element_id, element_node in element_type_data.items(): - element = Element(element_id, element_type, element_node) - elements.append(element) - - mesh = cls(mesh_data["ndim"]) - mesh.nodes = nodes - mesh.elements = elements - - return mesh - @classmethod def create_mesh_from_gmsh_group(cls, mesh_data: Dict[str, Any], group_name: str): """ @@ -128,7 +125,6 @@ def create_mesh_from_gmsh_group(cls, mesh_data: Dict[str, Any], group_name: str) return mesh - def prepare_data_for_kratos(self, mesh_data: Dict[str, Any]) \ -> Tuple[npt.NDArray[np.float64], npt.NDArray[np.int64]]: """ diff --git a/stem/model.py b/stem/model.py index ed4d8b0f2..4fa5181b3 100644 --- a/stem/model.py +++ b/stem/model.py @@ -8,35 +8,7 @@ from stem.soil_material import * from stem.structural_material import * from stem.geometry import Geometry -from stem.mesh import Mesh - - -class ElementShape(Enum): - """ - Enum class for the element shape. - """ - TRIANGLE = "triangle" - QUADRILATURAL = "quadrilateral" - - -@dataclass -class MeshSettings: - """ - A class to represent the mesh settings. - - Attributes: - - element_size (float): The element size. - - element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. - - element_shape (:class:`stem.model.ElementShape`): The element shape. TRIANGLE for triangular elements and - tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. - """ - element_size: float = -1 - element_order: int = 1 - element_shape: ElementShape = ElementShape.TRIANGLE # todo implement possibility to choose in gmsh utils - - def __post_init__(self): - if self.element_order not in [1, 2]: - raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") +from stem.mesh import Mesh, MeshSettings class Model: @@ -58,7 +30,6 @@ def __init__(self, ndim: int): self.project_parameters = None self.solver = None self.geometry: Optional[Geometry] = None - self.mesh: Optional[Mesh] = None self.mesh_settings: MeshSettings = MeshSettings() self.gmsh_io = gmsh_IO.GmshIO() self.body_model_parts: List[BodyModelPart] = [] From 5cfac04bf16b0ca207d67ed11c8f6fb49d425eea Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 13:51:21 +0200 Subject: [PATCH 057/116] extended mesh settings docstring --- stem/mesh.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index 2ad519767..f81ab99b2 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -24,10 +24,10 @@ class MeshSettings: A class to represent the mesh settings. Attributes: - - element_size (float): The element size. - - element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. + - element_size (float): The element size (default -1, which means that gmsh determines the size). + - element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. (default 1) - element_shape (:class:`stem.model.ElementShape`): The element shape. TRIANGLE for triangular elements and \ - tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. + tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. (default TRIANGLE) """ element_size: float = -1 element_order: int = 1 From 7dfb1f4a0259dfb7468d5bbc62e51ab733ff4486 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 13:52:18 +0200 Subject: [PATCH 058/116] extended mesh settings docstring --- stem/mesh.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stem/mesh.py b/stem/mesh.py index f81ab99b2..192651ab8 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -34,6 +34,12 @@ class MeshSettings: element_shape: ElementShape = ElementShape.TRIANGLE # todo implement possibility to choose in gmsh utils def __post_init__(self): + """ + Post initialization of the mesh settings. Checks if the element order is 1 or 2. + + Raises: + - ValueError: If the element order is not 1 or 2. + """ if self.element_order not in [1, 2]: raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") From 02e2df6a26c44d7200f257a8e0c0395b50908827 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 14:20:02 +0200 Subject: [PATCH 059/116] added tests for validation --- stem/model.py | 37 ++++++++++++++++++++++++++++++------ stem/model_part.py | 26 ++++++++++++++++--------- tests/test_model.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/stem/model.py b/stem/model.py index 4fa5181b3..97a44fc91 100644 --- a/stem/model.py +++ b/stem/model.py @@ -147,11 +147,7 @@ def synchronise_geometry(self): # Get the geometry from the geo_data for each model part for model_part in all_model_parts: - # Check if all model parts have a name - if model_part.name is None: - raise ValueError("All model parts must have a name") - else: - model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, model_part.name) + model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, model_part.name) # get the complete geometry self.__get_geometry_from_geo_data(self.gmsh_io.geo_data) @@ -162,6 +158,7 @@ def generate_mesh(self): """ + # generate mesh self.gmsh_io.generate_mesh(self.ndim, element_size=self.mesh_settings.element_size, order=self.mesh_settings.element_order) @@ -170,13 +167,41 @@ def generate_mesh(self): all_model_parts.extend(self.body_model_parts) all_model_parts.extend(self.process_model_parts) + # add the mesh to each model part + for model_part in all_model_parts: + model_part.mesh = Mesh.create_mesh_from_gmsh_group(self.gmsh_io.mesh_data, model_part.name) + + def __validate_model_part_names(self): + """ + Checks if all model parts have a unique name. + + Raises: + - ValueError: If not all model parts have a name. + - ValueError: If not all model part names are unique . + """ + + # collect all model parts + all_model_parts: List[Union[BodyModelPart, ModelPart]] = [] + all_model_parts.extend(self.body_model_parts) + all_model_parts.extend(self.process_model_parts) + + unique_names = [] for model_part in all_model_parts: # Check if all model parts have a name if model_part.name is None: raise ValueError("All model parts must have a name") else: - model_part.mesh = Mesh.create_mesh_from_gmsh_group(self.gmsh_io.mesh_data, model_part.name) + if model_part.name in unique_names: + raise ValueError("All model parts must have a unique name") + unique_names.append(model_part.name) + + def validate(self): + """ + Validate the model. \ + - Checks if all model parts have a unique name. + """ + self.__validate_model_part_names() diff --git a/stem/model_part.py b/stem/model_part.py index 5113c4aab..558cd1a33 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -13,11 +13,9 @@ class ModelPart: like excavation. Attributes: - - name (str): name of the model part - - nodes (None): node id followed by node coordinates in an array - - elements (None): element id followed by connectivities in an array - - conditions (None): condition id followed by connectivities in an array + - __name (str): name of the model part - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part + - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part - parameters (Dict[Any,Any]): dictionary containing the model part parameters """ def __init__(self, name: str): @@ -27,11 +25,22 @@ def __init__(self, name: str): Args: - name (str): name of the model part """ - self.name: str = name + self.__name: str = name self.geometry: Optional[Geometry] = None self.mesh: Optional[Mesh] = None self.parameters: Dict[Any, Any] = {} # todo define type + @property + def name(self): + """ + Get the name of the model part + + Returns: + - str: name of the model part + + """ + return self.__name + def get_geometry_from_geo_data(self, geo_data: Dict[str, Any], name: str): """ Get the geometry from the geo_data and set the nodes and elements attributes. @@ -52,10 +61,9 @@ class BodyModelPart(ModelPart): - :class:`ModelPart` Attributes: - - name (str): name of the model part - - nodes (None): node id followed by node coordinates in an array - - elements (None): element id followed by connectivities in an array - - conditions (None): condition id followed by connectivities in an array + - __name (str): name of the model part + - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part + - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part - parameters (Dict[str, Any]): dictionary containing the model part parameters - material (Union[:class:`stem.soil_material.SoilMaterial`, \ :class:`stem.structural_material.StructuralMaterial`]): material of the model part diff --git a/tests/test_model.py b/tests/test_model.py index 2724a74de..2c0edf33e 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -999,3 +999,49 @@ def test_generate_mesh_with_body_and_process_model_part(self, create_default_2d_ # check if node is also available in the body mesh assert node.id in unique_body_node_ids assert len(node.coordinates) == 3 + + def test_validate_expected_success(self): + """ + Test if the model is validated correctly. A model is created with two process model parts which both have + a unique name. + + """ + + model = Model(2) + + model_part1 = ModelPart("test1") + model_part2 = ModelPart("test2") + + model.process_model_parts = [model_part1, model_part2] + + model.validate() + + def test_validate_expected_fail_non_unique_names(self): + """ + Test if the model is validated correctly. A model is created with two process model parts which both have + the same name. This should raise a ValueError. + + """ + + model = Model(2) + + model_part1 = ModelPart("test") + model_part2 = ModelPart("test") + + model.process_model_parts = [model_part1, model_part2] + + pytest.raises(ValueError, model.validate) + + def test_validate_expected_fail_no_name(self): + """ + Test if the model is validated correctly. A model is created with a process model part which does not contain + a name. This should raise a ValueError. + + """ + + model = Model(2) + + model_part1 = ModelPart(None) + model.process_model_parts = [model_part1] + + pytest.raises(ValueError, model.validate) \ No newline at end of file From 3f7eda1a379b908434e68d3202ee07e1e3aceabc Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 14:22:38 +0200 Subject: [PATCH 060/116] extended docstring in mesh --- stem/mesh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stem/mesh.py b/stem/mesh.py index 192651ab8..b47d9e49f 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -101,6 +101,9 @@ def create_mesh_from_gmsh_group(cls, mesh_data: Dict[str, Any], group_name: str) - mesh_data (Dict[str, Any]): dictionary of mesh data - group_name (str): name of the group + Raises: + - ValueError: If the group name is not found in the mesh data + Returns: - :class:`Mesh`: mesh object """ From 16285b44e014bfd766e746f12edbd82ef8bd51b8 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 14:37:34 +0200 Subject: [PATCH 061/116] added element order as a property such that it can be validated --- stem/mesh.py | 51 +++++++++++++++++++++++++++++++++++++++++----- tests/test_mesh.py | 15 ++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index b47d9e49f..76e6e583e 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -18,20 +18,60 @@ class ElementShape(Enum): QUADRILATURAL = "quadrilateral" -@dataclass class MeshSettings: """ A class to represent the mesh settings. Attributes: - element_size (float): The element size (default -1, which means that gmsh determines the size). - - element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. (default 1) - element_shape (:class:`stem.model.ElementShape`): The element shape. TRIANGLE for triangular elements and \ tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. (default TRIANGLE) + - __element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. (default 1) """ - element_size: float = -1 - element_order: int = 1 - element_shape: ElementShape = ElementShape.TRIANGLE # todo implement possibility to choose in gmsh utils + + def __init__(self, element_size: float = -1, element_order: int = 1, + element_shape: ElementShape = ElementShape.TRIANGLE): + """ + Initialize the mesh settings. + + Args: + - element_size (float): The element size (default -1, which means that gmsh determines the size). + - element_order (int): The element order. 1 for linear elements, 2 for quadratic elements. (default 1) + - element_shape (:class:`stem.model.ElementShape`): The element shape. TRIANGLE for triangular elements and \ + tetrahedral elements, QUADRILATERAL for quadrilateral elements and hexahedral elements. (default TRIANGLE) + """ + self.element_size: float = element_size + self.element_shape: ElementShape = element_shape + + self.__element_order: int = element_order + + @property + def element_order(self): + """ + Get the element order. + + Returns: + - int: element order + """ + return self.__element_order + + @element_order.setter + def element_order(self, element_order: int): + """ + Set the element order. The element order must be 1 or 2. + + Args: + - element_order (int): element order + + Raises: + - ValueError: If the element order is not 1 or 2. + """ + + if self.element_order not in [1, 2]: + raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") + + self.__element_order = element_order + def __post_init__(self): """ @@ -43,6 +83,7 @@ def __post_init__(self): if self.element_order not in [1, 2]: raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") + class Node: """ Class containing information about a node diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 8cf139fe3..a1d0d50ff 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -200,3 +200,18 @@ def test_create_mesh_from_non_existing_group(self): Mesh.create_mesh_from_gmsh_group(mesh_data, "non_existing_group") +class TestMeshSettings: + """ + Test the mesh settings class. + """ + + def test_validation_element_order(self): + """ + Test the validation of the element order. + + """ + + # test if ValueError is raised when element_order is not 1 or 2 + with pytest.raises(ValueError): + + mesh_settings = MeshSettings(element_order=3) From 09ed8c80d421f13725e0d050a7d70c091f60c44f Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 21 Jul 2023 14:42:04 +0200 Subject: [PATCH 062/116] added element order as a property such that it can be validated --- stem/mesh.py | 16 ++++------------ tests/test_mesh.py | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/stem/mesh.py b/stem/mesh.py index 76e6e583e..329c20b10 100644 --- a/stem/mesh.py +++ b/stem/mesh.py @@ -43,6 +43,9 @@ def __init__(self, element_size: float = -1, element_order: int = 1, self.element_size: float = element_size self.element_shape: ElementShape = element_shape + if element_order not in [1, 2]: + raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") + self.__element_order: int = element_order @property @@ -67,23 +70,12 @@ def element_order(self, element_order: int): - ValueError: If the element order is not 1 or 2. """ - if self.element_order not in [1, 2]: + if element_order not in [1, 2]: raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") self.__element_order = element_order - def __post_init__(self): - """ - Post initialization of the mesh settings. Checks if the element order is 1 or 2. - - Raises: - - ValueError: If the element order is not 1 or 2. - """ - if self.element_order not in [1, 2]: - raise ValueError("The element order must be 1 or 2. Higher order elements are not supported.") - - class Node: """ Class containing information about a node diff --git a/tests/test_mesh.py b/tests/test_mesh.py index a1d0d50ff..fd0f98fed 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -205,13 +205,25 @@ class TestMeshSettings: Test the mesh settings class. """ - def test_validation_element_order(self): + def test_validation_element_order_at_initialisation_expected_raise(self): """ - Test the validation of the element order. + Test the validation of the element order. Expected to raise a ValueError when the element order is not 1 or 2. """ # test if ValueError is raised when element_order is not 1 or 2 with pytest.raises(ValueError): - mesh_settings = MeshSettings(element_order=3) + MeshSettings(element_order=3) + + def test_validation_element_order_after_initialisation_expected_raise(self): + """ + Test the validation of the element order. Expected to raise a ValueError when the element order is not 1 or 2. + + """ + + # test if ValueError is raised when element_order is not 1 or 2 + mesh_settings = MeshSettings() + + with pytest.raises(ValueError): + mesh_settings.element_order = 3 \ No newline at end of file From 0b1a778f66a3ce4742d8809cdf1d796331eb3f92 Mon Sep 17 00:00:00 2001 From: morettid Date: Fri, 21 Jul 2023 15:06:24 +0200 Subject: [PATCH 063/116] Adjust type settings for model_part and utils --- stem/model_part.py | 12 ++++++++++-- stem/utils.py | 16 ++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/stem/model_part.py b/stem/model_part.py index 351b8955a..acdfee2da 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -1,5 +1,8 @@ from typing import Optional, Union, Dict, Any +from stem.load import LoadParametersABC +from stem.boundary import BoundaryParametersABC +from stem.additional_processes import AdditionalProcessesParametersABC from stem.soil_material import SoilMaterial from stem.structural_material import StructuralMaterial @@ -17,7 +20,10 @@ class ModelPart: - elements (None): element id followed by connectivities in an array - conditions (None): condition id followed by connectivities in an array - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part - - parameters (Dict[Any,Any]): dictionary containing the model part parameters + - parameters (Optional[Union[:class:`stem.load.LoadParametersABC`, \ + :class:`stem.boundary.BoundaryParametersABC, \ + :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the + model part parameters. """ def __init__(self): self.name: Optional[str] = None @@ -26,7 +32,9 @@ def __init__(self): self.conditions = None # todo define type self.geometry: Optional[Geometry] = None - self.parameters = {} # todo define type + self.parameters: Optional[ + Union[LoadParametersABC, BoundaryParametersABC,AdditionalProcessesParametersABC] + ] = None def get_geometry_from_geo_data(self, geo_data: Dict[str, Any], name: str): """ diff --git a/stem/utils.py b/stem/utils.py index d510e9d31..5c75f588c 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -3,14 +3,14 @@ import numpy as np -def is_collinear(point:Sequence, start_point:Sequence, end_point:Sequence): +def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float]): """ Check if point is aligned with the other two on a line. Points must have the same dimension (2D or 3D) Args: - point (Sequence): point to be tested - start_point (Sequence): first point on the line - end_point (Sequence): second point on the line + point (Sequence[float]): point to be tested + start_point (Sequence[float]): first point on the line + end_point (Sequence[float]): second point on the line Returns: bool: whether the point is aligned or not @@ -23,14 +23,14 @@ def is_collinear(point:Sequence, start_point:Sequence, end_point:Sequence): return np.sum(np.abs(cross_product)) < 1e-06 -def is_point_between_points(point, start_point, end_point): +def is_point_between_points(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float]): """ Check if point is between the other two. Points must have the same dimension (2D or 3D). Args: - point (Sequence): point to be tested - start_point (Sequence): first extreme on the line - end_point (Sequence): second extreme on the line + point (Sequence[float]): point to be tested + start_point (Sequence[float]): first extreme on the line + end_point (Sequence[float]): second extreme on the line Returns: bool: whether the point is between the other two or not From 1f718adc8ca9ec144b29845310365fa1e2cabb2d Mon Sep 17 00:00:00 2001 From: morettid Date: Fri, 21 Jul 2023 15:26:55 +0200 Subject: [PATCH 064/116] small fixes --- stem/model.py | 8 +++----- stem/model_part.py | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/stem/model.py b/stem/model.py index fcf3c5b0f..5e1c40ac3 100644 --- a/stem/model.py +++ b/stem/model.py @@ -163,12 +163,11 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p if isinstance(load_parameters, MovingLoad): self.__validate_moving_load_parameters(coordinates, load_parameters) - # create body model part - model_part = ModelPart() - model_part.name = name + # create model part + model_part = ModelPart(name) model_part.parameters = load_parameters - # set the geometry of the body model part + # set the geometry of the model part model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, name) self.process_model_parts.append(model_part) @@ -195,7 +194,6 @@ def __validate_coordinates(coordinates: Sequence[Sequence[float]]): if len(coordinate) > 3 or len(coordinate) < 1: raise ValueError(f"Coordinate should be either 2D or 3D but {len(coordinate)} was given") - @staticmethod def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], load_parameters: MovingLoad): """ diff --git a/stem/model_part.py b/stem/model_part.py index 3ed163018..ced69e91a 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -20,7 +20,6 @@ class ModelPart: - __name (str): name of the model part - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part - - parameters (Dict[Any,Any]): dictionary containing the model part parameters - parameters (Optional[Union[:class:`stem.load.LoadParametersABC`, \ :class:`stem.boundary.BoundaryParametersABC, \ :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the From 13fbb466a231beed70afd459016b7a952230106b Mon Sep 17 00:00:00 2001 From: morettid Date: Fri, 21 Jul 2023 15:52:22 +0200 Subject: [PATCH 065/116] change in attribute name for excavation process --- stem/additional_processes.py | 4 ++-- tests/test_kratos_additional_processes_io.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stem/additional_processes.py b/stem/additional_processes.py index c43d7432c..26b07cef7 100644 --- a/stem/additional_processes.py +++ b/stem/additional_processes.py @@ -20,7 +20,7 @@ class Excavation(AdditionalProcessesParametersABC): - :class:`AdditionalProcessesParametersABC` Attributes: - - deactivate_soil_part (bool): Deactivate or not the body model part + - deactivate_body_model_part (bool): Deactivate or not the body model part """ - deactivate_soil_part: bool + deactivate_body_model_part: bool diff --git a/tests/test_kratos_additional_processes_io.py b/tests/test_kratos_additional_processes_io.py index 00da44a88..e54514e30 100644 --- a/tests/test_kratos_additional_processes_io.py +++ b/tests/test_kratos_additional_processes_io.py @@ -17,7 +17,7 @@ def test_create_additional_processes_dictionaries(self): # define constraints # Absorbing boundaries - excavation_parameters = Excavation(deactivate_soil_part=True) + excavation_parameters = Excavation(deactivate_body_model_part=True) # collect the part names and parameters into a dictionary # TODO: change later when model part is implemented From 69a55ea03dc13194c8c2725f867396c494d87d46 Mon Sep 17 00:00:00 2001 From: noordam Date: Mon, 24 Jul 2023 15:50:30 +0200 Subject: [PATCH 066/116] added function to add boundary condition by geometry id --- stem/model.py | 42 +++++++++++++++ stem/model_part.py | 12 ++++- tests/test_model.py | 125 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 176 insertions(+), 3 deletions(-) diff --git a/stem/model.py b/stem/model.py index 97a44fc91..e81063bf4 100644 --- a/stem/model.py +++ b/stem/model.py @@ -7,6 +7,7 @@ from stem.model_part import ModelPart, BodyModelPart from stem.soil_material import * from stem.structural_material import * +from stem.boundary import * from stem.geometry import Geometry from stem.mesh import Mesh, MeshSettings @@ -129,6 +130,47 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], self.body_model_parts.append(body_model_part) + # def __rearrange_line_point_connectivities(self, line_point_connectivities: List[List[int]]) -> List[List[int]]: + # # loop over lines and check if the connectivities have to be reversed + # for i in range(len(line_point_connectivities) - 1): + # + # # connectivities of the current line are reversed if the first point of the current line is in the + # # connectivities of the next line + # if line_point_connectivities[i][0] in line_point_connectivities[i + 1]: + # line_point_connectivities[i].reverse() + # + # # check if last line has to be reversed + # if line_point_connectivities[-1][1] in line_point_connectivities[-2]: + # line_point_connectivities[-1].reverse() + # return line_point_connectivities + + def add_boundary_condition_by_geometry_ids(self, ndim_boundary: int, geometry_ids: Sequence[int], + boundary_parameters: BoundaryParametersABC, name: str): + """ + Add a boundary condition to the model by giving the geometry ids of the boundary condition. + + Args: + - ndim_boundary (int): dimension of the boundary condition + - geometry_ids (Sequence[int]): geometry ids of the boundary condition + - boundary_condition (:class:`stem.boundary_condition.BoundaryCondition`): boundary condition object + - name (str): name of the boundary condition + + """ + + # add physical group to gmsh + self.gmsh_io.add_physical_group(name, ndim_boundary, geometry_ids) + + # create model part + model_part = ModelPart(name) + + # retrieve geometry from gmsh and add to model part + model_part.get_geometry_from_geo_data(self.gmsh_io.geo_data, name) + + # add boundary parameters to model part + model_part.parameters = boundary_parameters + + self.process_model_parts.append(model_part) + def synchronise_geometry(self): """ Synchronise the geometry of all model parts and synchronise the geometry of the whole model. This function diff --git a/stem/model_part.py b/stem/model_part.py index 558cd1a33..272b0049c 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -1,5 +1,8 @@ from typing import Optional, Union, Dict, Any +from stem.additional_processes import AdditionalProcessesParametersABC +from stem.boundary import BoundaryParametersABC +from stem.load import LoadParametersABC from stem.soil_material import SoilMaterial from stem.structural_material import StructuralMaterial @@ -16,7 +19,10 @@ class ModelPart: - __name (str): name of the model part - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part - - parameters (Dict[Any,Any]): dictionary containing the model part parameters + - parameters (Optional[Union[:class:`stem.load.LoadParametersABC`, \ + :class:`stem.boundary.BoundaryParametersABC, \ + :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the \ + model part parameters. """ def __init__(self, name: str): """ @@ -28,7 +34,9 @@ def __init__(self, name: str): self.__name: str = name self.geometry: Optional[Geometry] = None self.mesh: Optional[Mesh] = None - self.parameters: Dict[Any, Any] = {} # todo define type + self.parameters: Optional[ + Union[LoadParametersABC, BoundaryParametersABC, AdditionalProcessesParametersABC] + ] = None @property def name(self): diff --git a/tests/test_model.py b/tests/test_model.py index 2c0edf33e..db237654d 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -2,9 +2,11 @@ import pickle import pytest +import numpy.testing as npt from stem.model import * from stem.geometry import * +from stem.boundary import * class TestModel: @@ -1044,4 +1046,125 @@ def test_validate_expected_fail_no_name(self): model_part1 = ModelPart(None) model.process_model_parts = [model_part1] - pytest.raises(ValueError, model.validate) \ No newline at end of file + pytest.raises(ValueError, model.validate) + + def test_add_boundary_condition_by_geometry_ids(self,create_default_3d_soil_material: SoilMaterial): + """ + Test if a boundary condition is added correctly to the model. A boundary condition is added to the model by + specifying the geometry ids to which the boundary condition should be applied. + + Args: + - create_default_3d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + + """ + + + # create a 3D model + model = Model(3) + model.extrusion_length = [0, 0, 1] + + # create multiple boundary condition parameters + no_rotation_parameters = RotationConstraint(active=[True, True, True], is_fixed=[True, True, True], + value=[0, 0, 0]) + + absorbing_parameters = AbsorbingBoundary(absorbing_factors=[1,1], virtual_thickness=0) + + no_displacement_parameters = DisplacementConstraint(active=[True, True, True], is_fixed=[True, True, True], + value=[0, 0, 0]) + + # add body model part + soil_material = create_default_3d_soil_material + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)], soil_material, "test_soil") + + + # add boundary conditions in 0d, 1d and 2d + model.add_boundary_condition_by_geometry_ids(0, [1, 2], no_rotation_parameters, "no_rotation") + model.add_boundary_condition_by_geometry_ids(1, [8], absorbing_parameters, "absorbing") + model.add_boundary_condition_by_geometry_ids(2, [1, 2], no_displacement_parameters, "no_displacement") + + model.synchronise_geometry() + + # set expected parameters of the boundary conditions + expected_0d_model_part_parameters = RotationConstraint(active=[True, True, True], is_fixed=[True, True, True], + value=[0, 0, 0]) + + expected_1d_model_part_parameters = AbsorbingBoundary(absorbing_factors=[1, 1], virtual_thickness=0) + + expected_2d_model_part_parameters = DisplacementConstraint(active=[True, True, True], + is_fixed=[True, True, True], value=[0, 0, 0]) + + # set expected geometry 0d boundary condition + expected_boundary_points = [Point.create([0, 0, 0], 1), Point.create([1, 0, 0], 2)] + expected_boundary_lines = [Line.create([1, 2], 1)] + expected_boundary_surfaces = [] + expected_boundary_volumes = [] + + expected_boundary_geometry_0d = Geometry(expected_boundary_points, expected_boundary_lines, + expected_boundary_surfaces, expected_boundary_volumes) + + # set expected geometry 1d boundary condition + expected_boundary_points = [Point.create([1, 1, 0], 3), Point.create([1, 1, 1], 7)] + expected_boundary_lines = [Line.create([3, 7], 8)] + expected_boundary_surfaces = [] + expected_boundary_volumes = [] + + expected_boundary_geometry_1d = Geometry(expected_boundary_points, expected_boundary_lines, + expected_boundary_surfaces, expected_boundary_volumes) + + # set expected geometry 2d boundary condition + expected_boundary_points = [Point.create([0, 0, 0], 1), Point.create([1, 0, 0], 2), Point.create([1, 1, 0], 3), + Point.create([0, 1, 0], 4), Point.create([0, 0, 1], 5), Point.create([1, 0, 1], 6)] + + expected_boundary_lines = [Line.create([1, 2], 1), Line.create([2, 3], 2), Line.create([3, 4], 3), + Line.create([4, 1], 4), Line.create([1, 5], 5), Line.create([5, 6], 7), + Line.create([2, 6], 6)] + + expected_boundary_surfaces = [Surface.create([1, 2, 3, 4], 1), Surface.create([5, 7, -6, -1], 2)] + + expected_boundary_volumes = [] + + expected_boundary_geometry_2d = Geometry(expected_boundary_points, expected_boundary_lines, + expected_boundary_surfaces, expected_boundary_volumes) + + + # collect all expected geometries + all_expected_geometries = [expected_boundary_geometry_0d, expected_boundary_geometry_1d, + expected_boundary_geometry_2d] + + # check 0d parameters + npt.assert_allclose(model.process_model_parts[0].parameters.active, expected_0d_model_part_parameters.active) + npt.assert_allclose(model.process_model_parts[0].parameters.is_fixed, expected_0d_model_part_parameters.is_fixed) + npt.assert_allclose(model.process_model_parts[0].parameters.value, expected_0d_model_part_parameters.value) + + # check 1d parameters + npt.assert_allclose(model.process_model_parts[1].parameters.absorbing_factors, + expected_1d_model_part_parameters.absorbing_factors) + npt.assert_allclose(model.process_model_parts[1].parameters.virtual_thickness, + expected_1d_model_part_parameters.virtual_thickness) + + # check 2d parameters + npt.assert_allclose(model.process_model_parts[2].parameters.active, expected_2d_model_part_parameters.active) + npt.assert_allclose(model.process_model_parts[2].parameters.is_fixed, expected_2d_model_part_parameters.is_fixed) + npt.assert_allclose(model.process_model_parts[2].parameters.value, expected_2d_model_part_parameters.value) + + for expected_geometry, model_part in zip(all_expected_geometries, model.process_model_parts): + + # check if points are added correctly + for generated_point, expected_point in zip(model_part.geometry.points, expected_geometry.points): + assert generated_point.id == expected_point.id + assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + + # check if lines are added correctly + for generated_line, expected_line in zip(model_part.geometry.lines, expected_geometry.lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(model_part.geometry.surfaces, expected_geometry.surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + # check if volumes are added correctly + for generated_volume, expected_volume in zip(model_part.geometry.volumes, expected_geometry.volumes): + assert generated_volume.id == expected_volume.id + assert generated_volume.surface_ids == expected_volume.surface_ids From 434a846415402fdab146f2d1553e49c57e577eb3 Mon Sep 17 00:00:00 2001 From: noordam Date: Mon, 24 Jul 2023 15:50:52 +0200 Subject: [PATCH 067/116] removed commented code --- stem/model.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/stem/model.py b/stem/model.py index e81063bf4..5cb990c30 100644 --- a/stem/model.py +++ b/stem/model.py @@ -130,20 +130,6 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], self.body_model_parts.append(body_model_part) - # def __rearrange_line_point_connectivities(self, line_point_connectivities: List[List[int]]) -> List[List[int]]: - # # loop over lines and check if the connectivities have to be reversed - # for i in range(len(line_point_connectivities) - 1): - # - # # connectivities of the current line are reversed if the first point of the current line is in the - # # connectivities of the next line - # if line_point_connectivities[i][0] in line_point_connectivities[i + 1]: - # line_point_connectivities[i].reverse() - # - # # check if last line has to be reversed - # if line_point_connectivities[-1][1] in line_point_connectivities[-2]: - # line_point_connectivities[-1].reverse() - # return line_point_connectivities - def add_boundary_condition_by_geometry_ids(self, ndim_boundary: int, geometry_ids: Sequence[int], boundary_parameters: BoundaryParametersABC, name: str): """ From 20cb8362906d0ed7e354bb88bc0424932078d8cb Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 25 Jul 2023 14:09:02 +0200 Subject: [PATCH 068/116] added function to add gravity load to model --- stem/model.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/stem/model.py b/stem/model.py index 97a44fc91..2384b992b 100644 --- a/stem/model.py +++ b/stem/model.py @@ -2,6 +2,7 @@ from enum import Enum from dataclasses import dataclass +import numpy as np from gmsh_utils import gmsh_IO from stem.model_part import ModelPart, BodyModelPart @@ -9,6 +10,7 @@ from stem.structural_material import * from stem.geometry import Geometry from stem.mesh import Mesh, MeshSettings +from stem.load import * class Model: @@ -195,6 +197,68 @@ def __validate_model_part_names(self): raise ValueError("All model parts must have a unique name") unique_names.append(model_part.name) + def __add_gravity_model_part(self, gravity_load: GravityLoad, ndim: int, geometry_ids: Sequence[int]): + """ + Add a gravity model part to the complete model. + + Args: + - gravity_load (GravityLoad): The gravity load object. + - ndim (int): The number of dimensions of the on which the gravity load should be applied. + - geometry_ids (Sequence[int]): The geometry on which the gravity load should be applied. + + """ + + # set new model part name + model_part_name = f"gravity_load_{ndim}d" + + # create new gravity physical group and model part + self.gmsh_io.add_physical_group(model_part_name, ndim, geometry_ids) + model_part = ModelPart(model_part_name) + + model_part.parameters = gravity_load + + # add gravity load to process model parts + self.process_model_parts.append(model_part) + + def add_gravity_load(self, gravity_value: float = -9.81, vertical_axis: int = 1): + """ + Add a gravity load to the complete model. + + Args: + - gravity_value (float): The gravity value [m/s^2]. (default -9.81) + - vertical_axis (int): The vertical axis of the model. x=>0, y=>1, z=>2. (default y, 1) + + """ + + # set gravity load at vertical axis + gravity_load_values = [0, 0, 0] + gravity_load_values[vertical_axis] = gravity_value + gravity_load = GravityLoad(value=gravity_load_values, active=[True, True, True]) + + # get all body model part names + body_model_part_names = [body_model_part.name for body_model_part in self.body_model_parts] + + # get geometry ids and ndim for each body model part + model_parts_geometry_ids = np.array([self.gmsh_io.geo_data["physical_groups"][name]["geometry_ids"] for name in + body_model_part_names]) + + model_parts_ndim = np.array([self.gmsh_io.geo_data["physical_groups"][name]["ndim"] + for name in body_model_part_names]) + + # add gravity load as physical group per dimension + body_geometries_1d = model_parts_geometry_ids[model_parts_ndim == 1] + if len(body_geometries_1d) > 0: + self.__add_gravity_model_part(gravity_load, 1, body_geometries_1d) + + body_geometries_2d = model_parts_geometry_ids[model_parts_ndim == 2] + if len(body_geometries_2d) > 0: + self.__add_gravity_model_part(gravity_load, 2, body_geometries_1d) + + body_geometries_3d = model_parts_geometry_ids[model_parts_ndim == 3] + if len(body_geometries_3d) > 0: + self.__add_gravity_model_part(gravity_load, 3, body_geometries_1d) + + def validate(self): """ Validate the model. \ From c79315cd4fb148b6e6858ea2157cde5177bb4dd4 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 25 Jul 2023 15:05:42 +0200 Subject: [PATCH 069/116] added test for adding gravity load in 1d and 2d --- stem/model.py | 9 ++--- tests/test_model.py | 86 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/stem/model.py b/stem/model.py index 2384b992b..f7403dfd3 100644 --- a/stem/model.py +++ b/stem/model.py @@ -243,21 +243,22 @@ def add_gravity_load(self, gravity_value: float = -9.81, vertical_axis: int = 1) body_model_part_names]) model_parts_ndim = np.array([self.gmsh_io.geo_data["physical_groups"][name]["ndim"] - for name in body_model_part_names]) + for name in body_model_part_names]).ravel() # add gravity load as physical group per dimension - body_geometries_1d = model_parts_geometry_ids[model_parts_ndim == 1] + body_geometries_1d = model_parts_geometry_ids[model_parts_ndim == 1].ravel() if len(body_geometries_1d) > 0: self.__add_gravity_model_part(gravity_load, 1, body_geometries_1d) - body_geometries_2d = model_parts_geometry_ids[model_parts_ndim == 2] + body_geometries_2d = model_parts_geometry_ids[model_parts_ndim == 2].ravel() if len(body_geometries_2d) > 0: self.__add_gravity_model_part(gravity_load, 2, body_geometries_1d) - body_geometries_3d = model_parts_geometry_ids[model_parts_ndim == 3] + body_geometries_3d = model_parts_geometry_ids[model_parts_ndim == 3].ravel() if len(body_geometries_3d) > 0: self.__add_gravity_model_part(gravity_load, 3, body_geometries_1d) + self.synchronise_geometry() def validate(self): """ diff --git a/tests/test_model.py b/tests/test_model.py index 2c0edf33e..6a0007f63 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -2,6 +2,7 @@ import pickle import pytest +import numpy.testing as npt from stem.model import * from stem.geometry import * @@ -1044,4 +1045,87 @@ def test_validate_expected_fail_no_name(self): model_part1 = ModelPart(None) model.process_model_parts = [model_part1] - pytest.raises(ValueError, model.validate) \ No newline at end of file + pytest.raises(ValueError, model.validate) + + def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilMaterial): + """ + Test if a gravity load is added correctly to the model in a 2d space containing 1d and 2d elements. A gravity + load is generated and added to the model. + + """ + + # create model + model = Model(2) + + # add a 2d layer + + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1,1,0)], create_default_2d_soil_material, "soil1") + + # add a 1d layer + layer_settings = {"beam": {"ndim": 1, + "element_size": -1, + "coordinates": [[0, 0, 0], [1, 0, 0]]}} + + model.gmsh_io.generate_geometry(layer_settings,"") + model.synchronise_geometry() + + # add 1d model part to model + body_model_part = BodyModelPart("beam") + body_model_part.material = EulerBeam(ndim=2, YOUNG_MODULUS=1e6, POISSON_RATIO=0.3, DENSITY=1, CROSS_AREA=1, + I33=1) + body_model_part.get_geometry_from_geo_data(model.gmsh_io.geo_data, "beam") + + model.body_model_parts.append(body_model_part) + + # add gravity load + model.add_gravity_load() + + assert len(model.process_model_parts) == 2 + assert model.process_model_parts[0].name == "gravity_load_1d" + assert model.process_model_parts[1].name == "gravity_load_2d" + + # setup expected geometries for 1d and 2d + expected_geometry_points_1d = [Point.create([0, 0, 0],1), Point.create([1, 0, 0], 2)] + expected_geometry_lines_1d = [Line.create([1, 2], 1)] + expected_geometry_gravity_1d = Geometry(expected_geometry_points_1d, expected_geometry_lines_1d, [], []) + + expected_geometry_points_2d = [Point.create([0, 0, 0], 1), Point.create([1, 0, 0], 2), + Point.create([1, 1, 0], 3)] + expected_geometry_lines_2d = [Line.create([1, 2], 1), Line.create([2, 3], 2), Line.create([3, 1], 3)] + expected_geometry_surfaces_2d = [Surface.create([1, 2, 3], 1)] + expected_geometry_gravity_2d = Geometry(expected_geometry_points_2d, expected_geometry_lines_2d, + expected_geometry_surfaces_2d, []) + + expected_geometries = [expected_geometry_gravity_1d, expected_geometry_gravity_2d] + + # check if all process model parts are correct + for model_part in model.process_model_parts: + + # check if parameters are added correctly + npt.assert_allclose(model_part.parameters.value, [0, -9.81, 0]) + npt.assert_allclose(model_part.parameters.active, [True, True, True]) + + # check if geometry is added correctly + generated_model_part = model_part.geometry + + # check if points are added correctly + for generated_point, expected_point in zip(generated_model_part.points, expected_geometries[0].points): + assert generated_point.id == expected_point.id + npt.assert_allclose(generated_point.coordinates,expected_point.coordinates) + + # check if lines are added correctly + for generated_line, expected_line in zip(generated_model_part.lines, expected_geometries[0].lines): + assert generated_line.id == expected_line.id + assert generated_line.point_ids == expected_line.point_ids + + # check if surfaces are added correctly + for generated_surface, expected_surface in zip(generated_model_part.surfaces, + expected_geometries[0].surfaces): + assert generated_surface.id == expected_surface.id + assert generated_surface.line_ids == expected_surface.line_ids + + + + + + a=1+1 From 98619e494b52626ec271a2b8345848dfe87da979 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 25 Jul 2023 17:03:45 +0200 Subject: [PATCH 070/116] added test for adding gravity load in 2D and 3D --- stem/model.py | 4 +-- tests/test_model.py | 68 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/stem/model.py b/stem/model.py index f7403dfd3..87cbbd9da 100644 --- a/stem/model.py +++ b/stem/model.py @@ -252,11 +252,11 @@ def add_gravity_load(self, gravity_value: float = -9.81, vertical_axis: int = 1) body_geometries_2d = model_parts_geometry_ids[model_parts_ndim == 2].ravel() if len(body_geometries_2d) > 0: - self.__add_gravity_model_part(gravity_load, 2, body_geometries_1d) + self.__add_gravity_model_part(gravity_load, 2, body_geometries_2d) body_geometries_3d = model_parts_geometry_ids[model_parts_ndim == 3].ravel() if len(body_geometries_3d) > 0: - self.__add_gravity_model_part(gravity_load, 3, body_geometries_1d) + self.__add_gravity_model_part(gravity_load, 3, body_geometries_3d) self.synchronise_geometry() diff --git a/tests/test_model.py b/tests/test_model.py index 6a0007f63..d8d9f3889 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1058,15 +1058,14 @@ def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilM model = Model(2) # add a 2d layer - - model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1,1,0)], create_default_2d_soil_material, "soil1") + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0)], create_default_2d_soil_material, "soil1") # add a 1d layer layer_settings = {"beam": {"ndim": 1, "element_size": -1, "coordinates": [[0, 0, 0], [1, 0, 0]]}} - model.gmsh_io.generate_geometry(layer_settings,"") + model.gmsh_io.generate_geometry(layer_settings, "") model.synchronise_geometry() # add 1d model part to model @@ -1124,8 +1123,69 @@ def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilM assert generated_surface.id == expected_surface.id assert generated_surface.line_ids == expected_surface.line_ids + def test_add_gravity_load_two_layers_same_dimension(self, create_default_2d_soil_material: SoilMaterial): + """ + Test if a gravity load is added correctly to the model in a 2d space containing 2 layers. A gravity load is + generated and added to the model. + + Args: + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + + """ + + # create model + model = Model(2) + + # add a 2d layer + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0)], create_default_2d_soil_material, "soil1") + model.add_soil_layer_by_coordinates([(1, 0, 0), (0, 0, 0), (1, -1, 0)], create_default_2d_soil_material, "soil2") + + model.synchronise_geometry() + + # add gravity load + model.add_gravity_load(-12,0) + + assert len(model.process_model_parts) == 1 + + generated_geometry = model.process_model_parts[0].geometry + + # check if number of points, lines, surfaces are correct, i.e. if the number of points, lines, surfaces are the + # same as the number of points, lines, surfaces of the model geometry + assert len(generated_geometry.points) == len(model.geometry.points) == 4 + assert len(generated_geometry.lines) == len(model.geometry.lines) == 5 + assert len(generated_geometry.surfaces) == len(model.geometry.surfaces) == 2 + + assert model.process_model_parts[0].name == "gravity_load_2d" + npt.assert_allclose(model.process_model_parts[0].parameters.value, [-12, 0, 0]) + npt.assert_allclose(model.process_model_parts[0].parameters.active, [True, True, True]) + + + def test_add_gravity_load_3d(self, create_default_3d_soil_material): + + # create model + model = Model(3) + model.extrusion_length = [0, 0, 1] + + # add a 2d layer + model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0)], create_default_3d_soil_material, "soil1") + + model.synchronise_geometry() + + # add gravity load + model.add_gravity_load(vertical_axis=2, gravity_value=-10) + + assert len(model.process_model_parts) == 1 + generated_geometry = model.process_model_parts[0].geometry + # check if number of points, lines, surfaces are correct, i.e. if the number of points, lines, surfaces and + # volumes are the same as the number of points, lines, surfaces and volumes of the model geometry + assert len(generated_geometry.points) == len(model.geometry.points) == 6 + assert len(generated_geometry.lines) == len(model.geometry.lines) == 9 + assert len(generated_geometry.surfaces) == len(model.geometry.surfaces) == 5 + assert len(generated_geometry.volumes) == len(model.geometry.volumes) == 1 + assert model.process_model_parts[0].name == "gravity_load_3d" + npt.assert_allclose(model.process_model_parts[0].parameters.value, [0, 0, -10]) + npt.assert_allclose(model.process_model_parts[0].parameters.active, [True, True, True]) - a=1+1 From 4847c0ce9ae6b46bf0d7c98559533ef9f7ea2031 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 25 Jul 2023 17:05:41 +0200 Subject: [PATCH 071/116] corrected docstrings --- tests/test_model.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index d8d9f3889..d4a231545 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1052,6 +1052,9 @@ def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilM Test if a gravity load is added correctly to the model in a 2d space containing 1d and 2d elements. A gravity load is generated and added to the model. + Args: + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + """ # create model @@ -1159,8 +1162,15 @@ def test_add_gravity_load_two_layers_same_dimension(self, create_default_2d_soil npt.assert_allclose(model.process_model_parts[0].parameters.value, [-12, 0, 0]) npt.assert_allclose(model.process_model_parts[0].parameters.active, [True, True, True]) - def test_add_gravity_load_3d(self, create_default_3d_soil_material): + """ + Test if a gravity load is added correctly to the model in a 3d space. A gravity load is generated and added to + the model. + + Args: + - create_default_3d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + + """ # create model model = Model(3) From ef0befa9c319511d85c37ef5059a6ae6528792c6 Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 25 Jul 2023 17:22:55 +0200 Subject: [PATCH 072/116] made add gravity load private as it depends on the stress initialisation type --- stem/model.py | 36 ++++++++++++++++++++++++++++++++++-- tests/test_model.py | 6 +++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/stem/model.py b/stem/model.py index 87cbbd9da..6ba930e01 100644 --- a/stem/model.py +++ b/stem/model.py @@ -11,6 +11,7 @@ from stem.geometry import Geometry from stem.mesh import Mesh, MeshSettings from stem.load import * +from stem.solver import Problem, StressInitialisationType class Model: @@ -29,7 +30,7 @@ class Model: """ def __init__(self, ndim: int): self.ndim: int = ndim - self.project_parameters = None + self.project_parameters: Optional[Problem] = None self.solver = None self.geometry: Optional[Geometry] = None self.mesh_settings: MeshSettings = MeshSettings() @@ -220,7 +221,7 @@ def __add_gravity_model_part(self, gravity_load: GravityLoad, ndim: int, geometr # add gravity load to process model parts self.process_model_parts.append(model_part) - def add_gravity_load(self, gravity_value: float = -9.81, vertical_axis: int = 1): + def __add_gravity_load(self, gravity_value: float = -9.81, vertical_axis: int = 1): """ Add a gravity load to the complete model. @@ -269,4 +270,35 @@ def validate(self): self.__validate_model_part_names() + def __setup_stress_initialisation(self): + """ + Set up the stress initialisation. For K0 procedure and gravity loading, a gravity load is added to the model. + + """ + + # add gravity load if K0 procedure or gravity loading is used + if (self.project_parameters.settings.stress_initialisation_type == + StressInitialisationType.K0_PROCEDURE) or \ + (self.project_parameters.settings.stress_initialisation_type == + StressInitialisationType.GRAVITY_LOADING): + + self.__add_gravity_load() + + def post_setup(self): + """ + Post setup of the model. \ + - Synchronise the geometry. \ + - Generate the mesh. \ + - Validate the model. \ + - Set up the stress initialisation. + + """ + + self.synchronise_geometry() + self.generate_mesh() + self.validate() + + self.__setup_stress_initialisation() + + diff --git a/tests/test_model.py b/tests/test_model.py index d4a231545..3085d1b55 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1080,7 +1080,7 @@ def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilM model.body_model_parts.append(body_model_part) # add gravity load - model.add_gravity_load() + model._Model__add_gravity_load() assert len(model.process_model_parts) == 2 assert model.process_model_parts[0].name == "gravity_load_1d" @@ -1146,7 +1146,7 @@ def test_add_gravity_load_two_layers_same_dimension(self, create_default_2d_soil model.synchronise_geometry() # add gravity load - model.add_gravity_load(-12,0) + model._Model__add_gravity_load(-12,0) assert len(model.process_model_parts) == 1 @@ -1182,7 +1182,7 @@ def test_add_gravity_load_3d(self, create_default_3d_soil_material): model.synchronise_geometry() # add gravity load - model.add_gravity_load(vertical_axis=2, gravity_value=-10) + model._Model__add_gravity_load(vertical_axis=2, gravity_value=-10) assert len(model.process_model_parts) == 1 From a68dd26166fc12602b32962a3843ac86777945cc Mon Sep 17 00:00:00 2001 From: noordam Date: Tue, 25 Jul 2023 17:39:51 +0200 Subject: [PATCH 073/116] added test for setting up stress initialisation --- tests/test_model.py | 74 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index 3085d1b55..e82403f00 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -6,6 +6,7 @@ from stem.model import * from stem.geometry import * +from stem.solver import * class TestModel: @@ -1199,3 +1200,76 @@ def test_add_gravity_load_3d(self, create_default_3d_soil_material): npt.assert_allclose(model.process_model_parts[0].parameters.value, [0, 0, -10]) npt.assert_allclose(model.process_model_parts[0].parameters.active, [True, True, True]) + def test_setup_stress_initialisation(self, create_default_2d_soil_material: SoilMaterial): + """ + Test if the stress initialisation is set up correctly. A model is created with a soil layer. It is checked if + gravity is added in case the K0 procedure or gravity loading is used. + + Args: + - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. + + """ + + # set up solver settings + analysis_type = AnalysisType.MECHANICAL_GROUNDWATER_FLOW + + solution_type = SolutionType.QUASI_STATIC + + time_integration = TimeIntegration(start_time=0.0, end_time=1.0, delta_time=0.1, reduction_factor=0.5, + increase_factor=2.0, max_delta_time_factor=500) + + convergence_criterion = DisplacementConvergenceCriteria() + + stress_initialisation_type = StressInitialisationType.NONE + + solver_settings = SolverSettings(analysis_type=analysis_type, solution_type=solution_type, + stress_initialisation_type=stress_initialisation_type, + time_integration=time_integration, + is_stiffness_matrix_constant=True, are_mass_and_damping_constant=True, + convergence_criteria=convergence_criterion) + + # set up problem data + problem_data = Problem(problem_name="test", number_of_threads=2, settings=solver_settings) + + model_no_gravity = Model(2) + model_no_gravity.project_parameters = problem_data + + # set up soil material + soil_material = create_default_2d_soil_material + model_no_gravity.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0)], soil_material, "soil1") + model_no_gravity.synchronise_geometry() + + # setup_stress_initialisation + model_no_gravity._Model__setup_stress_initialisation() + + model_k0 = Model(2) + model_k0.project_parameters = problem_data + + model_k0.project_parameters.settings.stress_initialisation_type = StressInitialisationType.K0_PROCEDURE + model_k0.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0)], soil_material, "soil1") + model_k0.synchronise_geometry() + + # setup_stress_initialisation + model_k0._Model__setup_stress_initialisation() + + model_gravity_loading = Model(2) + model_gravity_loading.project_parameters = problem_data + + model_gravity_loading.project_parameters.settings.stress_initialisation_type = \ + StressInitialisationType.GRAVITY_LOADING + model_gravity_loading.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0)], soil_material, "soil1") + model_gravity_loading.synchronise_geometry() + + # setup_stress_initialisation + model_gravity_loading._Model__setup_stress_initialisation() + + assert len(model_no_gravity.process_model_parts) == 0 + assert len(model_k0.process_model_parts) == 1 + assert len(model_gravity_loading.process_model_parts) == 1 + + assert model_k0.process_model_parts[0].name == "gravity_load_2d" + assert model_gravity_loading.process_model_parts[0].name == "gravity_load_2d" + + @pytest.mark.skip("Not implemented yet") + def test_post_setup(self): + pass \ No newline at end of file From 6680d2585dea1dce4eb7ad8886ac54db823075b5 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:44:28 +0200 Subject: [PATCH 074/116] Update stem/model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/model.py b/stem/model.py index 5e1c40ac3..c778c3e7b 100644 --- a/stem/model.py +++ b/stem/model.py @@ -155,7 +155,7 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p else: # TODO: deal with Gravity loads raise ValueError(f'Invalid load_parameters ({load_parameters.__class__.__name__}) object' - f' provided for the load {name}.Expected one of PointLoad, MovingLoad,' + f' provided for the load {name}. Expected one of PointLoad, MovingLoad,' f' LineLoad or SurfaceLoad.') self.gmsh_io.generate_geometry(gmsh_input, "") From d9b2f13711e56d926a4f97fc174a159a78d1500d Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:45:09 +0200 Subject: [PATCH 075/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 8bdfd824b..266765384 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -924,7 +924,7 @@ def test_add_line_load_to_3_edges(self, expected_geometry_line_load: Geometry, # create model model = Model(ndim) - # add soil layer + # add line load model.add_load_by_coordinates(point_coordinates, load_parameters, "line_load_1") # check if layer is added correctly From 4208a73aa8583b1fe82b22a219aedd10cc1c1539 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:45:39 +0200 Subject: [PATCH 076/116] Update tests/utils.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index a17bdef37..3273dfe72 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -55,8 +55,6 @@ def assert_almost_equal_geometries(expected_geometry: Geometry, actual_geometry: """ # check if points are added correctly for generated_point, expected_point in zip(actual_geometry.points, expected_geometry.points): - if generated_point.id != expected_point.id: - a=1+1 assert generated_point.id == expected_point.id assert pytest.approx(generated_point.coordinates) == expected_point.coordinates From 50ca8d4c356edb9475e8aa83da195e87c84d39c8 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:46:20 +0200 Subject: [PATCH 077/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 266765384..731801a29 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -957,7 +957,7 @@ def test_add_moving_point_load(self, create_default_moving_load_parameters: Movi # create model model = Model(ndim) - # add soil layer + # add moving load model.add_load_by_coordinates(point_coordinates, load_parameters, "moving_load_1") # check if layer is added correctly From 2ab00819e1c74d372ceeea2bc30952740f6d27b4 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:46:34 +0200 Subject: [PATCH 078/116] Update stem/model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/model.py b/stem/model.py index c778c3e7b..a2d088a50 100644 --- a/stem/model.py +++ b/stem/model.py @@ -139,7 +139,7 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p Args: - coordinates (Sequence[Sequence[float]]): The coordinates of the load. - - parameters (:class:`stem.load.LoadParametersABC`): The parameters of the load. + - load_parameters (:class:`stem.load.LoadParametersABC`): The parameters of the load. - name (str): The name of the load part. """ From 9e609ce8d1c7eeb8cc280e878f986ccc81f9fe08 Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 26 Jul 2023 11:30:06 +0200 Subject: [PATCH 079/116] solved mypy issues and added test to check if value error is raised --- stem/model.py | 8 +++++++- stem/model_part.py | 12 ++++++++++-- tests/test_model.py | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/stem/model.py b/stem/model.py index 6ba930e01..e8bdee65f 100644 --- a/stem/model.py +++ b/stem/model.py @@ -232,7 +232,7 @@ def __add_gravity_load(self, gravity_value: float = -9.81, vertical_axis: int = """ # set gravity load at vertical axis - gravity_load_values = [0, 0, 0] + gravity_load_values: List[float] = [0, 0, 0] gravity_load_values[vertical_axis] = gravity_value gravity_load = GravityLoad(value=gravity_load_values, active=[True, True, True]) @@ -274,8 +274,14 @@ def __setup_stress_initialisation(self): """ Set up the stress initialisation. For K0 procedure and gravity loading, a gravity load is added to the model. + Raises: + - ValueError: If the project parameters are not set. + """ + if self.project_parameters is None: + raise ValueError("Project parameters must be set before setting up the stress initialisation") + # add gravity load if K0 procedure or gravity loading is used if (self.project_parameters.settings.stress_initialisation_type == StressInitialisationType.K0_PROCEDURE) or \ diff --git a/stem/model_part.py b/stem/model_part.py index 558cd1a33..272b0049c 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -1,5 +1,8 @@ from typing import Optional, Union, Dict, Any +from stem.additional_processes import AdditionalProcessesParametersABC +from stem.boundary import BoundaryParametersABC +from stem.load import LoadParametersABC from stem.soil_material import SoilMaterial from stem.structural_material import StructuralMaterial @@ -16,7 +19,10 @@ class ModelPart: - __name (str): name of the model part - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part - - parameters (Dict[Any,Any]): dictionary containing the model part parameters + - parameters (Optional[Union[:class:`stem.load.LoadParametersABC`, \ + :class:`stem.boundary.BoundaryParametersABC, \ + :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the \ + model part parameters. """ def __init__(self, name: str): """ @@ -28,7 +34,9 @@ def __init__(self, name: str): self.__name: str = name self.geometry: Optional[Geometry] = None self.mesh: Optional[Mesh] = None - self.parameters: Dict[Any, Any] = {} # todo define type + self.parameters: Optional[ + Union[LoadParametersABC, BoundaryParametersABC, AdditionalProcessesParametersABC] + ] = None @property def name(self): diff --git a/tests/test_model.py b/tests/test_model.py index e82403f00..735f41b4b 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1270,6 +1270,22 @@ def test_setup_stress_initialisation(self, create_default_2d_soil_material: Soil assert model_k0.process_model_parts[0].name == "gravity_load_2d" assert model_gravity_loading.process_model_parts[0].name == "gravity_load_2d" + def test_setup_stress_initialisation_without_project_parameters(self): + """ + A model is created without project parameters. It is + checked if a ValueError is raised while setting up the stress initialisation. + + """ + # create model + model = Model(2) + + # test if value error is raised + with pytest.raises(ValueError, + match=r"Project parameters must be set before setting up the stress initialisation"): + model._Model__setup_stress_initialisation() + + + @pytest.mark.skip("Not implemented yet") def test_post_setup(self): pass \ No newline at end of file From b432ff83a379c4d74e7675a5692cd0c7e462a3aa Mon Sep 17 00:00:00 2001 From: noordam Date: Wed, 26 Jul 2023 13:10:19 +0200 Subject: [PATCH 080/116] used npt asser allclose instead of pytest approx --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 205f00849..10e8cd4ef 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1153,7 +1153,7 @@ def test_add_boundary_condition_by_geometry_ids(self,create_default_3d_soil_mate # check if points are added correctly for generated_point, expected_point in zip(model_part.geometry.points, expected_geometry.points): assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + npt.assert_allclose(generated_point.coordinates, expected_point.coordinates) # check if lines are added correctly for generated_line, expected_line in zip(model_part.geometry.lines, expected_geometry.lines): From 27a30e6fa69259b21673f71b9672c17a797465e1 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:13:28 +0200 Subject: [PATCH 081/116] Update stem/model_part.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/model_part.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/model_part.py b/stem/model_part.py index ced69e91a..6574ff7c5 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -22,7 +22,7 @@ class ModelPart: - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part - parameters (Optional[Union[:class:`stem.load.LoadParametersABC`, \ :class:`stem.boundary.BoundaryParametersABC, \ - :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the + :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the \ model part parameters. """ def __init__(self, name: str): From ce080928396ebb600077e7de2bc91b3b01b43b01 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:16:07 +0200 Subject: [PATCH 082/116] Update stem/utils.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stem/utils.py b/stem/utils.py index 5c75f588c..91216917f 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -8,9 +8,9 @@ def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:S Check if point is aligned with the other two on a line. Points must have the same dimension (2D or 3D) Args: - point (Sequence[float]): point to be tested - start_point (Sequence[float]): first point on the line - end_point (Sequence[float]): second point on the line + - point (Sequence[float]): point coordinates to be tested + - start_point (Sequence[float]): coordinates of first point of a line + - end_point (Sequence[float]): coordinates of second point of a line Returns: bool: whether the point is aligned or not From 7ed085b713a98b69648a572411043919085f43c1 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:16:28 +0200 Subject: [PATCH 083/116] Update stem/utils.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/utils.py b/stem/utils.py index 91216917f..0d73133e2 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -13,7 +13,7 @@ def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:S - end_point (Sequence[float]): coordinates of second point of a line Returns: - bool: whether the point is aligned or not + - bool: whether the point is aligned or not """ vec_1 = np.asarray(point) - np.asarray(start_point) From 4e0d78af4c08b7bc6aee4e6fe12cff3ac2bd5cd9 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:17:05 +0200 Subject: [PATCH 084/116] Update stem/utils.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/utils.py b/stem/utils.py index 0d73133e2..128fdf1a4 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -5,7 +5,7 @@ def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float]): """ - Check if point is aligned with the other two on a line. Points must have the same dimension (2D or 3D) + Check if a point is aligned with the two points of a line. Points must have the same dimension (2D or 3D) Args: - point (Sequence[float]): point coordinates to be tested From 5c90b469f57d8cd6f0f2d116631d7e8f34abdb79 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:17:27 +0200 Subject: [PATCH 085/116] Update stem/utils.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stem/utils.py b/stem/utils.py index 128fdf1a4..a5c0ab105 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -28,9 +28,9 @@ def is_point_between_points(point:Sequence[float], start_point:Sequence[float], Check if point is between the other two. Points must have the same dimension (2D or 3D). Args: - point (Sequence[float]): point to be tested - start_point (Sequence[float]): first extreme on the line - end_point (Sequence[float]): second extreme on the line + - point (Sequence[float]): point coordinates to be tested + - start_point (Sequence[float]): first extreme coordinates of the line + - end_point (Sequence[float]): second extreme coordinates of the line Returns: bool: whether the point is between the other two or not From 7735db18aa8c9b27b72af163335b3317b29f3fed Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:20:55 +0200 Subject: [PATCH 086/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 731801a29..cb3546ddb 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -56,7 +56,7 @@ def expected_geometry_single_layer_3D(self): Sets expected geometry data for a 2D geometry group. The group is a geometry of a square. Returns: - - :class:`stem.geometry.Geometry`: geometry of a 2D square + - :class:`stem.geometry.Geometry`: geometry of a 3D cube """ geometry = Geometry() From 7d3bddc3e3d28aec316538fb54079b13f5b0b10b Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:22:06 +0200 Subject: [PATCH 087/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index cb3546ddb..5966bc209 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -884,7 +884,7 @@ def test_add_point_loads_to_2_points(self, create_default_point_load_parameters: # create model model = Model(ndim) - # add soil layer + # add point load model.add_load_by_coordinates(point_coordinates, load_parameters, "point_load_1") # check if layer is added correctly From 5b745d59d48a2b927d8d072df6b7df087732274c Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:22:24 +0200 Subject: [PATCH 088/116] Update stem/utils.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/utils.py b/stem/utils.py index a5c0ab105..89e895322 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -33,7 +33,7 @@ def is_point_between_points(point:Sequence[float], start_point:Sequence[float], - end_point (Sequence[float]): second extreme coordinates of the line Returns: - bool: whether the point is between the other two or not + - bool: whether the point is between the other two or not """ # Calculate vectors between the points From ccf1bd30277e097953f67adb5d4c97d1820da8f0 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:22:44 +0200 Subject: [PATCH 089/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 5966bc209..366545d47 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -256,7 +256,7 @@ def expected_geometry_two_layers_2D_after_sync(self): @pytest.fixture def expected_geometry_line_load(self): """ - Sets expected geometry data for a 2D geometry group. The group is a geometry of a square. + Sets expected geometry data for a 1D geometry group. The group is a geometry of a multi-line. Returns: - :class:`stem.geometry.Geometry`: geometry of a 2D square From a1b60174a1b4216cf070e26b75de5a548837c16e Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:22:58 +0200 Subject: [PATCH 090/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 366545d47..0842774fe 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -259,7 +259,7 @@ def expected_geometry_line_load(self): Sets expected geometry data for a 1D geometry group. The group is a geometry of a multi-line. Returns: - - :class:`stem.geometry.Geometry`: geometry of a 2D square + - :class:`stem.geometry.Geometry`: geometry of a 1D multi-line """ geometry = Geometry() From cad87834d110d428cd3c23f095487a119fa8280f Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:23:12 +0200 Subject: [PATCH 091/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 0842774fe..8597ddb83 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -53,7 +53,7 @@ def expected_geometry_single_layer_2D(self): @pytest.fixture def expected_geometry_single_layer_3D(self): """ - Sets expected geometry data for a 2D geometry group. The group is a geometry of a square. + Sets expected geometry data for a 3D geometry group. The group is a geometry of a cube. Returns: - :class:`stem.geometry.Geometry`: geometry of a 3D cube From 93601fea95294b7801a7a6db86c802671696df1c Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:23:26 +0200 Subject: [PATCH 092/116] Update tests/test_model.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- tests/test_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 8597ddb83..be550b2bc 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -527,7 +527,7 @@ def close_gmsh(self): Initializer to close gmsh if it was not closed before. In case a test fails, the destroyer method is not called on the Model object and gmsh keeps on running. Therefore, nodes, lines, surfaces and volumes ids are not reset to one. This causes also the next test after the failed one to fail as well, which has nothing to do - the test themselves. + the test itself. Returns: - None From 8b796da60ed865b5d140383b8cea287d19b0f287 Mon Sep 17 00:00:00 2001 From: morettid Date: Wed, 26 Jul 2023 16:36:49 +0200 Subject: [PATCH 093/116] first adjustment for review --- run_stem/demo_create_gmsh_mesh.py | 1 - stem/IO/kratos_additional_processes_io.py | 3 +- stem/model.py | 64 ++++++++++++-------- stem/model_part.py | 4 +- stem/utils.py | 53 ++++++++++++++-- tests/test_kratos_additional_processes_io.py | 2 +- tests/test_model.py | 61 ++++++++++++++++--- tests/test_utils.py | 13 +++- 8 files changed, 157 insertions(+), 44 deletions(-) diff --git a/run_stem/demo_create_gmsh_mesh.py b/run_stem/demo_create_gmsh_mesh.py index 54934ca9f..9fb7043c9 100644 --- a/run_stem/demo_create_gmsh_mesh.py +++ b/run_stem/demo_create_gmsh_mesh.py @@ -24,7 +24,6 @@ mesh_output_dir = "./" - gmsh_io = GmshIO() gmsh_io.generate_gmsh_mesh(input_points, extrusion_length, element_size, dims, name_label, mesh_name, mesh_output_dir, diff --git a/stem/IO/kratos_additional_processes_io.py b/stem/IO/kratos_additional_processes_io.py index db4fdb66a..759f9419b 100644 --- a/stem/IO/kratos_additional_processes_io.py +++ b/stem/IO/kratos_additional_processes_io.py @@ -42,11 +42,12 @@ def __create_excavation_dict( "python_module": "apply_excavation_process", "kratos_module": "KratosMultiphysics.GeoMechanicsApplication", "process_name": "ApplyExcavationProcess", - "Parameters": parameters.__dict__, + "Parameters": {}, } boundary_dict["Parameters"]["model_part_name"] = f"{self.domain}.{part_name}" boundary_dict["Parameters"]["variable_name"] = "EXCAVATION" + boundary_dict["Parameters"]["deactivate_soil_part"] = parameters.deactivate_body_model_part return boundary_dict diff --git a/stem/model.py b/stem/model.py index a2d088a50..6d34239ea 100644 --- a/stem/model.py +++ b/stem/model.py @@ -11,7 +11,7 @@ from stem.load import * from stem.geometry import Geometry from stem.mesh import Mesh, MeshSettings -from stem.utils import is_point_between_points, is_collinear +from stem.utils import is_point_between_points, is_collinear, is_non_string_sequence class Model: @@ -110,6 +110,8 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], :class:`stem.structural_material.StructuralMaterial`]): The material parameters of the soil layer. - name (str): The name of the soil layer. + Raises: + - ValueError: if extrusion_length is not specified. """ gmsh_input = {name: {"coordinates": coordinates, "ndim": self.ndim}} @@ -142,13 +144,19 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p - load_parameters (:class:`stem.load.LoadParametersABC`): The parameters of the load. - name (str): The name of the load part. + Raises: + - ValueError: if load_parameters is not of one of the classes PointLoad, MovingLoad, LineLoad + or SurfaceLoad. """ - self.__validate_coordinates(coordinates) + # validation of inputs + self.validate_coordinates(coordinates) + if isinstance(load_parameters, MovingLoad): + self.__validate_moving_load_parameters(coordinates, load_parameters) if isinstance(load_parameters, PointLoad): gmsh_input = {name: {"coordinates": coordinates, "ndim": 0}} - elif isinstance(load_parameters, get_args(Union[LineLoad, MovingLoad])): + elif isinstance(load_parameters, LineLoad) or isinstance(load_parameters, MovingLoad): gmsh_input = {name: {"coordinates": coordinates, "ndim": 1}} elif isinstance(load_parameters, SurfaceLoad): gmsh_input = {name: {"coordinates": coordinates, "ndim": 2}} @@ -160,9 +168,6 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p self.gmsh_io.generate_geometry(gmsh_input, "") - if isinstance(load_parameters, MovingLoad): - self.__validate_moving_load_parameters(coordinates, load_parameters) - # create model part model_part = ModelPart(name) model_part.parameters = load_parameters @@ -173,26 +178,30 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p self.process_model_parts.append(model_part) @staticmethod - def __validate_coordinates(coordinates: Sequence[Sequence[float]]): + def validate_coordinates(coordinates: Sequence[Sequence[float]]): """ Validates the coordinates in input. Args: - coordinates (Sequence[Sequence[float]]): The coordinates of the load. + Raises: + - ValueError: if coordinates is not a sequence + - ValueError: if each element (point) in coordinates is not a sequence + - ValueError: if the number of elements (number of coordinates) is not 2 or 3. """ # check if coordinates is a sequence - if not isinstance(coordinates, collections.abc.Sequence): + if not is_non_string_sequence(coordinates): raise ValueError(f"Coordinates are not a sequence!\n:{coordinates}.") # check if coordinates is a sequence for coordinate in coordinates: - if not isinstance(coordinate, collections.abc.Sequence): + if not is_non_string_sequence(coordinate): raise ValueError(f"Coordinate in coordinates is not a sequence!\n:{coordinate}.") if len(coordinate) > 3 or len(coordinate) < 1: - raise ValueError(f"Coordinate should be either 2D or 3D but {len(coordinate)} was given") + raise ValueError(f"Coordinate should be either 2D or 3D but {len(coordinate)} was given.") @staticmethod def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], load_parameters: MovingLoad): @@ -204,22 +213,29 @@ def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], lo - coordinates (Sequence[Sequence[float]]): The start-end coordinate of the moving load. - parameters (:class:`stem.load.LoadParametersABC`): The parameters of the load. + Raises: + - ValueError: if moving load origin is not on trajectory """ - # check if coordinates is a sequence - if len(coordinates) != 2: - raise ValueError(f"For moving loads, start and ending points have to be specified, but the given " - f"coordinates are:\n :{coordinates}.") - - if not is_collinear( - point=load_parameters.origin, start_point=coordinates[0], end_point=coordinates[1] - ): - raise ValueError(f"Origin of the moving load and given points of the trajectory are not aligned!") - - if not is_point_between_points( - point=load_parameters.origin, start_point=coordinates[0], end_point=coordinates[1] - ): - raise ValueError(f"Point not in between given two points.") + _checks = [] + + # iterate over each line constituting the trajectory + for ix in range(len(coordinates)-1): + + # check origin is collinear to the edges of the line + collinear_check = is_collinear( + point=load_parameters.origin, start_point=coordinates[ix],end_point=coordinates[ix+1] + ) + # check origin is between the edges of the line (edges included) + is_between_check = is_point_between_points( + point=load_parameters.origin, start_point=coordinates[ix], end_point=coordinates[ix+1] + ) + # check if point complies + _checks.append(collinear_check and is_between_check) + + # if point doesn't comply to at least one line, it raises an error + if not any(_checks): + raise ValueError(f"Origin is not in the trajectory of the moving load.") def synchronise_geometry(self): """ diff --git a/stem/model_part.py b/stem/model_part.py index ced69e91a..abdc1059a 100644 --- a/stem/model_part.py +++ b/stem/model_part.py @@ -19,11 +19,11 @@ class ModelPart: Attributes: - __name (str): name of the model part - geometry (Optional[:class:`stem.geometry.Geometry`]): geometry of the model part - - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part - parameters (Optional[Union[:class:`stem.load.LoadParametersABC`, \ :class:`stem.boundary.BoundaryParametersABC, \ - :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the + :class:`stem.additional_processes.AdditionalProcessesParametersABC`]]): process parameters containing the \ model part parameters. + - mesh (Optional[:class:`stem.mesh.Mesh`]): mesh of the model part """ def __init__(self, name: str): """ diff --git a/stem/utils.py b/stem/utils.py index 5c75f588c..033f65b00 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -1,9 +1,31 @@ -from typing import Sequence +import collections +from typing import Sequence, List import numpy as np -def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float]): +def check_dimensions(points:Sequence[Sequence[float]]): + """ + + Check if points have the same dimensions (2D or 3D). + + Args: + - points: (Sequence[Sequence[float]]): points to be tested + + Raises: + - ValueError: when the points have different dimensions. + - ValueError: when the dimension is not either 2 or 3D. + + """ + lengths = [len(point) for point in points] + if len(np.unique(lengths)) != 1: + raise ValueError("Mismatch in dimension of given points!") + + if any([ll not in [2,3] for ll in lengths]): + raise ValueError("Dimension of the points should be 2D or 3D.") + + +def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float], a_tol:float=1e-06): """ Check if point is aligned with the other two on a line. Points must have the same dimension (2D or 3D) @@ -11,16 +33,24 @@ def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:S point (Sequence[float]): point to be tested start_point (Sequence[float]): first point on the line end_point (Sequence[float]): second point on the line + a_tol (Sequence[float]): absolute tolerance to check collinearity Returns: bool: whether the point is aligned or not + Returns: + ValueError: when there is a dimension mismatch in the point dimensions. """ + # check dimensions of points for validation + check_dimensions([point, start_point, end_point]) + vec_1 = np.asarray(point) - np.asarray(start_point) vec_2 = np.asarray(end_point) - np.asarray(start_point) + # cross product of the two vector cross_product = np.cross(vec_1, vec_2) - return np.sum(np.abs(cross_product)) < 1e-06 + # It should be smaller than tolerance for points to be aligned + return np.sum(np.abs(cross_product)) < a_tol def is_point_between_points(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float]): @@ -44,4 +74,19 @@ def is_point_between_points(point:Sequence[float], start_point:Sequence[float], scalar_projection = sum(v1 * v2 for v1, v2 in zip(vec_1, vec_2)) / sum(v ** 2 for v in vec_2) # Check if the scalar projection is between 0 and 1 (inclusive) - return 0 <= scalar_projection <= 1 \ No newline at end of file + return 0 <= scalar_projection <= 1 + + +def is_non_string_sequence(obj:object): + """ + Check if object is a sequence but not a string + + Args: + obj (object): object to be tested + Returns: + bool: whether the object is a sequence but not a string + """ + + if isinstance(obj, str): + return False + return isinstance(obj, collections.abc.Sequence) \ No newline at end of file diff --git a/tests/test_kratos_additional_processes_io.py b/tests/test_kratos_additional_processes_io.py index e54514e30..c532a112e 100644 --- a/tests/test_kratos_additional_processes_io.py +++ b/tests/test_kratos_additional_processes_io.py @@ -7,7 +7,7 @@ from tests.utils import TestUtils -class KratosAdditionalProcessesIO: +class TestKratosAdditionalProcessesIO: def test_create_additional_processes_dictionaries(self): """ diff --git a/tests/test_model.py b/tests/test_model.py index 731801a29..4323c5a8f 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -360,14 +360,13 @@ def create_default_moving_load_parameters(self): """ # define soil material return MovingLoad( - origin=[5.0, 0.0, 0.0], + origin=[3.5, -0.5, 0.0], load=[0.0, -10.0, 0.0], velocity=5.0, offset=3.0, direction=[1, 1, 1] ) - @pytest.fixture def expected_geometry_two_layers_3D_extruded(self): """ @@ -938,19 +937,22 @@ def test_add_line_load_to_3_edges(self, expected_geometry_line_load: Geometry, TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) - def test_add_moving_point_load(self, create_default_moving_load_parameters: MovingLoad): + def test_add_moving_point_load(self, expected_geometry_line_load: Geometry, + create_default_moving_load_parameters: MovingLoad): """ Test if a single soil point load is added correctly to the model. Two points are generated and a single load is created and added to the model. Args: + - expected_geometry_line_load (:class:`stem.geometry.Geometry`): expected geometry of the model - create_default_moving_load_parameters (:class:`stem.load.MovingLoad`): default moving load parameters """ ndim = 3 - point_coordinates = [(0.0, 0, 0), (10, 0, 0)] + point_coordinates = [(0, 0, 0), (3, 0, 0), (4, -1, 0), (10, -1, 0)] + # origin is in (3.5, -0.5, 0) thus in the trajectory # define soil material load_parameters = create_default_moving_load_parameters @@ -967,15 +969,54 @@ def test_add_moving_point_load(self, create_default_moving_load_parameters: Movi # check if geometry is added correctly generated_geometry = model.process_model_parts[0].geometry - expected_geometry = Geometry( - points=[Point.create([0.0, 0, 0], 1), Point.create([10.0, 0, 0], 2)], - lines=[Line.create([1, 2], 1)], - surfaces=[], - volumes=[] - ) + expected_geometry = expected_geometry_line_load TestUtils.assert_almost_equal_geometries(expected_geometry, generated_geometry) + def test_validation_coordinates(self): + """ + Test that validation raises and error if the points are not correctly specified. + """ + + ndim = 3 + model = Model(ndim=ndim) + + # test for incorrect number of coordinates + with pytest.raises(ValueError, match=f"Coordinate should be either 2D or 3D but 4 was given."): + model.validate_coordinates([(0.0, 0, 0, 4.0)]) + + # test for incorrect type (Sequence of float instead of Sequence[Sequence[float]]) + with pytest.raises(ValueError, match=f"Coordinate in coordinates is not a sequence!\n:0.0."): + model.validate_coordinates([0.0]) + + # test for incorrect type (Sequence of float instead of Sequence[Sequence[float]]) + with pytest.raises(ValueError, match=f"oordinates are not a sequence!\n:0.0."): + model.validate_coordinates(0.0) + + def test_validation_moving_load(self, create_default_moving_load_parameters:MovingLoad): + """ + Test validation of moving load when points is not collinear to the trajectory. + + Args: + - create_default_moving_load_parameters (:class:`stem.load.MovingLoad`): default moving load parameters + + """ + + ndim = 3 + + point_coordinates = [(0.0, 0, 0), (1, 0, 0), (2, 0, 0), (4, 0, 0)] + # origin is in (1.5, 0.5, 0) thus not in the trajectory + + # define soil material + load_parameters = create_default_moving_load_parameters + # create model + model = Model(ndim) + + with pytest.raises(ValueError, match="Origin is not in the trajectory of the moving load."): + model.add_load_by_coordinates( + point_coordinates, load_parameters, "moving_load_1" + ) + def test_generate_mesh_with_only_a_body_model_part_2d(self, create_default_2d_soil_material: SoilMaterial): """ Test if the mesh is generated correctly in 2D if there is only one body model part. diff --git a/tests/test_utils.py b/tests/test_utils.py index e790893b3..3e67a7cb2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,7 +5,9 @@ class TestUtilsStem: def test_collinearity_2d(self): - + """ + Check collinearity between 3 points in 2D + """ p1 = np.array([0, 0]) p2 = np.array([-2, -1]) @@ -16,6 +18,9 @@ def test_collinearity_2d(self): assert not is_collinear(point=p_test_2, start_point=p1, end_point=p2) def test_collinearity_3d(self): + """ + Check collinearity between 3 points in 3D + """ p1 = np.array([0, 0, 0]) p2 = np.array([-2, -2, 2]) @@ -27,6 +32,9 @@ def test_collinearity_3d(self): assert not is_collinear(point=p_test_2, start_point=p1, end_point=p2) def test_is_in_between_2d(self): + """ + Check if point is in between other 2 points in 2D + """ p1 = np.array([0, 0]) p2 = np.array([-2, -2]) @@ -38,6 +46,9 @@ def test_is_in_between_2d(self): assert is_point_between_points(point=p_test_2, start_point=p1, end_point=p2) def test_is_in_between_3d(self): + """ + Check if point is in between other 2 points in 3D + """ p1 = np.array([0, 0, 0]) p2 = np.array([-2, -2, 2]) From 4c6714073b5902660d43b62bd9ef32c5b8ca749d Mon Sep 17 00:00:00 2001 From: morettid Date: Thu, 27 Jul 2023 10:38:54 +0200 Subject: [PATCH 094/116] commit for merge request --- stem/model.py | 18 +++++++++--------- stem/utils.py | 12 ++++++++++-- tests/test_model.py | 20 ++++++++++++++------ 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/stem/model.py b/stem/model.py index 6d34239ea..880bf0fed 100644 --- a/stem/model.py +++ b/stem/model.py @@ -188,7 +188,7 @@ def validate_coordinates(coordinates: Sequence[Sequence[float]]): Raises: - ValueError: if coordinates is not a sequence - ValueError: if each element (point) in coordinates is not a sequence - - ValueError: if the number of elements (number of coordinates) is not 2 or 3. + - ValueError: if the number of elements (number of coordinates) is not 3. """ # check if coordinates is a sequence @@ -200,8 +200,8 @@ def validate_coordinates(coordinates: Sequence[Sequence[float]]): if not is_non_string_sequence(coordinate): raise ValueError(f"Coordinate in coordinates is not a sequence!\n:{coordinate}.") - if len(coordinate) > 3 or len(coordinate) < 1: - raise ValueError(f"Coordinate should be either 2D or 3D but {len(coordinate)} was given.") + if len(coordinate) != 3: + raise ValueError(f"Coordinates should be 3D but {len(coordinate)} coordinates were given.") @staticmethod def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], load_parameters: MovingLoad): @@ -217,8 +217,6 @@ def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], lo - ValueError: if moving load origin is not on trajectory """ - _checks = [] - # iterate over each line constituting the trajectory for ix in range(len(coordinates)-1): @@ -231,11 +229,13 @@ def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], lo point=load_parameters.origin, start_point=coordinates[ix], end_point=coordinates[ix+1] ) # check if point complies - _checks.append(collinear_check and is_between_check) + is_on_line = collinear_check and is_between_check + # exit at the first success of the test (point in the line) + if is_on_line: + return - # if point doesn't comply to at least one line, it raises an error - if not any(_checks): - raise ValueError(f"Origin is not in the trajectory of the moving load.") + # none of the lines contain the origin, then raise an error + raise ValueError(f"Origin is not in the trajectory of the moving load.") def synchronise_geometry(self): """ diff --git a/stem/utils.py b/stem/utils.py index 0350b0db0..aad83ea49 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -37,8 +37,9 @@ def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:S Returns: - bool: whether the point is aligned or not - Returns: - ValueError: when there is a dimension mismatch in the point dimensions. + + Raises: + - ValueError: when there is a dimension mismatch in the point dimensions. """ # check dimensions of points for validation @@ -64,8 +65,14 @@ def is_point_between_points(point:Sequence[float], start_point:Sequence[float], Returns: - bool: whether the point is between the other two or not + + Raises: + - ValueError: when there is a dimension mismatch in the point dimensions. """ + # check dimensions of points for validation + check_dimensions([point, start_point, end_point]) + # Calculate vectors between the points vec_1 = np.asarray(point) - np.asarray(start_point) vec_2 = np.asarray(end_point) - np.asarray(start_point) @@ -83,6 +90,7 @@ def is_non_string_sequence(obj:object): Args: obj (object): object to be tested + Returns: bool: whether the object is a sequence but not a string """ diff --git a/tests/test_model.py b/tests/test_model.py index 946359db2..3d0f41874 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -889,7 +889,10 @@ def test_add_point_loads_to_2_points(self, create_default_point_load_parameters: # check if layer is added correctly assert len(model.process_model_parts) == 1 assert model.process_model_parts[0].name == "point_load_1" - assert model.process_model_parts[0].parameters == load_parameters + TestUtils.assert_dictionary_almost_equal( + model.process_model_parts[0].parameters.__dict__, + load_parameters.__dict__ + ) # check if geometry is added correctly generated_geometry = model.process_model_parts[0].geometry @@ -929,8 +932,10 @@ def test_add_line_load_to_3_edges(self, expected_geometry_line_load: Geometry, # check if layer is added correctly assert len(model.process_model_parts) == 1 assert model.process_model_parts[0].name == "line_load_1" - assert model.process_model_parts[0].parameters == load_parameters - + TestUtils.assert_dictionary_almost_equal( + model.process_model_parts[0].parameters.__dict__, + load_parameters.__dict__ + ) # check if geometry is added correctly generated_geometry = model.process_model_parts[0].geometry expected_geometry = expected_geometry_line_load @@ -965,7 +970,10 @@ def test_add_moving_point_load(self, expected_geometry_line_load: Geometry, # check if layer is added correctly assert len(model.process_model_parts) == 1 assert model.process_model_parts[0].name == "moving_load_1" - assert model.process_model_parts[0].parameters == load_parameters + TestUtils.assert_dictionary_almost_equal( + model.process_model_parts[0].parameters.__dict__, + load_parameters.__dict__ + ) # check if geometry is added correctly generated_geometry = model.process_model_parts[0].geometry @@ -982,7 +990,7 @@ def test_validation_coordinates(self): model = Model(ndim=ndim) # test for incorrect number of coordinates - with pytest.raises(ValueError, match=f"Coordinate should be either 2D or 3D but 4 was given."): + with pytest.raises(ValueError, match=f"Coordinates should be 3D but 4 coordinates were given."): model.validate_coordinates([(0.0, 0, 0, 4.0)]) # test for incorrect type (Sequence of float instead of Sequence[Sequence[float]]) @@ -990,7 +998,7 @@ def test_validation_coordinates(self): model.validate_coordinates([0.0]) # test for incorrect type (Sequence of float instead of Sequence[Sequence[float]]) - with pytest.raises(ValueError, match=f"oordinates are not a sequence!\n:0.0."): + with pytest.raises(ValueError, match=f"Coordinates are not a sequence!\n:0.0."): model.validate_coordinates(0.0) def test_validation_moving_load(self, create_default_moving_load_parameters:MovingLoad): From b5cf60b697357d8a3f011186b42d742a7353d574 Mon Sep 17 00:00:00 2001 From: morettid Date: Thu, 27 Jul 2023 11:07:56 +0200 Subject: [PATCH 095/116] small adjustments to testing and utils --- tests/test_model.py | 40 +++------------------------------------- tests/utils.py | 2 +- 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index abcb80be9..e3dd31db7 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1342,25 +1342,7 @@ def test_add_boundary_condition_by_geometry_ids(self,create_default_3d_soil_mate for expected_geometry, model_part in zip(all_expected_geometries, model.process_model_parts): - # check if points are added correctly - for generated_point, expected_point in zip(model_part.geometry.points, expected_geometry.points): - assert generated_point.id == expected_point.id - npt.assert_allclose(generated_point.coordinates, expected_point.coordinates) - - # check if lines are added correctly - for generated_line, expected_line in zip(model_part.geometry.lines, expected_geometry.lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(model_part.geometry.surfaces, expected_geometry.surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids - - # check if volumes are added correctly - for generated_volume, expected_volume in zip(model_part.geometry.volumes, expected_geometry.volumes): - assert generated_volume.id == expected_volume.id - assert generated_volume.surface_ids == expected_volume.surface_ids + TestUtils.assert_almost_equal_geometries(expected_geometry, model_part.geometry) def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilMaterial): @@ -1426,21 +1408,7 @@ def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilM # check if geometry is added correctly generated_model_part = model_part.geometry - # check if points are added correctly - for generated_point, expected_point in zip(generated_model_part.points, expected_geometries[0].points): - assert generated_point.id == expected_point.id - npt.assert_allclose(generated_point.coordinates,expected_point.coordinates) - - # check if lines are added correctly - for generated_line, expected_line in zip(generated_model_part.lines, expected_geometries[0].lines): - assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids - - # check if surfaces are added correctly - for generated_surface, expected_surface in zip(generated_model_part.surfaces, - expected_geometries[0].surfaces): - assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + TestUtils.assert_almost_equal_geometries(expected_geometries[0], generated_model_part) def test_add_gravity_load_two_layers_same_dimension(self, create_default_2d_soil_material: SoilMaterial): """ @@ -1599,8 +1567,6 @@ def test_setup_stress_initialisation_without_project_parameters(self): match=r"Project parameters must be set before setting up the stress initialisation"): model._Model__setup_stress_initialisation() - - @pytest.mark.skip("Not implemented yet") def test_post_setup(self): - pass \ No newline at end of file + pass diff --git a/tests/utils.py b/tests/utils.py index 3273dfe72..bd36db544 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -56,7 +56,7 @@ def assert_almost_equal_geometries(expected_geometry: Geometry, actual_geometry: # check if points are added correctly for generated_point, expected_point in zip(actual_geometry.points, expected_geometry.points): assert generated_point.id == expected_point.id - assert pytest.approx(generated_point.coordinates) == expected_point.coordinates + npt.assert_allclose(generated_point.coordinates, expected_point.coordinates) # check if lines are added correctly for generated_line, expected_line in zip(actual_geometry.lines, expected_geometry.lines): From 5910fa13c913511074bc4b8a2a1333dbf4d6ae69 Mon Sep 17 00:00:00 2001 From: morettid <116064597+morettid@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:45:27 +0200 Subject: [PATCH 096/116] Update stem/utils.py Co-authored-by: aronnoordam <51492202+aronnoordam@users.noreply.github.com> --- stem/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/utils.py b/stem/utils.py index aad83ea49..fa793b28d 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -33,7 +33,7 @@ def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:S - point (Sequence[float]): point coordinates to be tested - start_point (Sequence[float]): coordinates of first point of a line - end_point (Sequence[float]): coordinates of second point of a line - - a_tol (Sequence[float]): absolute tolerance to check collinearity + - a_tol (float): absolute tolerance to check collinearity (default 1e-6) Returns: - bool: whether the point is aligned or not From d1bcb19786eacedb2b34fbf1981ae389997a2156 Mon Sep 17 00:00:00 2001 From: morettid Date: Thu, 27 Jul 2023 14:25:00 +0200 Subject: [PATCH 097/116] adjustments to validation of coordinates --- stem/model.py | 25 ++++++++++++------------- stem/utils.py | 17 ++++++++--------- tests/test_model.py | 23 ++++++++++++++++------- tests/utils.py | 6 +++--- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/stem/model.py b/stem/model.py index 8f482ccef..b00f631d9 100644 --- a/stem/model.py +++ b/stem/model.py @@ -1,9 +1,10 @@ from enum import Enum from dataclasses import dataclass -import collections from typing import List, Sequence, Dict, Any, Optional, Union, get_args import numpy as np +import numpy.typing as npty + from gmsh_utils import gmsh_IO from stem.model_part import ModelPart, BodyModelPart @@ -182,7 +183,7 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p self.process_model_parts.append(model_part) @staticmethod - def validate_coordinates(coordinates: Sequence[Sequence[float]]): + def validate_coordinates(coordinates: Union[Sequence[Sequence[float]], npty.NDArray[np.float64]]): """ Validates the coordinates in input. @@ -190,22 +191,20 @@ def validate_coordinates(coordinates: Sequence[Sequence[float]]): - coordinates (Sequence[Sequence[float]]): The coordinates of the load. Raises: - - ValueError: if coordinates is not a sequence - - ValueError: if each element (point) in coordinates is not a sequence + - ValueError: if coordinates is not convertible to a 2D array (i.e. a sequence of sequences) - ValueError: if the number of elements (number of coordinates) is not 3. """ - # check if coordinates is a sequence - if not is_non_string_sequence(coordinates): - raise ValueError(f"Coordinates are not a sequence!\n:{coordinates}.") + # if is not an array, make it array! + + if not isinstance(coordinates, np.ndarray): + coordinates = np.array(coordinates) - # check if coordinates is a sequence - for coordinate in coordinates: - if not is_non_string_sequence(coordinate): - raise ValueError(f"Coordinate in coordinates is not a sequence!\n:{coordinate}.") + if len(coordinates.shape) != 2: + raise ValueError(f"Coordinates are not a sequence of a sequence or a 2D array.") - if len(coordinate) != 3: - raise ValueError(f"Coordinates should be 3D but {len(coordinate)} coordinates were given.") + if coordinates.shape[1] != 3: + raise ValueError(f"Coordinates should be 3D but {coordinates.shape[1]} coordinates were given.") @staticmethod def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], load_parameters: MovingLoad): diff --git a/stem/utils.py b/stem/utils.py index aad83ea49..cb83da7a0 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -1,4 +1,3 @@ -import collections from typing import Sequence, List import numpy as np @@ -15,8 +14,8 @@ def check_dimensions(points:Sequence[Sequence[float]]): Raises: - ValueError: when the points have different dimensions. - ValueError: when the dimension is not either 2 or 3D. - """ + lengths = [len(point) for point in points] if len(np.unique(lengths)) != 1: raise ValueError("Mismatch in dimension of given points!") @@ -35,11 +34,11 @@ def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:S - end_point (Sequence[float]): coordinates of second point of a line - a_tol (Sequence[float]): absolute tolerance to check collinearity - Returns: - - bool: whether the point is aligned or not - Raises: - ValueError: when there is a dimension mismatch in the point dimensions. + + Returns: + - bool: whether the point is aligned or not """ # check dimensions of points for validation @@ -63,11 +62,11 @@ def is_point_between_points(point:Sequence[float], start_point:Sequence[float], - start_point (Sequence[float]): first extreme coordinates of the line - end_point (Sequence[float]): second extreme coordinates of the line - Returns: - - bool: whether the point is between the other two or not - Raises: - ValueError: when there is a dimension mismatch in the point dimensions. + + Returns: + - bool: whether the point is between the other two or not """ # check dimensions of points for validation @@ -97,4 +96,4 @@ def is_non_string_sequence(obj:object): if isinstance(obj, str): return False - return isinstance(obj, collections.abc.Sequence) \ No newline at end of file + return isinstance(obj, Sequence) \ No newline at end of file diff --git a/tests/test_model.py b/tests/test_model.py index e3dd31db7..ac2dd7103 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -992,17 +992,26 @@ def test_validation_coordinates(self): ndim = 3 model = Model(ndim=ndim) + # test inputs for numpy arrays: + # test for 2D-array, correct number of coordinates (shape 3,2) + model.validate_coordinates(np.zeros((2,3))) + + # test for incorrect number of coordinates in array (shape 3,2) + with pytest.raises(ValueError, match=f"Coordinates should be 3D but 2 coordinates were given."): + model.validate_coordinates(np.zeros((3,2))) + + # test for incorrect number of dimension in array (1-D array) + with pytest.raises(ValueError, match=f"Coordinates are not a sequence of a sequence or a 2D array."): + model.validate_coordinates(np.arange(3)) + + # test inputs for sequence of floats: # test for incorrect number of coordinates with pytest.raises(ValueError, match=f"Coordinates should be 3D but 4 coordinates were given."): - model.validate_coordinates([(0.0, 0, 0, 4.0)]) - - # test for incorrect type (Sequence of float instead of Sequence[Sequence[float]]) - with pytest.raises(ValueError, match=f"Coordinate in coordinates is not a sequence!\n:0.0."): - model.validate_coordinates([0.0]) + model.validate_coordinates([(0.0, 0.0, 0.0, 4.0)]) # test for incorrect type (Sequence of float instead of Sequence[Sequence[float]]) - with pytest.raises(ValueError, match=f"Coordinates are not a sequence!\n:0.0."): - model.validate_coordinates(0.0) + with pytest.raises(ValueError, match="Coordinates are not a sequence of a sequence or a 2D array."): + model.validate_coordinates([0.0, 0.0, 0.0]) def test_validation_moving_load(self, create_default_moving_load_parameters:MovingLoad): """ diff --git a/tests/utils.py b/tests/utils.py index bd36db544..243047fdd 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -61,14 +61,14 @@ def assert_almost_equal_geometries(expected_geometry: Geometry, actual_geometry: # check if lines are added correctly for generated_line, expected_line in zip(actual_geometry.lines, expected_geometry.lines): assert generated_line.id == expected_line.id - assert generated_line.point_ids == expected_line.point_ids + npt.assert_equal(generated_line.point_ids, expected_line.point_ids) # check if surfaces are added correctly for generated_surface, expected_surface in zip(actual_geometry.surfaces, expected_geometry.surfaces): assert generated_surface.id == expected_surface.id - assert generated_surface.line_ids == expected_surface.line_ids + npt.assert_equal(generated_surface.line_ids, expected_surface.line_ids) # check if volumes are added correctly for generated_volume, expected_volume in zip(actual_geometry.volumes, expected_geometry.volumes): assert generated_volume.id == expected_volume.id - assert generated_volume.surface_ids == expected_volume.surface_ids + npt.assert_equal(generated_volume.surface_ids, expected_volume.surface_ids) From c0eb1105da155b617436867a68b62426729f54bb Mon Sep 17 00:00:00 2001 From: morettid Date: Thu, 27 Jul 2023 14:26:35 +0200 Subject: [PATCH 098/116] adjustments to validation of coordinates --- stem/model.py | 2 +- stem/utils.py | 18 +----------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/stem/model.py b/stem/model.py index b00f631d9..f87d502e8 100644 --- a/stem/model.py +++ b/stem/model.py @@ -16,7 +16,7 @@ from stem.mesh import Mesh, MeshSettings from stem.load import * from stem.solver import Problem, StressInitialisationType -from stem.utils import is_point_between_points, is_collinear, is_non_string_sequence +from stem.utils import is_point_between_points, is_collinear class Model: diff --git a/stem/utils.py b/stem/utils.py index c2675ee41..5c77d62d0 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -20,7 +20,7 @@ def check_dimensions(points:Sequence[Sequence[float]]): if len(np.unique(lengths)) != 1: raise ValueError("Mismatch in dimension of given points!") - if any([ll not in [2,3] for ll in lengths]): + if any([ll not in [2, 3] for ll in lengths]): raise ValueError("Dimension of the points should be 2D or 3D.") @@ -81,19 +81,3 @@ def is_point_between_points(point:Sequence[float], start_point:Sequence[float], # Check if the scalar projection is between 0 and 1 (inclusive) return 0 <= scalar_projection <= 1 - - -def is_non_string_sequence(obj:object): - """ - Check if object is a sequence but not a string - - Args: - obj (object): object to be tested - - Returns: - bool: whether the object is a sequence but not a string - """ - - if isinstance(obj, str): - return False - return isinstance(obj, Sequence) \ No newline at end of file From 3c1a6a4a227d30a9336fc8053e430e13d6685826 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 10:11:24 +0200 Subject: [PATCH 099/116] added function to check if coordinates are clockwise --- stem/model.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/stem/model.py b/stem/model.py index 0a983b758..17c07597f 100644 --- a/stem/model.py +++ b/stem/model.py @@ -98,6 +98,27 @@ def add_all_layers_from_geo_file(self, geo_file_name: str, body_names: Sequence[ else: self.process_model_parts.append(model_part) + @staticmethod + def is_clockwise(coordinates: Sequence[Sequence[float]]): + """ + Checks if the coordinates are given in clockwise order. If the sum of the edges is positive, the coordinates + are given in clockwise order. + + Args: + - coordinates (Sequence[Sequence[float]]): The plane coordinates of the soil layer. + + Returns: + - bool: True if the coordinates are given in clockwise order, False otherwise. + """ + + sum_edges = 0 + for i in range(len(coordinates)-1): + sum_edges += (coordinates[i+1][0] - coordinates[i][0]) * (coordinates[i+1][1]+coordinates[i][1]) + + sum_edges += (coordinates[0][0] - coordinates[-1][0])*(coordinates[0][1]+coordinates[-1][1]) + + return sum_edges > 0 + def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], material_parameters: Union[SoilMaterial, StructuralMaterial], name: str, ): @@ -113,6 +134,10 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], """ + # sort coordinates in anti-clockwise order + if self.is_clockwise(coordinates): + coordinates = coordinates[::-1] + gmsh_input = {name: {"coordinates": coordinates, "ndim": self.ndim}} # check if extrusion length is specified in 3D if self.ndim == 3: From f9d12c02daa1f043fcf88a0120b2bb6aeee83da4 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 10:44:59 +0200 Subject: [PATCH 100/116] corrected a test --- stem/model.py | 30 ++-------- stem/utils.py | 139 ++++++++++++++++++++++++++------------------ tests/test_model.py | 20 ++++--- 3 files changed, 99 insertions(+), 90 deletions(-) diff --git a/stem/model.py b/stem/model.py index 9b5d03803..fa9011c6b 100644 --- a/stem/model.py +++ b/stem/model.py @@ -11,12 +11,11 @@ from stem.soil_material import * from stem.structural_material import * from stem.boundary import * -from stem.load import * from stem.geometry import Geometry from stem.mesh import Mesh, MeshSettings from stem.load import * from stem.solver import Problem, StressInitialisationType -from stem.utils import is_point_between_points, is_collinear +from stem.utils import Utils class Model: @@ -102,27 +101,6 @@ def add_all_layers_from_geo_file(self, geo_file_name: str, body_names: Sequence[ else: self.process_model_parts.append(model_part) - @staticmethod - def is_clockwise(coordinates: Sequence[Sequence[float]]): - """ - Checks if the coordinates are given in clockwise order. If the sum of the edges is positive, the coordinates - are given in clockwise order. - - Args: - - coordinates (Sequence[Sequence[float]]): The plane coordinates of the soil layer. - - Returns: - - bool: True if the coordinates are given in clockwise order, False otherwise. - """ - - sum_edges = 0 - for i in range(len(coordinates)-1): - sum_edges += (coordinates[i+1][0] - coordinates[i][0]) * (coordinates[i+1][1]+coordinates[i][1]) - - sum_edges += (coordinates[0][0] - coordinates[-1][0])*(coordinates[0][1]+coordinates[-1][1]) - - return sum_edges > 0 - def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], material_parameters: Union[SoilMaterial, StructuralMaterial], name: str, ): @@ -141,7 +119,7 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], """ # sort coordinates in anti-clockwise order - if self.is_clockwise(coordinates): + if Utils.is_clockwise(coordinates): coordinates = coordinates[::-1] gmsh_input = {name: {"coordinates": coordinates, "ndim": self.ndim}} @@ -249,11 +227,11 @@ def __validate_moving_load_parameters(coordinates: Sequence[Sequence[float]], lo for ix in range(len(coordinates)-1): # check origin is collinear to the edges of the line - collinear_check = is_collinear( + collinear_check = Utils.is_collinear( point=load_parameters.origin, start_point=coordinates[ix],end_point=coordinates[ix+1] ) # check origin is between the edges of the line (edges included) - is_between_check = is_point_between_points( + is_between_check = Utils.is_point_between_points( point=load_parameters.origin, start_point=coordinates[ix], end_point=coordinates[ix+1] ) # check if point complies diff --git a/stem/utils.py b/stem/utils.py index 5c77d62d0..cd9e76bd4 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -3,81 +3,110 @@ import numpy as np -def check_dimensions(points:Sequence[Sequence[float]]): +class Utils: """ + Class containing utility methods. - Check if points have the same dimensions (2D or 3D). + """ - Args: - - points: (Sequence[Sequence[float]]): points to be tested + @staticmethod + def is_clockwise(coordinates: Sequence[Sequence[float]]): + """ + Checks if the coordinates are given in clockwise order. If the sum of the edges is positive, the coordinates + are given in clockwise order. - Raises: - - ValueError: when the points have different dimensions. - - ValueError: when the dimension is not either 2 or 3D. - """ + Args: + - coordinates (Sequence[Sequence[float]]): coordinates of the points of a surface - lengths = [len(point) for point in points] - if len(np.unique(lengths)) != 1: - raise ValueError("Mismatch in dimension of given points!") + Returns: + - bool: True if the coordinates are given in clockwise order, False otherwise. + """ - if any([ll not in [2, 3] for ll in lengths]): - raise ValueError("Dimension of the points should be 2D or 3D.") + sum_edges = 0 + for i in range(len(coordinates) - 1): + sum_edges += (coordinates[i + 1][0] - coordinates[i][0]) * (coordinates[i + 1][1] + coordinates[i][1]) + sum_edges += (coordinates[0][0] - coordinates[-1][0]) * (coordinates[0][1] + coordinates[-1][1]) -def is_collinear(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float], a_tol:float=1e-06): - """ - Check if point is aligned with the other two on a line. Points must have the same dimension (2D or 3D) + return sum_edges > 0 - Args: - - point (Sequence[float]): point coordinates to be tested - - start_point (Sequence[float]): coordinates of first point of a line - - end_point (Sequence[float]): coordinates of second point of a line - - a_tol (float): absolute tolerance to check collinearity (default 1e-6) + @staticmethod + def check_dimensions(points:Sequence[Sequence[float]]): + """ - Raises: - - ValueError: when there is a dimension mismatch in the point dimensions. + Check if points have the same dimensions (2D or 3D). - Returns: - - bool: whether the point is aligned or not - """ + Args: + - points: (Sequence[Sequence[float]]): points to be tested - # check dimensions of points for validation - check_dimensions([point, start_point, end_point]) + Raises: + - ValueError: when the points have different dimensions. + - ValueError: when the dimension is not either 2 or 3D. + """ - vec_1 = np.asarray(point) - np.asarray(start_point) - vec_2 = np.asarray(end_point) - np.asarray(start_point) + lengths = [len(point) for point in points] + if len(np.unique(lengths)) != 1: + raise ValueError("Mismatch in dimension of given points!") - # cross product of the two vector - cross_product = np.cross(vec_1, vec_2) - # It should be smaller than tolerance for points to be aligned - return np.sum(np.abs(cross_product)) < a_tol + if any([ll not in [2, 3] for ll in lengths]): + raise ValueError("Dimension of the points should be 2D or 3D.") + @staticmethod + def is_collinear(point: Sequence[float], start_point: Sequence[float], end_point: Sequence[float], + a_tol: float = 1e-06): + """ + Check if point is aligned with the other two on a line. Points must have the same dimension (2D or 3D) -def is_point_between_points(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float]): - """ - Check if point is between the other two. Points must have the same dimension (2D or 3D). + Args: + - point (Sequence[float]): point coordinates to be tested + - start_point (Sequence[float]): coordinates of first point of a line + - end_point (Sequence[float]): coordinates of second point of a line + - a_tol (float): absolute tolerance to check collinearity (default 1e-6) - Args: - - point (Sequence[float]): point coordinates to be tested - - start_point (Sequence[float]): first extreme coordinates of the line - - end_point (Sequence[float]): second extreme coordinates of the line + Raises: + - ValueError: when there is a dimension mismatch in the point dimensions. - Raises: - - ValueError: when there is a dimension mismatch in the point dimensions. + Returns: + - bool: whether the point is aligned or not + """ - Returns: - - bool: whether the point is between the other two or not - """ + # check dimensions of points for validation + Utils.check_dimensions([point, start_point, end_point]) + + vec_1 = np.asarray(point) - np.asarray(start_point) + vec_2 = np.asarray(end_point) - np.asarray(start_point) + + # cross product of the two vector + cross_product = np.cross(vec_1, vec_2) + # It should be smaller than tolerance for points to be aligned + return np.sum(np.abs(cross_product)) < a_tol + + @staticmethod + def is_point_between_points(point:Sequence[float], start_point:Sequence[float], end_point:Sequence[float]): + """ + Check if point is between the other two. Points must have the same dimension (2D or 3D). + + Args: + - point (Sequence[float]): point coordinates to be tested + - start_point (Sequence[float]): first extreme coordinates of the line + - end_point (Sequence[float]): second extreme coordinates of the line + + Raises: + - ValueError: when there is a dimension mismatch in the point dimensions. + + Returns: + - bool: whether the point is between the other two or not + """ - # check dimensions of points for validation - check_dimensions([point, start_point, end_point]) + # check dimensions of points for validation + Utils.check_dimensions([point, start_point, end_point]) - # Calculate vectors between the points - vec_1 = np.asarray(point) - np.asarray(start_point) - vec_2 = np.asarray(end_point) - np.asarray(start_point) + # Calculate vectors between the points + vec_1 = np.asarray(point) - np.asarray(start_point) + vec_2 = np.asarray(end_point) - np.asarray(start_point) - # Calculate the scalar projections of vector1 onto vector2 - scalar_projection = sum(v1 * v2 for v1, v2 in zip(vec_1, vec_2)) / sum(v ** 2 for v in vec_2) + # Calculate the scalar projections of vector1 onto vector2 + scalar_projection = sum(v1 * v2 for v1, v2 in zip(vec_1, vec_2)) / sum(v ** 2 for v in vec_2) - # Check if the scalar projection is between 0 and 1 (inclusive) - return 0 <= scalar_projection <= 1 + # Check if the scalar projection is between 0 and 1 (inclusive) + return 0 <= scalar_projection <= 1 diff --git a/tests/test_model.py b/tests/test_model.py index ac2dd7103..2a4150fb0 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -169,17 +169,17 @@ def expected_geometry_two_layers_2D(self): geometry_1.volumes = [] geometry_2 = Geometry() - geometry_2.points = [Point.create([1, 1, 0], 3), + geometry_2.points = [Point.create([1, 2, 0], 5), + Point.create([0, 2, 0], 6), Point.create([0, 1, 0], 4), - Point.create([0, 2, 0], 5), - Point.create([1, 2, 0], 6)] + Point.create([1, 1, 0], 3)] - geometry_2.lines = [Line.create([3, 4], 3), - Line.create([4, 5], 5), - Line.create([5, 6], 6), - Line.create([6, 3], 7)] + geometry_2.lines = [Line.create([5, 6], 5), + Line.create([6, 4], 6), + Line.create([3, 4], 3), + Line.create([3, 5], 7)] - geometry_2.surfaces = [Surface.create([3, 5, 6, 7], 2)] + geometry_2.surfaces = [Surface.create([5, 6, -3, 7], 2)] geometry_2.volumes = [] @@ -1363,7 +1363,7 @@ def test_add_gravity_load_1d_and_2d(self, create_default_2d_soil_material: SoilM - create_default_2d_soil_material (:class:`stem.soil_material.SoilMaterial`): A default soil material. """ - + gmsh_IO.GmshIO().finalize_gmsh() # create model model = Model(2) @@ -1429,9 +1429,11 @@ def test_add_gravity_load_two_layers_same_dimension(self, create_default_2d_soil """ + # create model model = Model(2) + # add a 2d layer model.add_soil_layer_by_coordinates([(0, 0, 0), (1, 0, 0), (1, 1, 0)], create_default_2d_soil_material, "soil1") model.add_soil_layer_by_coordinates([(1, 0, 0), (0, 0, 0), (1, -1, 0)], create_default_2d_soil_material, "soil2") From b853a1b5b4c4e1c83b6f44ec6d51ab9f8ed1e689 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 11:46:58 +0200 Subject: [PATCH 101/116] changed geometry lists to dictionaries --- stem/geometry.py | 73 +- .../expected_geometry_after_sync_3D.pickle | Bin 3069 -> 3291 bytes tests/test_model.py | 673 ++++++++++-------- tests/utils.py | 24 +- 4 files changed, 421 insertions(+), 349 deletions(-) diff --git a/stem/geometry.py b/stem/geometry.py index 73fab018e..1694211dc 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -266,12 +266,12 @@ class Geometry: - surfaces (Optional[List[:class:`Surface`]]): An Iterable of Surface objects representing the surfaces in the geometry. - volumes (Optional[List[:class:`Volume`]]): An Iterable of Volume objects representing the volumes in the geometry. """ - def __init__(self, points: Optional[List[Point]] = None, lines: Optional[List[Line]] = None, - surfaces: Optional[List[Surface]] = None, volumes: Optional[List[Volume]] = None): - self.points: Optional[List[Point]] = points - self.lines: Optional[List[Line]] = lines - self.surfaces: Optional[List[Surface]] = surfaces - self.volumes: Optional[List[Volume]] = volumes + def __init__(self, points: Dict[int, Point] = {}, lines: Dict[int, Line] = {}, + surfaces: Dict[int, Surface] = {}, volumes: Dict[int,Volume] = {}): + self.points: Dict[int, Point] = points + self.lines: Dict[int, Line] = lines + self.surfaces: Dict[int, Surface] = surfaces + self.volumes: Dict[int, Volume] = volumes @staticmethod def __get_unique_entities_by_ids(entities: Sequence[GeometricalObjectABC]): @@ -374,26 +374,26 @@ def create_geometry_from_geo_data(cls, geo_data: Dict[str,Any]): """ # initialise geometry lists - points = [] - lines = [] - surfaces = [] - volumes = [] + points = {} + lines = {} + surfaces = {} + volumes = {} # add volumes to geometry for key, value in geo_data["volumes"].items(): - volumes.append(Volume.create(value,key)) + volumes[key] = Volume.create(value,key) # add surfaces to geometry for key, value in geo_data["surfaces"].items(): - surfaces.append(Surface.create(value, key)) + surfaces[key] = Surface.create(value, key) # add lines to geometry for key, value in geo_data["lines"].items(): - lines.append(Line.create(value,key)) + lines[key] = Line.create(value,key) # add points to geometry for key, value in geo_data["points"].items(): - points.append(Point.create(value,key)) + points[key] = Point.create(value,key) # create the geometry class return cls(points, lines, surfaces, volumes) @@ -412,10 +412,10 @@ def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: s """ # initialize point, line, surface and volume lists - points = [] - lines = [] - surfaces = [] - volumes = [] + points = {} + lines = {} + surfaces = {} + volumes = {} group_data = geo_data["physical_groups"][group_name] ndim_group = group_data["ndim"] @@ -423,23 +423,27 @@ def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: s if ndim_group == 0: # create points for id in group_data["geometry_ids"]: - points.append(Geometry.__set_point(geo_data, id)) + points[id] = Geometry.__set_point(geo_data, id) elif ndim_group == 1: # create lines and lower dimensional objects for id in group_data["geometry_ids"]: line, line_points = Geometry.__set_line(geo_data, id) - lines.append(line) - points.extend(line_points) + lines[id] = line + for point in line_points: + points[point.id] = point elif ndim_group == 2: # create surfaces and lower dimensional objects for id in group_data["geometry_ids"]: surface, surface_lines, surface_points = Geometry.__create_surface(geo_data, id) - surfaces.append(surface) - lines.extend(surface_lines) - points.extend(surface_points) + surfaces[id] = surface + for line in surface_lines: + lines[line.id] = line + for point in surface_points: + points[point.id] = point + elif ndim_group == 3: # Create volumes and lower dimensional objects @@ -449,15 +453,12 @@ def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: s # create surfaces and lower dimensional objects which are part of the current volume for surface_id in volume.surface_ids: surface, surface_lines, surface_points = Geometry.__create_surface(geo_data, surface_id) - surfaces.append(surface) - lines.extend(surface_lines) - points.extend(surface_points) - volumes.append(volume) - - # remove duplicates from points, lines, surfaces, volumes - unique_volumes = Geometry.__get_unique_entities_by_ids(volumes) - unique_surfaces = Geometry.__get_unique_entities_by_ids(surfaces) - unique_lines = Geometry.__get_unique_entities_by_ids(lines) - unique_points = Geometry.__get_unique_entities_by_ids(points) - - return cls(unique_points, unique_lines, unique_surfaces, unique_volumes) + surfaces[abs(surface_id)] = surface + for line in surface_lines: + lines[line.id] = line + for point in surface_points: + points[point.id] = point + + volumes[id] = volume + + return cls(points, lines, surfaces, volumes) diff --git a/tests/test_data/expected_geometry_after_sync_3D.pickle b/tests/test_data/expected_geometry_after_sync_3D.pickle index f0ab7a6157c715a9e120150e742065b7934bcd3e..40c6436fa82e3f4d8c9b400b3a0fac1e9606a0ce 100644 GIT binary patch literal 3291 zcmZ`+TZ!Mfz?Qd^T!<(dg)~ zG2T2sm`qL&k4~=6#?uB<3_01_+MR=Oy>EJuavmsfTy_hdxXqK0AP|Cp1&&^3a0m%} zA@ElT+#*R32|=U;ZsMUEkRTL-aFsx2h=m}wf*&|wG4WzElpzs9S;&mwpRKbNl}9nrUY6lwd0@P)Kb!g!`fy9(ezd-o6F_yXR& z>6w6-o?v>+_{{i%@tN_APnuXTF*AXQhzSJ~G7~Zr2_|xRLeeCHNt`AoCKF7?Ok@&b zQo*Fm#LVP^$sLoDrW8!cOky%(3c(c2q|B@fW}TUgPqZ(XzSDR(TRl&v_35>%2ja%! zON=Zptr%Iz-CII;EUqo6{yRH6tNz7bw8S2~U@!0h$A^4@arHOF{AFW&5#zTp)t^Y2 z&}7vgHYN};0muAKF{vr5-)u}MVnWVUnDq+Tv+7qH6N#9JV|;^`V{+4{^~QpanGZ~C zGEbawR9%9*5L1N#?nSH+RxVVrc+ysZMG>H5$%PL zX_-yIY%&v@oHVyt_$HMRA5DwZ&g9PjceutAPs&4w^f8zE zV*)l2Q|%uHh^fCJU#AR#B#9@G#|W+m~t?@gP5`q ze1MqpAvE+RTIYD;&3JGD9>!?ZRN+y?R6B*o5L0a#9zm?jb*g#7(-^0kFg%V}my5gH z+2vx@IN>7Bry3(%MohI)xP+K$hVVRMs&&G1h$-*GM~Erc!^eoJ>H!}jrkoC+BBneJ zpCP7d1$=^-@-=*cn5qKs`NHL%@R0|HK;BaQ7XY!YYM}ZnoM4>lr|=45s(-=^G1W8S zHN;ePfiDqL)dapmOjQr~8ZlKZ;2XqLjeu`y*H{_oAC6^d@Zcug!e~`4;5&@2_s8e| E2a=^5r~m)} literal 3069 zcmai#?Q0xW6vi{z*`2rDO_L@K2tq-SRxoIxP$>8Ug@rjS6bc2wu*PKc1e2^Lo4z1@ z@%ti3NBjc>K@bXsLO%(Gg5U@L20u$nLr@go;&bQB-N~8TwP6W6zx(XDXU;v(J-7WY z_mipOZ#h3P>g)`&&7}{s!DcqxzMhZ5p8a|!KlxdH1CrLqgY~W9P7XPHqvl2Pl#f^A zegXRZ^|hS5QL;K1Y_F|vtqil`7d_c(YRdhR?Y_^E?7?lC<_s#w58-Ps>|1xeYoYndT(In4NneO9Nm5qPRz)%ymnHq%BsQ)AnHE_=sTyUe zTINcut}t8!{Gz(l;B-E&2yPaBK@r%SUs9x9GyGenm&aB%g{ZboCdus;pyl0CUS6xc z=S1&Jk{^nm@hhehmC0c$?jgyFz?S^BA}|=IiojZYL6JFYp0;_1D%ug@3Dx+l@PHDn zUl87w9;)IT72c`yc^ki@<17mA)-RW1HjcSmGF~>-sQd)C@JGnMBBAtdM04q$X|bnH z??<9ILJR2=qj>BnVo@BWMd{FA97Q6EkY;I~j+r;9GHAO89m1~D%xf=SNvinu;^QL5x9>(R|FQ|uM~lYI5#I<>w6C!dlTbJ zKBqJofc%CcuIpS!xvsQD9LNt8fmiwdGu-IGjoJ&Ccg*;0 Date: Fri, 28 Jul 2023 12:00:23 +0200 Subject: [PATCH 102/116] corrected geometry tests --- stem/geometry.py | 16 +++++----- tests/test_geometry.py | 72 +++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/stem/geometry.py b/stem/geometry.py index 1694211dc..cf6e7f86d 100644 --- a/stem/geometry.py +++ b/stem/geometry.py @@ -261,13 +261,14 @@ class Geometry: A class to represent a collection of geometric objects in a zero-, one-, two- or three-dimensional space. Attributes: - - points (Optional[List[:class:`Point`]]): An Iterable of Point objects representing the points in the geometry. - - lines (Optional[List[:class:`Line`]]): An Iterable of Line objects representing the lines in the geometry. - - surfaces (Optional[List[:class:`Surface`]]): An Iterable of Surface objects representing the surfaces in the geometry. - - volumes (Optional[List[:class:`Volume`]]): An Iterable of Volume objects representing the volumes in the geometry. + - points (Dict[int, :class:`Point`]): An dictionary of Point objects representing the points in the geometry. + - lines (Dict[int, :class:`Line`]): A dictionary of Line objects representing the lines in the geometry. + - surfaces (Dict[int, :class:`Surface`]): A dictionary of Surface objects representing the surfaces in the \ + geometry. + - volumes (Dict[int, :class:`Volume`]): A dictionary of Volume objects representing the volumes in the geometry. """ def __init__(self, points: Dict[int, Point] = {}, lines: Dict[int, Line] = {}, - surfaces: Dict[int, Surface] = {}, volumes: Dict[int,Volume] = {}): + surfaces: Dict[int, Surface] = {}, volumes: Dict[int, Volume] = {}): self.points: Dict[int, Point] = points self.lines: Dict[int, Line] = lines self.surfaces: Dict[int, Surface] = surfaces @@ -373,7 +374,7 @@ def create_geometry_from_geo_data(cls, geo_data: Dict[str,Any]): - :class:`Geometry`: The geometry object. """ - # initialise geometry lists + # initialise geometry dictionaries points = {} lines = {} surfaces = {} @@ -411,7 +412,7 @@ def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: s - :class:`Geometry`: A Geometry object containing the geometric objects in the group. """ - # initialize point, line, surface and volume lists + # initialize point, line, surface and volume dictionaries points = {} lines = {} surfaces = {} @@ -444,7 +445,6 @@ def create_geometry_from_gmsh_group(cls, geo_data: Dict[str, Any], group_name: s for point in surface_points: points[point.id] = point - elif ndim_group == 3: # Create volumes and lower dimensional objects for id in group_data["geometry_ids"]: diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 1a509a933..13f1f0c13 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -1,5 +1,6 @@ import pytest from gmsh_utils.gmsh_IO import GmshIO +import numpy.testing as npt from stem.geometry import * @@ -88,8 +89,9 @@ def test_create_0d_geometry_from_gmsh_group(self, expected_geo_data_0D: Dict[str # Assert that the geometry is created correctly assert len(geometry.points) == len(expected_geo_data_0D["points"]) - for point in geometry.points: - assert pytest.approx(point.coordinates) == expected_geo_data_0D["points"][point.id] + for point_id, point in geometry.points.items(): + assert point_id == point.id + npt.assert_allclose(point.coordinates, expected_geo_data_0D["points"][point.id]) def test_create_1d_geometry_from_gmsh_group(self, expected_geo_data_1D: Dict[str, Any]): """ @@ -110,12 +112,14 @@ def test_create_1d_geometry_from_gmsh_group(self, expected_geo_data_1D: Dict[str # Assert that the geometry is created correctly assert len(geometry.points) == len(expected_geo_data_1D["points"]) - for point in geometry.points: - assert pytest.approx(point.coordinates) == expected_geo_data_1D["points"][point.id] + for point_id, point in geometry.points.items(): + assert point_id == point.id + npt.assert_allclose(point.coordinates, expected_geo_data_1D["points"][point.id]) assert len(geometry.lines) == len(expected_geo_data_1D["lines"]) - for line in geometry.lines: - assert line.point_ids == expected_geo_data_1D["lines"][line.id] + for line_id, line in geometry.lines.items(): + assert line.id == line.id + npt.assert_equal(line.point_ids, expected_geo_data_1D["lines"][line.id]) def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D: Dict[str, Any]): """ @@ -136,17 +140,19 @@ def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D: Dict[str # Assert that the geometry is created correctly assert len(geometry.points) == len(expected_geo_data_2D["points"]) - for point in geometry.points: - assert pytest.approx(point.coordinates) == expected_geo_data_2D["points"][point.id] + for point_id, point in geometry.points.items(): + assert point_id == point.id + npt.assert_allclose(point.coordinates, expected_geo_data_2D["points"][point.id]) assert len(geometry.lines) == len(expected_geo_data_2D["lines"]) - for line in geometry.lines: - assert line.point_ids == expected_geo_data_2D["lines"][line.id] + for line_id, line in geometry.lines.items(): + assert line.id == line.id + npt.assert_equal(line.point_ids, expected_geo_data_2D["lines"][line.id]) assert len(geometry.surfaces) == len(expected_geo_data_2D["surfaces"]) - for surface in geometry.surfaces: - assert surface.line_ids == expected_geo_data_2D["surfaces"][surface.id] - + for surface_id, surface in geometry.surfaces.items(): + assert surface.id == surface.id + npt.assert_equal(surface.line_ids, expected_geo_data_2D["surfaces"][surface.id]) def test_create_3d_geometry_from_gmsh_group(self, expected_geo_data_3D: Dict[str, Any]): """ @@ -167,20 +173,24 @@ def test_create_3d_geometry_from_gmsh_group(self, expected_geo_data_3D: Dict[str # Assert that the geometry is created correctly assert len(geometry.points) == len(expected_geo_data_3D["points"]) - for point in geometry.points: - assert pytest.approx(point.coordinates) == expected_geo_data_3D["points"][point.id] + for point_id, point in geometry.points.items(): + assert point_id == point.id + npt.assert_allclose(point.coordinates, expected_geo_data_3D["points"][point.id]) assert len(geometry.lines) == len(expected_geo_data_3D["lines"]) - for line in geometry.lines: - assert line.point_ids == expected_geo_data_3D["lines"][line.id] + for line_id, line in geometry.lines.items(): + assert line.id == line.id + npt.assert_equal(line.point_ids, expected_geo_data_3D["lines"][line.id]) assert len(geometry.surfaces) == len(expected_geo_data_3D["surfaces"]) - for surface in geometry.surfaces: - assert surface.line_ids == expected_geo_data_3D["surfaces"][surface.id] + for surface_id, surface in geometry.surfaces.items(): + assert surface.id == surface.id + npt.assert_equal(surface.line_ids, expected_geo_data_3D["surfaces"][surface.id]) assert len(geometry.volumes) == len(expected_geo_data_3D["volumes"]) - for volume in geometry.volumes: - assert volume.surface_ids == expected_geo_data_3D["volumes"][volume.id] + for volume_id, volume in geometry.volumes.items(): + assert volume.id == volume.id + npt.assert_equal(volume.surface_ids, expected_geo_data_3D["volumes"][volume.id]) def test_create_geometry_from_geo_data(self, expected_geo_data_3D: Dict[str, Any]): """ @@ -198,17 +208,21 @@ def test_create_geometry_from_geo_data(self, expected_geo_data_3D: Dict[str, Any # Assert that the geometry is created correctly assert len(geometry.points) == len(expected_geo_data_3D["points"]) - for point in geometry.points: - assert pytest.approx(point.coordinates) == expected_geo_data_3D["points"][point.id] + for point_id, point in geometry.points.items(): + assert point_id == point.id + npt.assert_allclose(point.coordinates, expected_geo_data_3D["points"][point.id]) assert len(geometry.lines) == len(expected_geo_data_3D["lines"]) - for line in geometry.lines: - assert line.point_ids == expected_geo_data_3D["lines"][line.id] + for line_id, line in geometry.lines.items(): + assert line.id == line.id + npt.assert_equal(line.point_ids, expected_geo_data_3D["lines"][line.id]) assert len(geometry.surfaces) == len(expected_geo_data_3D["surfaces"]) - for surface in geometry.surfaces: - assert surface.line_ids == expected_geo_data_3D["surfaces"][surface.id] + for surface_id, surface in geometry.surfaces.items(): + assert surface.id == surface.id + npt.assert_equal(surface.line_ids, expected_geo_data_3D["surfaces"][surface.id]) assert len(geometry.volumes) == len(expected_geo_data_3D["volumes"]) - for volume in geometry.volumes: - assert volume.surface_ids == expected_geo_data_3D["volumes"][volume.id] \ No newline at end of file + for volume_id, volume in geometry.volumes.items(): + assert volume.id == volume.id + npt.assert_equal(volume.surface_ids, expected_geo_data_3D["volumes"][volume.id]) From fb3d28c83b4bb06a32e574ce6798c6fbae422607 Mon Sep 17 00:00:00 2001 From: aronnoordam <51492202+aronnoordam@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:22:52 +0200 Subject: [PATCH 103/116] Update tests/test_geometry.py --- tests/test_geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 13f1f0c13..2772bb698 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -118,7 +118,7 @@ def test_create_1d_geometry_from_gmsh_group(self, expected_geo_data_1D: Dict[str assert len(geometry.lines) == len(expected_geo_data_1D["lines"]) for line_id, line in geometry.lines.items(): - assert line.id == line.id + assert line_id == line.id npt.assert_equal(line.point_ids, expected_geo_data_1D["lines"][line.id]) def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D: Dict[str, Any]): From b845a5a34f3f558362fae79f3504e73399ab5e85 Mon Sep 17 00:00:00 2001 From: aronnoordam <51492202+aronnoordam@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:23:22 +0200 Subject: [PATCH 104/116] Update tests/test_geometry.py --- tests/test_geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 2772bb698..a01e0635b 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -146,7 +146,7 @@ def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D: Dict[str assert len(geometry.lines) == len(expected_geo_data_2D["lines"]) for line_id, line in geometry.lines.items(): - assert line.id == line.id + assert line_id == line.id npt.assert_equal(line.point_ids, expected_geo_data_2D["lines"][line.id]) assert len(geometry.surfaces) == len(expected_geo_data_2D["surfaces"]) From 3267d8912926b9b02b69bfd2b1e335616f3c3c3b Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 13:24:40 +0200 Subject: [PATCH 105/116] corrected geometry asserts --- tests/test_geometry.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 13f1f0c13..19bd6a8d0 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -118,7 +118,7 @@ def test_create_1d_geometry_from_gmsh_group(self, expected_geo_data_1D: Dict[str assert len(geometry.lines) == len(expected_geo_data_1D["lines"]) for line_id, line in geometry.lines.items(): - assert line.id == line.id + assert line_id == line.id npt.assert_equal(line.point_ids, expected_geo_data_1D["lines"][line.id]) def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D: Dict[str, Any]): @@ -146,12 +146,12 @@ def test_create_2d_geometry_from_gmsh_group(self, expected_geo_data_2D: Dict[str assert len(geometry.lines) == len(expected_geo_data_2D["lines"]) for line_id, line in geometry.lines.items(): - assert line.id == line.id + assert line_id == line.id npt.assert_equal(line.point_ids, expected_geo_data_2D["lines"][line.id]) assert len(geometry.surfaces) == len(expected_geo_data_2D["surfaces"]) for surface_id, surface in geometry.surfaces.items(): - assert surface.id == surface.id + assert surface_id == surface.id npt.assert_equal(surface.line_ids, expected_geo_data_2D["surfaces"][surface.id]) def test_create_3d_geometry_from_gmsh_group(self, expected_geo_data_3D: Dict[str, Any]): @@ -179,17 +179,17 @@ def test_create_3d_geometry_from_gmsh_group(self, expected_geo_data_3D: Dict[str assert len(geometry.lines) == len(expected_geo_data_3D["lines"]) for line_id, line in geometry.lines.items(): - assert line.id == line.id + assert line_id == line.id npt.assert_equal(line.point_ids, expected_geo_data_3D["lines"][line.id]) assert len(geometry.surfaces) == len(expected_geo_data_3D["surfaces"]) for surface_id, surface in geometry.surfaces.items(): - assert surface.id == surface.id + assert surface_id == surface.id npt.assert_equal(surface.line_ids, expected_geo_data_3D["surfaces"][surface.id]) assert len(geometry.volumes) == len(expected_geo_data_3D["volumes"]) for volume_id, volume in geometry.volumes.items(): - assert volume.id == volume.id + assert volume_id == volume.id npt.assert_equal(volume.surface_ids, expected_geo_data_3D["volumes"][volume.id]) def test_create_geometry_from_geo_data(self, expected_geo_data_3D: Dict[str, Any]): @@ -214,15 +214,15 @@ def test_create_geometry_from_geo_data(self, expected_geo_data_3D: Dict[str, Any assert len(geometry.lines) == len(expected_geo_data_3D["lines"]) for line_id, line in geometry.lines.items(): - assert line.id == line.id + assert line_id == line.id npt.assert_equal(line.point_ids, expected_geo_data_3D["lines"][line.id]) assert len(geometry.surfaces) == len(expected_geo_data_3D["surfaces"]) for surface_id, surface in geometry.surfaces.items(): - assert surface.id == surface.id + assert surface_id == surface.id npt.assert_equal(surface.line_ids, expected_geo_data_3D["surfaces"][surface.id]) assert len(geometry.volumes) == len(expected_geo_data_3D["volumes"]) for volume_id, volume in geometry.volumes.items(): - assert volume.id == volume.id + assert volume_id == volume.id npt.assert_equal(volume.surface_ids, expected_geo_data_3D["volumes"][volume.id]) From 0d2b18dafdfa64e2426022b46cbbb2819c1dd679 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 14:16:17 +0200 Subject: [PATCH 106/116] corrected tests in model --- .../expected_geometry_after_sync_3D.pickle | Bin 3291 -> 3306 bytes tests/test_model.py | 98 ++++++++---------- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/tests/test_data/expected_geometry_after_sync_3D.pickle b/tests/test_data/expected_geometry_after_sync_3D.pickle index 40c6436fa82e3f4d8c9b400b3a0fac1e9606a0ce..5a8c99e739709bdbda6c100e5243ba24517ba31e 100644 GIT binary patch delta 620 zcmZ9JJxjw-6o#p7l6#vEnW7oQkV@KT*P{H>5k;z&|yl z-5A0YO=PCTOk^!&mc%S%hu0liPE?OkvZZ0zIKWe6LyHh+$Q;S%pzj3P(4fTyvY}Op Vb2!i+y*pH>Gz1)#xQCjt{R`wt-k$&f delta 557 zcmZ9Iy-EW?6om=N&hG4=?w?DP)kY){0tR2eMuffO5dxAQm`X^og@s^Vz#w-e4z-%WC9IzeyVqRVa106NoHNhqfo@DTr28$+mqHaY7SeYc0B;itG z1H+*jnieReNJKq**UShaO`NaZ)C5hmvj1~%ac&64>WS5Sm%KiCpNIt#3y2M{Z@dLBG~v>oT(rb1 HUYebMX?3~c diff --git a/tests/test_model.py b/tests/test_model.py index 17d3590c2..dde45d386 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -172,26 +172,18 @@ def expected_geometry_two_layers_2D(self): # geometry_2 geometry_2 = Geometry() - geometry_2.points = [Point.create([1, 2, 0], 5), - Point.create([0, 2, 0], 6), - Point.create([0, 1, 0], 4), - Point.create([1, 1, 0], 3)] - geometry_2.points = {3: Point.create([1, 1, 0], 3), + + geometry_2.points = {5: Point.create([1, 2, 0], 5), + 6: Point.create([0, 2, 0], 6), 4: Point.create([0, 1, 0], 4), - 5: Point.create([0, 2, 0], 5), - 6: Point.create([1, 2, 0], 6)} + 3: Point.create([1, 1, 0], 3)} - geometry_2.lines = [Line.create([5, 6], 5), - Line.create([6, 4], 6), - Line.create([3, 4], 3), - Line.create([3, 5], 7)] - geometry_2.lines = {3: Line.create([3, 4], 3), - 5: Line.create([4, 5], 5), - 6: Line.create([5, 6], 6), - 7: Line.create([6, 3], 7)} + geometry_2.lines = {5: Line.create([5, 6],5), + 6: Line.create([6, 4], 6), + 3: Line.create([3, 4], 3), + 7: Line.create([3, 5], 7)} - geometry_2.surfaces = [Surface.create([5, 6, -3, 7], 2)] - geometry_2.surfaces = {2: Surface.create([3, 5, 6, 7], 2)} + geometry_2.surfaces = {2: Surface.create([5, 6, -3, 7], 2)} geometry_2.volumes = {} @@ -232,21 +224,21 @@ def expected_geometry_two_layers_2D_after_sync(self): geometry_2 = Geometry() geometry_2.points = { - 3: Point.create([1, 1, 0], 3), + 6: Point.create([1.0, 2.0, 0.0], 6), + 7: Point.create([0.5, 2.0, 0.0], 7), 4: Point.create([0.5, 1, 0], 4), - 6: Point.create([0.5, 2, 0], 6), - 7: Point.create([1, 2, 0], 7) + 3: Point.create([1, 1, 0], 3) } geometry_2.lines = { + 6: Line.create([6, 7], 6), + 7: Line.create([7, 4], 7), 3: Line.create([3, 4], 3), - 6: Line.create([4, 6], 6), - 7: Line.create([6, 7], 7), - 8: Line.create([7, 3], 8) + 8: Line.create([3, 6], 8) } geometry_2.surfaces = { - 2: Surface.create([3, 6, 7, 8], 2) + 2: Surface.create([6, 7, -3, 8], 2) } geometry_2.volumes = {} @@ -259,8 +251,8 @@ def expected_geometry_two_layers_2D_after_sync(self): 3: Point.create([1, 1, 0], 3), 4: Point.create([0.5, 1, 0], 4), 5: Point.create([0, 1, 0], 5), - 6: Point.create([0.5, 2, 0], 6), - 7: Point.create([1, 2, 0], 7) + 6: Point.create([1, 2, 0], 6), + 7: Point.create([0.5, 2, 0], 7) } full_geometry.lines = { @@ -269,14 +261,14 @@ def expected_geometry_two_layers_2D_after_sync(self): 3: Line.create([3, 4], 3), 4: Line.create([4, 5], 4), 5: Line.create([5, 1], 5), - 6: Line.create([4, 6], 6), - 7: Line.create([6, 7], 7), - 8: Line.create([7, 3], 8) + 6: Line.create([6, 7], 6), + 7: Line.create([7, 4], 7), + 8: Line.create([3, 6], 8) } full_geometry.surfaces = { 1: Surface.create([1, 2, 3, 4, 5], 1), - 2: Surface.create([3, 6, 7, 8], 2) + 2: Surface.create([6, 7, -3, 8], 2) } full_geometry.volumes = {} @@ -454,42 +446,42 @@ def expected_geometry_two_layers_3D_extruded(self): geometry_2 = Geometry() geometry_2.points = { - 5: Point.create([1., 1., 0.], 5), - 6: Point.create([1., 1., 1.], 6), - 8: Point.create([0.0, 1., 1.], 8), - 7: Point.create([0, 1., 0.], 7), - 10: Point.create([0., 2., 1], 10), - 9: Point.create([0., 2., 0], 9), - 12: Point.create([1, 2., 1], 12), - 11: Point.create([1, 2., 0], 11) + 9: Point.create([1.0, 2.0, 0.0], 9), + 10: Point.create([1., 2., 1.], 10), + 12: Point.create([0.0, 2., 1.], 12), + 11: Point.create([0, 2., 0.], 11), + 8: Point.create([0., 1., 1], 8), + 7: Point.create([0., 1., 0], 7), + 5: Point.create([1, 1., 0], 5), + 6: Point.create([1, 1., 1], 6) } geometry_2.lines = { + 13: Line.create([9, 10], 13), + 16: Line.create([10, 12], 16), + 14: Line.create([11, 12], 14), + 15: Line.create([9, 11], 15), + 18: Line.create([12, 8], 18), + 8: Line.create([7, 8], 8), + 17: Line.create([11, 7], 17), 5: Line.create([5, 6], 5), 10: Line.create([6, 8], 10), - 8: Line.create([7, 8], 8), 9: Line.create([5, 7], 9), - 15: Line.create([8, 10], 15), - 13: Line.create([9, 10], 13), - 14: Line.create([7, 9], 14), - 18: Line.create([10, 12], 18), - 16: Line.create([11, 12], 16), - 17: Line.create([9, 11], 17), - 20: Line.create([12, 6], 20), - 19: Line.create([11, 5], 19) + 20: Line.create([6, 10], 20), + 19: Line.create([5, 9], 19) } geometry_2.surfaces = { + 7: Surface.create([13, 16, -14, -15], 7), + 8: Surface.create([14, 18, -8, -17], 8), 3: Surface.create([5, 10, -8, -9], 3), - 7: Surface.create([8, 15, -13, -14], 7), - 8: Surface.create([13, 18, -16, -17], 8), - 9: Surface.create([16, 20, -5, -19], 9), - 10: Surface.create([9, 14, 17, 19], 10), - 11: Surface.create([10, 15, 18, 20], 11) + 9: Surface.create([5, 20, -13, -19], 9), + 10: Surface.create([15, 17, -9, 19], 10), + 11: Surface.create([16, 18, -10, 20], 11) } geometry_2.volumes = { - 2: Volume.create([3, 7, 8, 9, 10, -11], 2) + 2: Volume.create([-7, -8, 3, -9, -10, 11], 2) } return geometry_1, geometry_2 From c65a5da7e292b61bf205e1a2443d89afb21b443a Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 14:20:33 +0200 Subject: [PATCH 107/116] corrected utils tests --- tests/test_utils.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 3e67a7cb2..7154f6362 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,7 @@ -from stem.utils import * +import numpy as np + +from stem.utils import Utils class TestUtilsStem: @@ -14,8 +16,8 @@ def test_collinearity_2d(self): p_test_1 = np.array([2, 1]) p_test_2 = np.array([-5, 1]) - assert is_collinear(point=p_test_1, start_point=p1, end_point=p2) - assert not is_collinear(point=p_test_2, start_point=p1, end_point=p2) + assert Utils.is_collinear(point=p_test_1, start_point=p1, end_point=p2) + assert not Utils.is_collinear(point=p_test_2, start_point=p1, end_point=p2) def test_collinearity_3d(self): """ @@ -28,8 +30,8 @@ def test_collinearity_3d(self): p_test_1 = np.array([2, 2, -2]) p_test_2 = np.array([2, -2, 2]) - assert is_collinear(point=p_test_1, start_point=p1, end_point=p2) - assert not is_collinear(point=p_test_2, start_point=p1, end_point=p2) + assert Utils.is_collinear(point=p_test_1, start_point=p1, end_point=p2) + assert not Utils.is_collinear(point=p_test_2, start_point=p1, end_point=p2) def test_is_in_between_2d(self): """ @@ -42,8 +44,8 @@ def test_is_in_between_2d(self): p_test_1 = np.array([2, 2]) p_test_2 = np.array([-1, -1]) - assert not is_point_between_points(point=p_test_1, start_point=p1, end_point=p2) - assert is_point_between_points(point=p_test_2, start_point=p1, end_point=p2) + assert not Utils.is_point_between_points(point=p_test_1, start_point=p1, end_point=p2) + assert Utils.is_point_between_points(point=p_test_2, start_point=p1, end_point=p2) def test_is_in_between_3d(self): """ @@ -56,5 +58,5 @@ def test_is_in_between_3d(self): p_test_1 = np.array([2, 2, -2]) p_test_2 = np.array([-1, -1, 1]) - assert not is_point_between_points(point=p_test_1, start_point=p1, end_point=p2) - assert is_point_between_points(point=p_test_2, start_point=p1, end_point=p2) \ No newline at end of file + assert not Utils.is_point_between_points(point=p_test_1, start_point=p1, end_point=p2) + assert Utils.is_point_between_points(point=p_test_2, start_point=p1, end_point=p2) \ No newline at end of file From bbd104fd3ff4fe942fba608463168ac95e77d2f5 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 15:09:17 +0200 Subject: [PATCH 108/116] solved mypy issue --- stem/model.py | 6 ++++-- stem/utils.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stem/model.py b/stem/model.py index fa9011c6b..22611699e 100644 --- a/stem/model.py +++ b/stem/model.py @@ -118,7 +118,7 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], - ValueError: if extrusion_length is not specified. """ - # sort coordinates in anti-clockwise order + # sort coordinates in anti-clockwise order, such that elements in mesh are also in anti-clockwise order if Utils.is_clockwise(coordinates): coordinates = coordinates[::-1] @@ -157,11 +157,14 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p or SurfaceLoad. """ + # todo add validation that load is applied on a body model part + # validation of inputs self.validate_coordinates(coordinates) if isinstance(load_parameters, MovingLoad): self.__validate_moving_load_parameters(coordinates, load_parameters) + # create input for gmsh if isinstance(load_parameters, PointLoad): gmsh_input = {name: {"coordinates": coordinates, "ndim": 0}} elif isinstance(load_parameters, LineLoad) or isinstance(load_parameters, MovingLoad): @@ -169,7 +172,6 @@ def add_load_by_coordinates(self, coordinates: Sequence[Sequence[float]], load_p elif isinstance(load_parameters, SurfaceLoad): gmsh_input = {name: {"coordinates": coordinates, "ndim": 2}} else: - # TODO: deal with Gravity loads raise ValueError(f'Invalid load_parameters ({load_parameters.__class__.__name__}) object' f' provided for the load {name}. Expected one of PointLoad, MovingLoad,' f' LineLoad or SurfaceLoad.') diff --git a/stem/utils.py b/stem/utils.py index cd9e76bd4..a797be143 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -22,13 +22,13 @@ def is_clockwise(coordinates: Sequence[Sequence[float]]): - bool: True if the coordinates are given in clockwise order, False otherwise. """ - sum_edges = 0 + sum_edges = 0.0 for i in range(len(coordinates) - 1): sum_edges += (coordinates[i + 1][0] - coordinates[i][0]) * (coordinates[i + 1][1] + coordinates[i][1]) sum_edges += (coordinates[0][0] - coordinates[-1][0]) * (coordinates[0][1] + coordinates[-1][1]) - return sum_edges > 0 + return sum_edges > 0.0 @staticmethod def check_dimensions(points:Sequence[Sequence[float]]): From 1f2c6a90dca6e4911eeb973a783f1a45cda66c9d Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 16:11:13 +0200 Subject: [PATCH 109/116] added tests which check if coordinates are clockwise --- stem/model.py | 2 +- stem/utils.py | 4 ++-- tests/test_utils.py | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/stem/model.py b/stem/model.py index 22611699e..88fc86705 100644 --- a/stem/model.py +++ b/stem/model.py @@ -119,7 +119,7 @@ def add_soil_layer_by_coordinates(self, coordinates: Sequence[Sequence[float]], """ # sort coordinates in anti-clockwise order, such that elements in mesh are also in anti-clockwise order - if Utils.is_clockwise(coordinates): + if Utils.are_2d_coordinates_clockwise(coordinates): coordinates = coordinates[::-1] gmsh_input = {name: {"coordinates": coordinates, "ndim": self.ndim}} diff --git a/stem/utils.py b/stem/utils.py index a797be143..50e9b06ef 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -10,9 +10,9 @@ class Utils: """ @staticmethod - def is_clockwise(coordinates: Sequence[Sequence[float]]): + def are_2d_coordinates_clockwise(coordinates: Sequence[Sequence[float]]): """ - Checks if the coordinates are given in clockwise order. If the sum of the edges is positive, the coordinates + Checks if the 2D coordinates are given in clockwise order. If the sum of the edges is positive, the coordinates are given in clockwise order. Args: diff --git a/tests/test_utils.py b/tests/test_utils.py index 7154f6362..2f48dfd5b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,6 +6,27 @@ class TestUtilsStem: + def test_is_clockwise(self): + """ + Test the check which checks if coordinates are given in clockwise order + """ + + coordinates = [[0, 0], [2, 0], [2, 2], [0, 2]] + + assert not Utils.are_2d_coordinates_clockwise(coordinates=coordinates) + assert Utils.are_2d_coordinates_clockwise(coordinates=coordinates[::-1]) + + def test_is_clockwise_non_convex(self): + """ + Test the check which checks if coordinates are given in clockwise order for a non-convex polygon + + """ + + coordinates = [[0, 0], [2, 0], [2, 2], [1, -1], [0, 2]] + + assert not Utils.are_2d_coordinates_clockwise(coordinates=coordinates) + assert Utils.are_2d_coordinates_clockwise(coordinates=coordinates[::-1]) + def test_collinearity_2d(self): """ Check collinearity between 3 points in 2D From 434e8bed4d7bff1da15f5b612000cd6352173039 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 17:14:56 +0200 Subject: [PATCH 110/116] added comments --- stem/utils.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/stem/utils.py b/stem/utils.py index 50e9b06ef..d080b8f83 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -12,7 +12,7 @@ class Utils: @staticmethod def are_2d_coordinates_clockwise(coordinates: Sequence[Sequence[float]]): """ - Checks if the 2D coordinates are given in clockwise order. If the sum of the edges is positive, the coordinates + Checks if the 2D coordinates are given in clockwise order. If the signed area is positive, the coordinates are given in clockwise order. Args: @@ -22,13 +22,15 @@ def are_2d_coordinates_clockwise(coordinates: Sequence[Sequence[float]]): - bool: True if the coordinates are given in clockwise order, False otherwise. """ - sum_edges = 0.0 + # calculate signed area of polygon + signed_area = 0.0 for i in range(len(coordinates) - 1): - sum_edges += (coordinates[i + 1][0] - coordinates[i][0]) * (coordinates[i + 1][1] + coordinates[i][1]) + signed_area += (coordinates[i + 1][0] - coordinates[i][0]) * (coordinates[i + 1][1] + coordinates[i][1]) - sum_edges += (coordinates[0][0] - coordinates[-1][0]) * (coordinates[0][1] + coordinates[-1][1]) + signed_area += (coordinates[0][0] - coordinates[-1][0]) * (coordinates[0][1] + coordinates[-1][1]) - return sum_edges > 0.0 + # if signed area is positive, the coordinates are given in clockwise order + return signed_area > 0.0 @staticmethod def check_dimensions(points:Sequence[Sequence[float]]): From 32057976a53bbd00de7291c863a5523cedb4af90 Mon Sep 17 00:00:00 2001 From: noordam Date: Fri, 28 Jul 2023 17:19:57 +0200 Subject: [PATCH 111/116] removed obsolete import --- stem/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/utils.py b/stem/utils.py index d080b8f83..e24df4335 100644 --- a/stem/utils.py +++ b/stem/utils.py @@ -1,4 +1,4 @@ -from typing import Sequence, List +from typing import Sequence import numpy as np From 40a982e0ba90b5569b46d3d44b03051d24c6c333 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Fri, 11 Aug 2023 11:52:01 +0200 Subject: [PATCH 112/116] Changed according reviewers commends --- stem/soil_material.py | 2 ++ stem/water_boundaries.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/stem/soil_material.py b/stem/soil_material.py index 9a4c999c0..421369142 100644 --- a/stem/soil_material.py +++ b/stem/soil_material.py @@ -150,6 +150,7 @@ class SmallStrainUmatLaw(SoilConstitutiveLawABC): - STATE_VARIABLES (list): The state variables of the umat. """ UMAT_NAME: str + UMAT_NUMBER: int IS_FORTRAN_UMAT: bool UMAT_PARAMETERS: List[Any] STATE_VARIABLES: List[Any] @@ -171,6 +172,7 @@ class SmallStrainUdsmLaw(SoilConstitutiveLawABC): """ UDSM_NAME: str UDSM_NUMBER: int + UDSM_NUMBER: int IS_FORTRAN_UDSM: bool UDSM_PARAMETERS: List[Any] diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index 68b77a98f..d6aecfa01 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -107,7 +107,7 @@ class WaterBoundary: Class containing water boundary information acting on a body part Attributes: - - water_boundary (WaterBoundaryParameters): Water boundary parameters + - water_boundary (:class:`WaterBoundaryParameters`): Water boundary parameters - type (str): Type of water boundary """ @@ -117,7 +117,7 @@ def __init__(self, water_boundary_parameters: Union[InterpolateLineBoundary, Phr Constructor of the class Attributes: - - water_boundary (WaterBoundaryParameters): Water boundary parameters + - water_boundary (:class:`WaterBoundaryParameters`): Water boundary parameters """ From 82c113969d59382206cfeda7ae5bd4fea5aaead1 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Fri, 11 Aug 2023 11:58:38 +0200 Subject: [PATCH 113/116] Commit reverted --- stem/soil_material.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/stem/soil_material.py b/stem/soil_material.py index 421369142..9a4c999c0 100644 --- a/stem/soil_material.py +++ b/stem/soil_material.py @@ -150,7 +150,6 @@ class SmallStrainUmatLaw(SoilConstitutiveLawABC): - STATE_VARIABLES (list): The state variables of the umat. """ UMAT_NAME: str - UMAT_NUMBER: int IS_FORTRAN_UMAT: bool UMAT_PARAMETERS: List[Any] STATE_VARIABLES: List[Any] @@ -172,7 +171,6 @@ class SmallStrainUdsmLaw(SoilConstitutiveLawABC): """ UDSM_NAME: str UDSM_NUMBER: int - UDSM_NUMBER: int IS_FORTRAN_UDSM: bool UDSM_PARAMETERS: List[Any] From b9dbb5f7496ae8e61a92bc09ff0aa3bc0241f2df Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Fri, 11 Aug 2023 12:00:37 +0200 Subject: [PATCH 114/116] Deleted sneaky pass typo --- stem/water_boundaries.py | 1 - 1 file changed, 1 deletion(-) diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index d6aecfa01..4ab79afde 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -71,7 +71,6 @@ class InterpolateLineBoundary(WaterBoundaryParameters): Class containing the boundary parameters for a interpolate line boundary condition. """ - pass @property def type(self): From 0a1c388ee6fcebd151cd83c8ed5803b22e7a0576 Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Fri, 11 Aug 2023 12:01:15 +0200 Subject: [PATCH 115/116] Attributes to Args --- stem/water_boundaries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index 4ab79afde..486a69a64 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -105,7 +105,7 @@ class WaterBoundary: """ Class containing water boundary information acting on a body part - Attributes: + Args: - water_boundary (:class:`WaterBoundaryParameters`): Water boundary parameters - type (str): Type of water boundary @@ -115,7 +115,7 @@ def __init__(self, water_boundary_parameters: Union[InterpolateLineBoundary, Phr """ Constructor of the class - Attributes: + Args: - water_boundary (:class:`WaterBoundaryParameters`): Water boundary parameters """ From 21222ffa818632ccb96b8bed8f3124a626f05adf Mon Sep 17 00:00:00 2001 From: ElenSmi Date: Fri, 11 Aug 2023 12:01:37 +0200 Subject: [PATCH 116/116] Attributes to Args --- stem/water_boundaries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stem/water_boundaries.py b/stem/water_boundaries.py index 486a69a64..36f035edb 100644 --- a/stem/water_boundaries.py +++ b/stem/water_boundaries.py @@ -115,7 +115,7 @@ def __init__(self, water_boundary_parameters: Union[InterpolateLineBoundary, Phr """ Constructor of the class - Args: + Args: - water_boundary (:class:`WaterBoundaryParameters`): Water boundary parameters """