Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GeoMechanicsApplication] Re-enable settlement test using the C++ route #12877

Merged
merged 30 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1b5e75f
Run `settlement_workflow.py` in subdirectory `python`
avdg81 Nov 21, 2024
4e57780
Renamed a Python test script and a test class
avdg81 Nov 21, 2024
0335747
Added an incomplete Python test script for the C++ route
avdg81 Nov 22, 2024
cf10af3
Introduced an intermediate test fixture
avdg81 Nov 22, 2024
790e9a0
Added two new attributes to the base test fixture
avdg81 Nov 22, 2024
a4fa4ac
Renamed a Python test script
avdg81 Nov 23, 2024
344e420
Added a test fixture for the C++ route
avdg81 Nov 23, 2024
cd2c352
Fixed an incorrect Python module name
avdg81 Nov 25, 2024
c844a52
Also corrected an import statement
avdg81 Nov 25, 2024
022daac
Added more checks for the C++ route
avdg81 Nov 25, 2024
32386ad
Add a lambda for the "should cancel" callback
avdg81 Nov 25, 2024
c4e4f36
Attempt to fix test failures
avdg81 Nov 26, 2024
064d8fe
Use proper callback functions in the C++ route test
avdg81 Nov 26, 2024
52f6e09
Defined a map that contains the time steps to check
avdg81 Nov 26, 2024
ba5bc8f
Added time, output filename and expected displacements
avdg81 Nov 26, 2024
08b3fe6
Extracted a method for checking the displacements
avdg81 Nov 26, 2024
044f63d
Extracted a method for defining the expected displacements
avdg81 Nov 26, 2024
de7ea3f
Defined methods for checking the nodal stresses
avdg81 Nov 26, 2024
99090e9
Also the test case for the C++ route now checks the nodal stresses
avdg81 Nov 26, 2024
8b0cf23
Updated the descriptions of the test fixtures
avdg81 Nov 26, 2024
a76b3e0
Removed the C++ unit test for the settlement workflow
avdg81 Nov 26, 2024
4f18573
Removed the now unused original output files
avdg81 Nov 26, 2024
e0bb35d
Merge remote-tracking branch 'origin/master' into geo/12865-re-enable…
avdg81 Nov 27, 2024
249c737
Use `def_static` rather than `def` to bind static member functions
avdg81 Nov 27, 2024
d0b280e
Removed an unused `#include`
avdg81 Nov 27, 2024
029e63b
Removed an unused test script
avdg81 Nov 27, 2024
cedcf3f
Added a missing `tearDown` call of the base class
avdg81 Nov 27, 2024
b8383d2
Moved a function definition to another line in the file
avdg81 Nov 27, 2024
3edbf35
Don't use a loop counter when we don't need one
avdg81 Nov 27, 2024
b981d8f
Processed the review suggestions
avdg81 Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include "custom_python/add_custom_utilities_to_python.h"

#include "custom_utilities/node_utilities.h"
#include "custom_workflows/custom_workflow_factory.h"
#include "custom_workflows/dgeosettlement.h"

namespace Kratos::Python
{
Expand All @@ -26,6 +28,12 @@ void AddCustomUtilitiesToPython(const pybind11::module& rModule)
pybind11::class_<NodeUtilities>(rModule, "NodeUtilities")
.def("AssignUpdatedVectorVariableToNonFixedComponentsOfNodes",
&NodeUtilities::AssignUpdatedVectorVariableToNonFixedComponentsOfNodes);

pybind11::class_<CustomWorkflowFactory>(rModule, "CustomWorkflowFactory")
.def_static("CreateKratosGeoSettlement", &CustomWorkflowFactory::CreateKratosGeoSettlement,
pybind11::return_value_policy::take_ownership);

pybind11::class_<KratosGeoSettlement>(rModule, "KratosGeoSettlement").def("RunStage", &KratosGeoSettlement::RunStage);
}

} // Namespace Kratos::Python.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define KRATOS_ADD_UTILITIES_TO_PYTHON_H_INCLUDED

// System includes
#include <pybind11/functional.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For those interested, this include is needed such that python (lambda) functions can be converted to C++ ones (since we pass functions to the exported RunStage function)

#include <pybind11/pybind11.h>

// External includes
Expand Down

This file was deleted.

210 changes: 171 additions & 39 deletions applications/GeoMechanicsApplication/tests/settlement_workflow.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,193 @@
import os
import shutil
import importlib

import KratosMultiphysics
import KratosMultiphysics.KratosUnittest as KratosUnittest

# Import the Kratos applications that we need. We need to reload these later.
import KratosMultiphysics.LinearSolversApplication
import KratosMultiphysics.StructuralMechanicsApplication
import KratosMultiphysics.GeoMechanicsApplication as GeoMechanicsApplication

import test_helper


class KratosGeoMechanicsSettlementWorkflow(KratosUnittest.TestCase):
def setUp(self):
super().setUp()

self.test_root = test_helper.get_file_path("test_settlement_workflow")
self.test_path = os.path.join(self.test_root, self.get_test_dir_name())

shutil.rmtree(self.test_path, ignore_errors=True)

os.makedirs(self.test_path)

self.number_of_stages = 4
self.project_parameters_filenames = [f"ProjectParameters_stage{i+1}.json" for i in range(self.number_of_stages)]
input_filenames = self.project_parameters_filenames + ["MaterialParameters.json", "test_model.mdpa"]

for filename in input_filenames:
shutil.copy(os.path.join(self.test_root, filename), os.path.join(self.test_path, filename))

# The expected values have been taken from a validated test run
self.define_expected_displacements()
self.define_expected_stresses()


def get_test_dir_name(self):
raise RuntimeError("This base class does not provide a generic test directory name")


def define_expected_displacements(self):
# The selected nodes are at the top corner, at the bottom of the excavation, in the middle of the model,
# and at the bottom.
self.expected_displacements = [{"output_filename": "test_model_stage1.post.res",
"time": 1.0,
"expected_values": [
{"node": 1, "DISPLACEMENT": [0.0, -0.123882, 0.0]},
{"node": 102, "DISPLACEMENT": [8.49409e-06, -0.119848, 0.0]},
{"node": 1085, "DISPLACEMENT": [-7.25396e-09, -0.101875, 0.0]},
{"node": 1442, "DISPLACEMENT": [0.0, 0.0, 0.0]}
]},
{"output_filename": "test_model_stage2.post.res",
"time": 2.0,
"expected_values": [
{"node": 1, "DISPLACEMENT": [0.0, -0.000162389, 0.0]},
{"node": 102, "DISPLACEMENT": [8.50645e-05, -0.000467135, 0.0]},
{"node": 1085, "DISPLACEMENT": [-0.00012122, -0.00186572, 0.0]},
{"node": 1442, "DISPLACEMENT": [0.0, 0.0, 0.0]}
]},
{"output_filename": "test_model_stage3.post.res",
"time": 3.0,
"expected_values": [
{"node": 1, "DISPLACEMENT": [0.0, -0.000526393, 0.0]},
{"node": 102, "DISPLACEMENT": [-4.47571e-05, -0.000954867, 0.0]},
{"node": 1085, "DISPLACEMENT": [-0.000330983, -0.00372074, 0.0]},
{"node": 1442, "DISPLACEMENT": [0.0, 0.0, 0.0]}
]},
{"output_filename": "test_model_stage4.post.res",
"time": 3.2,
"expected_values": [
{"node": 1, "DISPLACEMENT": [0.0, 0.0200705, 0.0]},
{"node": 102, "DISPLACEMENT": [-0.000423281, 0.023303, 0.0]},
{"node": 1085, "DISPLACEMENT": [0.000927345, 0.00102933, 0.0]},
{"node": 1442, "DISPLACEMENT": [0.0, 0.0, 0.0]}
]}]


def define_expected_stresses(self):
self.expected_stresses = [{"output_filename": "test_model_stage4.post.res",
"time": 3.2,
"expected_values": [
{"node": 1,
"TOTAL_STRESS_TENSOR": [-7.69856, -0.127088, -1.56513, 0.127088, 0.0, 0.0],
"CAUCHY_STRESS_TENSOR": [-7.69856, -0.127088, -1.56513, 0.127088, 0.0, 0.0]},
{"node": 102,
"TOTAL_STRESS_TENSOR": [-53.8354, -21.2794, -38.6748, 0.755572, 0.0, 0.0],
"CAUCHY_STRESS_TENSOR": [-34.2154, -1.65944, -19.0548, 0.755572, 0.0, 0.0]},
{"node": 1085,
"TOTAL_STRESS_TENSOR": [-99.4165, -142.12, -102.597, -1.27531, 0.0, 0.0],
"CAUCHY_STRESS_TENSOR": [-47.0336, -89.7376, -50.2142, -1.27531, 0.0, 0.0]},
{"node": 1442,
"TOTAL_STRESS_TENSOR": [-245.877, -319.876, -245.89, 1.67524, 0.0, 0.0],
"CAUCHY_STRESS_TENSOR": [-108.537, -182.536, -108.55, 1.67524, 0.0, 0.0]}]
}]


def check_displacements(self):
rfaasse marked this conversation as resolved.
Show resolved Hide resolved
reader = test_helper.GiDOutputFileReader()

for item in self.expected_displacements:
time = item["time"]
node_ids = [sub_item["node"] for sub_item in item["expected_values"]]
expected_displacements = [sub_item["DISPLACEMENT"] for sub_item in item["expected_values"]]

actual_data = reader.read_output_from(os.path.join(self.test_path, item["output_filename"]))
actual_displacements = reader.nodal_values_at_time("DISPLACEMENT", time, actual_data, node_ids)

self.assertEqual(len(actual_displacements), len(expected_displacements))
for actual_displacement, expected_displacement in zip(actual_displacements, expected_displacements):
self.assertVectorAlmostEqual(actual_displacement, expected_displacement, 3)


def _check_nodal_stresses_with_name(self, output_data, stress_tensor_name, expected_stress_item):
time = expected_stress_item["time"]
node_ids = [sub_item["node"] for sub_item in expected_stress_item["expected_values"]]
expected_nodal_stresses = [sub_item[stress_tensor_name] for sub_item in expected_stress_item["expected_values"]]

actual_nodal_stresses = test_helper.GiDOutputFileReader.nodal_values_at_time(stress_tensor_name, time, output_data, node_ids)

self.assertEqual(len(actual_nodal_stresses), len(expected_nodal_stresses))
for actual_total_stress, expected_total_stress in zip(actual_nodal_stresses, expected_nodal_stresses):
# Although the values are matrices, they are read as lists, meaning we can use assertVectorAlmostEqual
self.assertVectorAlmostEqual(actual_total_stress, expected_total_stress, 3)


def check_nodal_stresses(self):
reader = test_helper.GiDOutputFileReader()

for item in self.expected_stresses:
actual_data = reader.read_output_from(os.path.join(self.test_path, item["output_filename"]))
self._check_nodal_stresses_with_name(actual_data, "TOTAL_STRESS_TENSOR", item)
self._check_nodal_stresses_with_name(actual_data, "CAUCHY_STRESS_TENSOR", item)


class KratosGeoMechanicsSettlementWorkflowPyRoute(KratosGeoMechanicsSettlementWorkflow):
"""
This test class is used to check the settlement workflow test, same as test_settlement_workflow.cpp to
make sure the python workflow yields the same results as the c++ workflow.
This test class checks the settlement workflow using the Python route.
"""
def get_test_dir_name(self):
return "python"


def test_d_settlement_workflow(self):
test_name = 'test_settlement_workflow'
file_path = test_helper.get_file_path(test_name)
test_helper.run_stages(file_path, 4)
test_helper.run_stages(self.test_path, self.number_of_stages)

times_to_check = [1.0, 2.0, 3.0, 3.2]
self.check_displacements()
self.check_nodal_stresses()

for i in range(4):
result_file_name = os.path.join(file_path, f'test_model_stage{i+1}.post.res')
expected_result_file_name = os.path.join(file_path, f'test_model_stage{i+1}.post.orig.res')

reader = test_helper.GiDOutputFileReader()

# These node ids are at the top corner, at the bottom of the excavation,
# in the middle of the model and at the bottom.
node_ids = [1, 102, 1085, 1442]
class KratosGeoMechanicsSettlementWorkflowCppRoute(KratosGeoMechanicsSettlementWorkflow):
"""
This test class checks the settlement workflow using the C++ route.
"""
def tearDown(self):
# The `KratosGeoSettlement` instance used by this test removes all registered GeoMechanicsApplication
# components when it's destroyed. If no action is taken, any following tests will start to fail due to
# components not being registered. It seems that reloading the relevant Kratos applications overcomes this
# problem.
importlib.reload(KratosMultiphysics.LinearSolversApplication)
importlib.reload(KratosMultiphysics.StructuralMechanicsApplication)
importlib.reload(KratosMultiphysics.GeoMechanicsApplication)

actual_data = reader.read_output_from(result_file_name)
actual_nodal_values = reader.nodal_values_at_time("DISPLACEMENT", times_to_check[i], actual_data, node_ids)
super().tearDown()
rfaasse marked this conversation as resolved.
Show resolved Hide resolved

expected_data = reader.read_output_from(expected_result_file_name)
expected_nodal_values = reader.nodal_values_at_time(
"DISPLACEMENT", times_to_check[i], expected_data, node_ids)

self.assertEqual(len(actual_nodal_values), len(expected_nodal_values))
for actual_displacement, expected_displacement in zip(actual_nodal_values, expected_nodal_values):
self.assertVectorAlmostEqual(actual_displacement, expected_displacement, 3)
def get_test_dir_name(self):
return "cpp"

if i > 2:
self.check_stress_values(expected_result_file_name, times_to_check[i], node_ids, reader,
result_file_name, "TOTAL_STRESS_TENSOR")
self.check_stress_values(expected_result_file_name, times_to_check[i], node_ids, reader,
result_file_name, "CAUCHY_STRESS_TENSOR")

def check_stress_values(self, expected_result_file_name, time_to_check, node_ids, reader, result_file_name,
variable_name):
actual_data = reader.read_output_from(result_file_name)
actual_nodal_stress_values = reader.nodal_values_at_time(variable_name, time_to_check, actual_data, node_ids)
expected_data = reader.read_output_from(expected_result_file_name)
expected_nodal_stress_values = reader.nodal_values_at_time(
variable_name, time_to_check, expected_data, node_ids)
self.assertEqual(len(actual_nodal_stress_values), len(expected_nodal_stress_values))
for actual_total_stress, expected_total_stress in zip(actual_nodal_stress_values, expected_nodal_stress_values):
# Although the values are matrices, they are read as lists,
# meaning we can use assertVectorAlmostEqual
self.assertVectorAlmostEqual(actual_total_stress, expected_total_stress, 3)

def test_d_settlement_workflow(self):
settlement_api = GeoMechanicsApplication.CustomWorkflowFactory.CreateKratosGeoSettlement()

no_logging = lambda msg: None
no_progress_reporting = lambda fraction_done: None
no_progress_message = lambda msg: None
do_not_cancel = lambda: False

for project_parameters_filename in self.project_parameters_filenames:
status = settlement_api.RunStage(self.test_path, project_parameters_filename, no_logging, no_progress_reporting, no_progress_message, do_not_cancel)
self.assertEqual(status, 0)

self.check_displacements()
self.check_nodal_stresses()

# Don't rely on the garbage collector to clean up the API object. Make sure it's destructor has run before
# executing the test case's `tearDown` method (which will reload the relevant Kratos applications)
del settlement_api


if __name__ == '__main__':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from test_normal_load_on_hexa_element import KratosGeoMechanicsNormalLoadHexaTests
from test_pressure_line_element import KratosGeoMechanicsTransientPressureLineElementTests
from test_pressure_point_flux import KratosGeoMechanicsTransientPressurePointFluxTests
from settlement_workflow import KratosGeoMechanicsSettlementWorkflow
from settlement_workflow import KratosGeoMechanicsSettlementWorkflowCppRoute, KratosGeoMechanicsSettlementWorkflowPyRoute
from test_compressibility import KratosGeoMechanicsCompressibilityTests
from fixed_spatial_variation import KratosGeoMechanicsFixedSpatialVariationTests
from test_integration_node_extrapolation import KratosGeoMechanicsExtrapolationTests
Expand Down Expand Up @@ -113,6 +113,8 @@ def AssembleTestSuites():
# - testNightlySecondExample

night_test_cases = [
KratosGeoMechanicsSettlementWorkflowCppRoute,
KratosGeoMechanicsSettlementWorkflowPyRoute,
KratosGeoMechanicsCPhiReductionProcess,
KratosGeoMechanicsInterfaceTests,
KratosGeoMechanicsDynamicsTests,
Expand All @@ -122,8 +124,7 @@ def AssembleTestSuites():
KratosGeoMechanicsTransientThermalTests,
KratosGeoMechanicsTimeIntegrationTests,
KratosGeoMechanicsTransientPressureLineElementTests,
KratosGeoMechanicsTransientPressurePointFluxTests,
KratosGeoMechanicsSettlementWorkflow
KratosGeoMechanicsTransientPressurePointFluxTests
]
night_test_cases.extend(small_test_cases)

Expand Down
Loading
Loading