diff --git a/applications/StructuralMechanicsApplication/python_scripts/step_controller.py b/applications/StructuralMechanicsApplication/python_scripts/step_controller.py new file mode 100644 index 000000000000..a3fdaff85e35 --- /dev/null +++ b/applications/StructuralMechanicsApplication/python_scripts/step_controller.py @@ -0,0 +1,210 @@ +from abc import ABC, abstractmethod +import KratosMultiphysics as Kratos + +class StepController(ABC): + """\ + @brief Abstract base class for controlling the progression of steps in a simulation. + + @details + Defines the interface used by all step controllers to manage time stepping + behaviour during a simulation. Implementations must provide logic for + determining the next step size, and checking completion. + """ + + @abstractmethod + def GetNextStep(self, current_time: float, is_converged: bool) -> float: + """\ + @brief Compute the next stepping time. + + @param[in] current_time The current simulation time. + @param[in] is_converged True if the previous step converged successfully. + + @return The proposed time for the next step. + """ + pass + + @abstractmethod + def IsCompleted(self, current_time: float, is_converged: bool) -> bool: + """\ + @brief Query whether the stepping procedure has finished. + + @param[in] current_time The current simulation time. + @param[in] is_converged Flag indicating convergence at the current step. + + @return True when no further steps are required, false otherwise. + """ + pass + +class DefaultStepController(StepController): + """\ + @brief A trivial controller that jumps directly to the final time. + + @details + This step controller ignores intermediate steps and always returns the + final simulation time as the next step. It is useful when no sub-stepping + behaviour is desired. + """ + + @classmethod + def GetDefaultParameters(cls) -> Kratos.Parameters: + return Kratos.Parameters("""{ + "type": "default_step_controller" + }""") + + def __init__(self, _: float, time_end: float, parameters: Kratos.Parameters) -> None: + parameters.ValidateAndAssignDefaults(self.GetDefaultParameters()) + self.__time_end = time_end + + def GetNextStep(self, _: float, __: bool) -> float: + return self.__time_end + + def IsCompleted(self, _: float, __: bool) -> bool: + return True + +class GeometricStepController(StepController): + """\ + @brief Adaptive geometric time-step controller. + + @details + A controller that modifies the time increment according to a geometric + progression. When consecutive steps converge, the step size is multiplied + by a convergence factor up to a maximum. Conversely, on divergence the step + size is scaled down by a divergence factor until a minimum is reached. The + controller also tracks the number of sub-steps and failed attempts, throwing + runtime errors if limits are exceeded. + """ + @classmethod + def GetDefaultParameters(cls) -> Kratos.Parameters: + return Kratos.Parameters("""{ + "type" : "geometric_step_controller", + "divergence_factor" : 0.25, + "convergence_factor" : 1.5, + "delta_t_init" : 1.0, + "delta_t_min" : 1.0, + "delta_t_max" : 1.0, + "max_number_of_sub_steps" : 100, + "number_of_successful_attempts_for_increment": 2, + "number_of_failed_attempts_for_termination" : 5, + "verbosity" : 0 + }""") + + def __init__(self, time_begin: float, time_end: float, settings: Kratos.Parameters) -> None: + default_parameters = self.GetDefaultParameters() + + settings.ValidateAndAssignDefaults(default_parameters) + + self.__divergence_factor = settings["divergence_factor"].GetDouble() + self.__convergence_factor = settings["convergence_factor"].GetDouble() + self.__delta_t_init = settings["delta_t_init"].GetDouble() + self.__delta_t_min = settings["delta_t_min"].GetDouble() + self.__delta_t_max = settings["delta_t_max"].GetDouble() + self.__verbosity = settings["verbosity"].GetInt() + self.__max_number_of_sub_steps = settings["max_number_of_sub_steps"].GetInt() + self.__number_of_successful_attempts_for_increment = settings["number_of_successful_attempts_for_increment"].GetInt() + self.__number_of_failed_attempts_for_termination = settings["number_of_failed_attempts_for_termination"].GetInt() + + self.__time_begin = time_begin + self.__time_end = time_end + + self.__sub_stepping_iteration = 0 + self.__success_full_attempts_count = 0 + self.__failed_attempts_count = 0 + self.__delta_time = self.__delta_t_init + + def GetNextStep(self, current_time: float, is_converged: bool) -> float: + + self.__sub_stepping_iteration += 1 + if self.__sub_stepping_iteration >= self.__max_number_of_sub_steps: + raise RuntimeError(f"{self.__class__.__name__}: Reached maximum number of Load steps [ max number of Load steps = {self.__max_number_of_sub_steps} ].") + + if is_converged: + # the sub-step is converged. So try the next Step + self.__failed_attempts_count = 0 + self.__success_full_attempts_count += 1 + + if self.__success_full_attempts_count > self.__number_of_successful_attempts_for_increment: + # now we increment the step + self.__delta_time = min(self.__convergence_factor * self.__delta_time, self.__delta_t_max) + if self.__verbosity > 0: + Kratos.Logger.PrintInfo(self.__class__.__name__, f"Incrementing the delta time to {self.__delta_time}") + + return min(self.__time_end, current_time + self.__delta_time) + else: + # the sub-step is not converged. + self.__failed_attempts_count += 1 + self.__success_full_attempts_count = 0 + + if (self.__failed_attempts_count > self.__number_of_failed_attempts_for_termination): + raise RuntimeError(f"Reached maximum number of failed attempts [ {self.__failed_attempts_count} / {self.__number_of_failed_attempts_for_termination} ]") + else: + self.__delta_time = self.__divergence_factor * self.__delta_time + if self.__verbosity > 0: + Kratos.Logger.PrintInfo(self.__class__.__name__, f"Decrementing the delta time to {self.__delta_time}") + if self.__delta_time <= self.__delta_t_min: + raise RuntimeError(f"Reached the minimum allowed delta time [ delta_time = {self.__delta_time}, allowed minimum delta_time = {self.__delta_t_min} ]") + + new_time = current_time + self.__delta_time + if self.__verbosity > 0: + Kratos.Logger.PrintInfo(self.__class__.__name__, f"Solving Step {self.__sub_stepping_iteration} for time = {new_time:0.6e}") + return new_time + + def IsCompleted(self, current_time: float, is_converged: bool) -> bool: + return is_converged and abs(current_time - self.__time_end) <= (self.__time_end - self.__time_begin) * 1e-9 + +class CompoundStepController(StepController): + """\ + @brief CompoundStepController handles different step controllers over different time intervals. + + @details + This class allows for adaptive time stepping strategies that change based on the current + time period. It selects the appropriate step controller from a list based on which interval + the current time falls within. + + @throws RuntimeError If no step controller is found for the given time period. + """ + @classmethod + def GetDefaultParameters(cls) -> Kratos.Parameters: + return Kratos.Parameters("""{ + "type" : "compound_step_controller", + "list_of_step_controllers": [ + { + "interval": [0.0, "End"], + "settings": {} + } + ] + }""") + + def __init__(self, time_begin: float, time_end: float, settings: Kratos.Parameters) -> None: + settings.ValidateAndAssignDefaults(self.GetDefaultParameters()) + self.step_controller = None + for step_controller_setting in reversed(settings["list_of_step_controllers"].values()): + current_settings: Kratos.Parameters = step_controller_setting.Clone() + current_settings.ValidateAndAssignDefaults(self.GetDefaultParameters()["list_of_step_controllers"].values()[0]) + if Kratos.IntervalUtility(current_settings).IsInInterval(time_end): + self.step_controller = Factory(time_begin, time_end, current_settings["settings"]) + break + + if self.step_controller is None: + raise RuntimeError(f"No step controller is found for the time period [{time_begin}, {time_end}].") + + def GetNextStep(self, current_time: float, is_converged: bool) -> float: + return self.step_controller.GetNextStep(current_time, is_converged) + + def IsCompleted(self, current_time: float, is_converged: bool) -> bool: + return self.step_controller.IsCompleted(current_time, is_converged) + +def Factory(time_begin: float, time_end: float, parameters: Kratos.Parameters) -> StepController: + if not parameters.Has("type"): + parameters.AddString("type", "default_step_controller") + + step_controller_map = { + "default_step_controller": DefaultStepController, + "geometric_step_controller": GeometricStepController, + "compound_step_controller": CompoundStepController + } + + controller_type = parameters["type"].GetString() + if controller_type not in step_controller_map.keys(): + raise RuntimeError(f"Unsupported controller type = \"{controller_type}\". Followings are supported:\n\t" + "\n\t".join(step_controller_map.keys())) + + return step_controller_map[controller_type](time_begin, time_end, parameters) \ No newline at end of file diff --git a/applications/StructuralMechanicsApplication/python_scripts/structural_mechanics_load_stepping_analysis.py b/applications/StructuralMechanicsApplication/python_scripts/structural_mechanics_load_stepping_analysis.py new file mode 100644 index 000000000000..df1cdb0461b4 --- /dev/null +++ b/applications/StructuralMechanicsApplication/python_scripts/structural_mechanics_load_stepping_analysis.py @@ -0,0 +1,107 @@ +import KratosMultiphysics as Kratos +from KratosMultiphysics.StructuralMechanicsApplication.structural_mechanics_analysis import StructuralMechanicsAnalysis +from KratosMultiphysics.StructuralMechanicsApplication.step_controller import StepController, DefaultStepController, Factory + +class StructuralMechanicsLoadSteppingAnalysis(StructuralMechanicsAnalysis): + def RunSolutionLoop(self): + """This function executes the solution loop of the AnalysisStage + It can be overridden by derived classes + """ + + computing_mp: Kratos.ModelPart = self._GetSolver().GetComputingModelPart() + + def __get_serializer(): + return Kratos.FileSerializer("serialization", Kratos.SerializerTraceType.SERIALIZER_NO_TRACE, True) + + self.is_converged = False + while self.KeepAdvancingSolutionLoop(): + time_begin = self.time # current step lower bound + # current step upper bound. + # - This also clones a new time step + # - This sets the DELTA_TIME (time_end - time_begin) + time_end = self._AdvanceTime() + + # create the appropriate step controller + if self.project_parameters.Has("step_controller_settings"): + step_controller = Factory(time_begin, time_end, self.project_parameters["step_controller_settings"]) + else: + step_controller = DefaultStepController(time_begin, time_end, Kratos.Parameters("""{}""")) + + self.time = time_end + + __get_serializer().Save(computing_mp.FullName(), computing_mp) + + # first try to solve for the final time. + self.InitializeSolutionStep() + self.is_converged = self._GetSolver().SolveSolutionStep() + + if not self.is_converged: + Kratos.Logger.PrintInfo(self.__class__.__name__, f"Did not converge for time = {self.time}.") + + current_step_controller_time = time_begin + while not step_controller.IsCompleted(current_step_controller_time, self.is_converged): + if self.is_converged: + Kratos.Logger.PrintInfo(self.__class__.__name__, f"Step at time = [{time_begin}, {self.time}] converged.") + # so the sub-step converged. + + # first finalize the success full step + self.FinalizeSolutionStep() + self.OutputSolutionStep() + + # make the next step ready for solving + computing_mp.ProcessInfo[Kratos.STEP] += 1 + + # here we will correctly set TIME and DELTA_TIME + # and put current values in the previous time step + time_begin = self.time + self.time = step_controller.GetNextStep(self.time, self.is_converged) + computing_mp.CloneTimeStep(self.time) + + # now collect the nodes positions, which may have moved + # can be used at a later time to reset the nodal positions. + __get_serializer().Save(computing_mp.FullName(), computing_mp) + else: + Kratos.Logger.PrintInfo(self.__class__.__name__, f"Step at time = {self.time} did not converge.") + # here we need to reset the coordinates of the mesh, if someone has used the move_mesh_flag = true + # reset the mesh coordinates + __get_serializer().Load(computing_mp.FullName(), computing_mp) + + # sub_step did not converge + # do not advance in step. get a new sub-step + self.time = step_controller.GetNextStep(time_begin, self.is_converged) + computing_mp.ProcessInfo[Kratos.TIME] = self.time + computing_mp.ProcessInfo[Kratos.DELTA_TIME] = self.time - time_begin + + self.InitializeSolutionStep() + self.is_converged = self._GetSolver().SolveSolutionStep() + + current_step_controller_time = self.time + + # we need the last finalize solution step, because once the Solver solves, and converges, and + # [t_begin, t_end] is reached, it will no longer go in to the stepping while loop. + self.FinalizeSolutionStep() + self.OutputSolutionStep() + +if __name__ == "__main__": + from sys import argv + + if len(argv) > 2: + err_msg = 'Too many input arguments!\n' + err_msg += 'Use this script in the following way:\n' + err_msg += '- With default ProjectParameters (read from "ProjectParameters.json"):\n' + err_msg += ' "python3 structural_mechanics_analysis.py"\n' + err_msg += '- With custom ProjectParameters:\n' + err_msg += ' "python3 structural_mechanics_analysis.py CustomProjectParameters.json"\n' + raise Exception(err_msg) + + if len(argv) == 2: # ProjectParameters is being passed from outside + project_parameters_file_name = argv[1] + else: # using default name + project_parameters_file_name = "ProjectParameters.json" + + with open(project_parameters_file_name,'r') as parameter_file: + parameters = Kratos.Parameters(parameter_file.read()) + + model = Kratos.Model() + simulation = StructuralMechanicsLoadSteppingAnalysis(model, parameters) + simulation.Run() diff --git a/applications/StructuralMechanicsApplication/tests/beam_test/dynamic_3D2NBeamCr_pseudo_step_test_parameters.json b/applications/StructuralMechanicsApplication/tests/beam_test/dynamic_3D2NBeamCr_pseudo_step_test_parameters.json new file mode 100755 index 000000000000..25bffb304ba5 --- /dev/null +++ b/applications/StructuralMechanicsApplication/tests/beam_test/dynamic_3D2NBeamCr_pseudo_step_test_parameters.json @@ -0,0 +1,139 @@ +{ + "problem_data": { + "problem_name": "Beam_nonlinear_cantilever", + "parallel_type": "OpenMP", + "start_time": 0.0, + "end_time": 1.0, + "echo_level": 0 + }, + "step_controller_settings": { + "type": "geometric_step_controller", + "divergence_factor": 0.25, + "convergence_factor": 1.5, + "delta_t_init": 0.05, + "delta_t_min": 1e-5, + "delta_t_max": 0.2, + "max_number_of_sub_steps": 100, + "number_of_successful_attempts_for_increment": 2, + "number_of_failed_attempts_for_termination": 5 + }, + "solver_settings": { + "solver_type": "static", + "model_part_name": "Structure", + "domain_size": 3, + "echo_level": 0, + "analysis_type": "non_linear", + "model_import_settings": { + "input_type": "mdpa", + "input_filename": "beam_test/dynamic_3D2NBeamCr_test" + }, + "time_stepping": { + "time_step": 0.125 + }, + "line_search": false, + "convergence_criterion": "residual_criterion", + "displacement_relative_tolerance": 1e-5, + "displacement_absolute_tolerance": 1e-5, + "residual_relative_tolerance": 1e-5, + "residual_absolute_tolerance": 1e-5, + "max_iteration": 10, + "rotation_dofs": true + }, + "processes": { + "constraints_process_list": [ + { + "python_module": "assign_vector_variable_process", + "kratos_module": "KratosMultiphysics", + "help": "This process fixes the selected components of a given vector variable", + "process_name": "AssignVectorVariableProcess", + "Parameters": { + "model_part_name": "Structure.DISPLACEMENT_dirichletXYZ", + "variable_name": "DISPLACEMENT", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "interval": [ + 0.0, + "End" + ] + } + }, + { + "python_module": "assign_vector_variable_process", + "kratos_module": "KratosMultiphysics", + "help": "This process fixes the selected components of a given vector variable", + "process_name": "AssignVectorVariableProcess", + "Parameters": { + "model_part_name": "Structure.ROTATION_dirrot", + "variable_name": "ROTATION", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "interval": [ + 0.0, + "End" + ] + } + } + ], + "loads_process_list": [ + { + "python_module": "assign_vector_by_direction_to_condition_process", + "kratos_module": "KratosMultiphysics", + "help": "This process sets a vector variable value over a condition", + "check": "DirectorVectorNonZero direction", + "process_name": "AssignModulusAndDirectionToConditionsProcess", + "Parameters": { + "model_part_name": "Structure.PointLoad3D_neumann", + "variable_name": "POINT_LOAD", + "modulus": "10000000.0*t", + "direction": [ + 0, + -1, + 0 + ], + "interval": [ + 0.0, + "End" + ] + } + } + ], + "list_other_processes": [ + { + "python_module": "from_json_check_result_process", + "kratos_module": "KratosMultiphysics", + "help": "", + "process_name": "FromJsonCheckResultProcess", + "Parameters": { + "check_variables": [ + "DISPLACEMENT_Y" + ], + "input_file_name": "beam_test/dynamic_3D2NBeamCr_pseudo_step_test_results.json", + "model_part_name": "Structure.PointLoad3D_neumann", + "time_frequency": 1e-6 + } + } + ] + }, + "print_output_process": [ + { + "python_module": "json_output_process", + "kratos_module": "KratosMultiphysics", + "help": "", + "process_name": "JsonOutputProcess", + "Parameters": { + "output_variables": [ + "DISPLACEMENT_Y" + ], + "output_file_name": "beam_test/dynamic_3D2NBeamCr_pseudo_step_test_results.json", + "model_part_name": "Structure.PointLoad3D_neumann", + "time_frequency": 1e-6 + } + } + ] +} \ No newline at end of file diff --git a/applications/StructuralMechanicsApplication/tests/beam_test/dynamic_3D2NBeamCr_pseudo_step_test_results.json b/applications/StructuralMechanicsApplication/tests/beam_test/dynamic_3D2NBeamCr_pseudo_step_test_results.json new file mode 100644 index 000000000000..fb3bb7be27d0 --- /dev/null +++ b/applications/StructuralMechanicsApplication/tests/beam_test/dynamic_3D2NBeamCr_pseudo_step_test_results.json @@ -0,0 +1,104 @@ +{ + "TIME": [ + 0.125, + 0.25, + 0.375, + 0.3875, + 0.4, + 0.41250000000000003, + 0.43125, + 0.45937500000000003, + 0.5, + 0.5125, + 0.5249999999999999, + 0.5374999999999999, + 0.5562499999999999, + 0.5843749999999999, + 0.625, + 0.6375, + 0.6499999999999999, + 0.6624999999999999, + 0.6812499999999999, + 0.7093749999999999, + 0.75, + 0.7625, + 0.7749999999999999, + 0.7874999999999999, + 0.8062499999999999, + 0.8343749999999999, + 0.8449218749999998, + 0.8554687499999998, + 0.8660156249999997, + 0.875, + 0.8875, + 0.8999999999999999, + 0.9124999999999999, + 0.9171874999999998, + 0.9218749999999998, + 0.9265624999999997, + 0.9335937499999998, + 0.9441406249999997, + 0.9480957031249997, + 0.9520507812499996, + 0.9560058593749996, + 0.9619384765624996, + 0.9708374023437496, + 0.9841857910156246, + 0.9891914367675778, + 0.994197082519531, + 0.9992027282714842, + 1.0 + ], + "NODE_11": { + "DISPLACEMENT_Y": [ + -0.19099939402652352, + -0.34725030542755686, + -0.46187307439223046, + -0.47135664185246784, + -0.4805257824086631, + -0.4893917975082423, + -0.5021466866981458, + -0.520126307843976, + -0.5438661989931176, + -0.5506841725581821, + -0.557288714676356, + -0.5636882042773053, + -0.5729204985654933, + -0.5859938375915992, + -0.6033806184053648, + -0.6084038999909442, + -0.6132838371420217, + -0.6180259235148735, + -0.6248920191314431, + -0.6346681151971973, + -0.6477747853535226, + -0.6515853704095523, + -0.6552979968244164, + -0.6589161962477869, + -0.6641737997999305, + -0.6716994927222932, + -0.6744159751501222, + -0.6770774489925141, + -0.6796855141532641, + -0.68186623190818, + -0.6848393978023032, + -0.6877438921181521, + -0.6905820137079975, + -0.6916296504974221, + -0.6926683775492074, + -0.6936983060924344, + -0.6952269408136239, + -0.6974840263080051, + -0.6983195462528496, + -0.6991492235133063, + -0.6999731185192647, + -0.7011982495889593, + -0.7030122360224984, + -0.7056811521726393, + -0.706666262716544, + -0.707642962051921, + -0.7086113572781008, + -0.7087648383243628 + ] + } +} \ No newline at end of file diff --git a/applications/StructuralMechanicsApplication/tests/structural_mechanics_test_factory.py b/applications/StructuralMechanicsApplication/tests/structural_mechanics_test_factory.py index b7e97fe0b845..08fa6fe15e76 100644 --- a/applications/StructuralMechanicsApplication/tests/structural_mechanics_test_factory.py +++ b/applications/StructuralMechanicsApplication/tests/structural_mechanics_test_factory.py @@ -3,6 +3,7 @@ from KratosMultiphysics import IsDistributedRun import KratosMultiphysics.kratos_utilities as kratos_utils from KratosMultiphysics.StructuralMechanicsApplication.structural_mechanics_analysis import StructuralMechanicsAnalysis +from KratosMultiphysics.StructuralMechanicsApplication.structural_mechanics_load_stepping_analysis import StructuralMechanicsLoadSteppingAnalysis # Import KratosUnittest import KratosMultiphysics.KratosUnittest as KratosUnittest @@ -37,6 +38,8 @@ def SelectAndVerifyLinearSolver(settings, skiptest): class StructuralMechanicsTestFactory(KratosUnittest.TestCase): + analysis_type = StructuralMechanicsAnalysis + def setUp(self): # Within this location context: with KratosUnittest.WorkFolderScope(".", __file__): @@ -57,7 +60,7 @@ def setUp(self): # Creating the test model = KratosMultiphysics.Model() - self.test = StructuralMechanicsAnalysis(model, ProjectParameters) + self.test = self.analysis_type(model, ProjectParameters) self.test.Initialize() def modify_parameters(self, project_parameters): @@ -275,6 +278,24 @@ class Simple3D2NBeamCrNonLinearTest(StructuralMechanicsTestFactory): class Simple3D2NBeamCrDynamicTest(StructuralMechanicsTestFactory): file_name = "beam_test/dynamic_3D2NBeamCr_test" +class Simple3D2NBeamCrDynamicPseudoStepTest(StructuralMechanicsTestFactory): + class TestClass(StructuralMechanicsLoadSteppingAnalysis): + list_of_steps: 'list[int]' = [] + def FinalizeSolutionStep(self): + super().FinalizeSolutionStep() + self.list_of_steps.append(self.is_converged) + + file_name = "beam_test/dynamic_3D2NBeamCr_pseudo_step_test" + analysis_type = TestClass + + def test_execution(self): + super().test_execution() + self.assertTrue(all(self.test.list_of_steps)) + + def tearDown(self): + super().tearDown() + kratos_utils.DeleteFileIfExisting("serialization.rest") + class Simple2D2NBeamCrTest(StructuralMechanicsTestFactory): file_name = "beam_test/nonlinear_2D2NBeamCr_test" diff --git a/applications/StructuralMechanicsApplication/tests/test_StructuralMechanicsApplication.py b/applications/StructuralMechanicsApplication/tests/test_StructuralMechanicsApplication.py index faae135ed443..0c8e85654a7d 100644 --- a/applications/StructuralMechanicsApplication/tests/test_StructuralMechanicsApplication.py +++ b/applications/StructuralMechanicsApplication/tests/test_StructuralMechanicsApplication.py @@ -174,6 +174,7 @@ from structural_mechanics_test_factory import Simple3D2NBeamCrLinearTest as T3D2NBeamCrLinearTest from structural_mechanics_test_factory import SimpleSemiRigid3D2NBeamCrLinearTest as T3D2NBeamCrLinearSemiRigidTest from structural_mechanics_test_factory import Simple3D2NBeamCrDynamicTest as T3D2NBeamCrDynamicTest +from structural_mechanics_test_factory import Simple3D2NBeamCrDynamicPseudoStepTest as T3D2NBeamCrDynamicPseudoStepTest from structural_mechanics_test_factory import Simple2D2NBeamCrTest as T2D2NBeamCrTest from structural_mechanics_test_factory import Simple3D2NTrussNonLinearSnapthroughDisplacementControlTest as T3D2NNLDispCtrlTest # Shell tests @@ -247,6 +248,7 @@ from restart_tests import TestSmallDisplacement2D4N as TTestSmallDisplacement2D4N from restart_tests import TestTotalLagrangian2D3N as TTestTotalLagrangian2D3N from restart_tests import TestUpdatedLagrangian3D8N as TTestUpdatedLagrangian3D8N +from test_step_controller import TestStepControllers as TTestStepControllers ##### RESPONSE_FUNCTION ##### from structural_response_function_test_factory import TestAdjointStrainEnergyResponseFunction as TTestAdjointStrainEnergyResponseFunction @@ -377,6 +379,7 @@ def AssembleTestSuites(): smallSuite.addTest(TTimoshenkoCurvedBeam2D3NTest('test_execution')) smallSuite.addTest(TTimoshenkoCurvedBeam3D3NTest('test_execution')) smallSuite.addTest(TAutomatedInitialVariableProcessTest('test_execution')) + smallSuite.addTest(T3D2NBeamCrDynamicPseudoStepTest('test_execution')) nightSuite.addTest(TSDTwoDShearQuaPatchTest('test_execution')) nightSuite.addTest(TSDTwoDShearTriPatchTest('test_execution')) nightSuite.addTest(TSDTwoDTensionQuaPatchTest('test_execution')) @@ -459,6 +462,7 @@ def AssembleTestSuites(): print("FEAST not available in LinearSolversApplication") nightSuite.addTests(KratosUnittest.TestLoader().loadTestsFromTestCases([THarmonicAnalysisTestsWithHDF5])) + nightSuite.addTests(KratosUnittest.TestLoader().loadTestsFromTestCases([TTestStepControllers])) nightSuite.addTest(TTestAdjointSensitivityAnalysisBeamStructureLocalStress('test_execution')) nightSuite.addTest(TTestAdjointSensitivityAnalysisBeamStructureNodalDisplacement('test_execution')) diff --git a/applications/StructuralMechanicsApplication/tests/test_step_controller.py b/applications/StructuralMechanicsApplication/tests/test_step_controller.py new file mode 100644 index 000000000000..9eb30f654722 --- /dev/null +++ b/applications/StructuralMechanicsApplication/tests/test_step_controller.py @@ -0,0 +1,143 @@ +# Importing the Kratos Library +import KratosMultiphysics +import KratosMultiphysics.KratosUnittest as KratosUnittest +from KratosMultiphysics.StructuralMechanicsApplication.step_controller import DefaultStepController, GeometricStepController, CompoundStepController + +class TestStepControllers(KratosUnittest.TestCase): + def test_DefaultStepController(self): + params = KratosMultiphysics.Parameters("""{}""") + step_controller = DefaultStepController(10, 15, params) + + self.assertTrue(step_controller.IsCompleted(100, False)) + self.assertTrue(step_controller.IsCompleted(100, True)) + self.assertTrue(step_controller.IsCompleted(12, False)) + self.assertTrue(step_controller.IsCompleted(12, True)) + self.assertTrue(step_controller.IsCompleted(5, False)) + self.assertTrue(step_controller.IsCompleted(5, True)) + + self.assertEqual(step_controller.GetNextStep(14, False), 15) + self.assertEqual(step_controller.GetNextStep(14, True), 15) + + def test_GeometricStepController1(self): + params = KratosMultiphysics.Parameters("""{ + "type" : "geometric_step_controller", + "divergence_factor" : 0.25, + "convergence_factor" : 1.5, + "delta_t_init" : 1.1, + "delta_t_min" : 0.12, + "delta_t_max" : 3.1, + "max_number_of_sub_steps" : 25, + "number_of_successful_attempts_for_increment": 2, + "number_of_failed_attempts_for_termination" : 5 + }""") + + step_controller = GeometricStepController(0.2, 10.0, params) + + self.assertFalse(step_controller.IsCompleted(0.1, True)) + self.assertFalse(step_controller.IsCompleted(0.2, True)) + self.assertFalse(step_controller.IsCompleted(5, True)) + self.assertFalse(step_controller.IsCompleted(10, False)) + self.assertTrue(step_controller.IsCompleted(10, True)) + + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 1.1) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 1.1) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 1.1 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 1.1 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1) + + self.assertAlmostEqual(step_controller.GetNextStep(0.3, False), 0.3 + 3.1 * 0.25) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, False), 0.3 + 3.1 * 0.25 * 0.25) + + with self.assertRaises(RuntimeError): + self.assertAlmostEqual(step_controller.GetNextStep(0.3, False), 0.3 + 3.1 * 0.25 * 0.25 * 0.25) + + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1 * 0.25 * 0.25 * 0.25 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5 * 1.5) + self.assertAlmostEqual(step_controller.GetNextStep(0.3, True), 0.3 + 3.1) + + def test_GeometricStepController2(self): + params = KratosMultiphysics.Parameters("""{ + "type" : "geometric_step_controller", + "divergence_factor" : 0.25, + "convergence_factor" : 1.5, + "delta_t_init" : 1.1, + "delta_t_min" : 0.001, + "delta_t_max" : 3.1, + "max_number_of_sub_steps" : 25, + "number_of_successful_attempts_for_increment": 2, + "number_of_failed_attempts_for_termination" : 2 + }""") + + step_controller = GeometricStepController(0.2, 10.0, params) + + step_controller.GetNextStep(0.3, False) + step_controller.GetNextStep(0.3, False) + with self.assertRaises(RuntimeError): + step_controller.GetNextStep(0.3, False) + + def test_CompoundStepController(self): + params = KratosMultiphysics.Parameters("""{ + "type" : "compound_step_controller", + "list_of_step_controllers": [ + { + "interval": [0.1, 0.2], + "settings": { + "type": "default_step_controller" + } + }, + { + "interval": [0.2, 10.0], + "settings": { + "type" : "geometric_step_controller", + "divergence_factor" : 0.25, + "convergence_factor" : 1.5, + "delta_t_init" : 1.1, + "delta_t_min" : 0.12, + "delta_t_max" : 3.1, + "max_number_of_sub_steps" : 25, + "number_of_successful_attempts_for_increment": 2, + "number_of_failed_attempts_for_termination" : 5 + } + }, + { + "interval": [10.2, 20.0], + "settings": { + "type" : "geometric_step_controller", + "divergence_factor" : 0.25, + "convergence_factor" : 1.5, + "delta_t_init" : 1.1, + "delta_t_min" : 0.001, + "delta_t_max" : 3.1, + "max_number_of_sub_steps" : 25, + "number_of_successful_attempts_for_increment": 2, + "number_of_failed_attempts_for_termination" : 2 + } + } + ] + }""") + + step_controller = CompoundStepController(0.1, 0.19, params) + self.assertTrue(isinstance(step_controller.step_controller, DefaultStepController)) + + step_controller = CompoundStepController(0.2, 0.4, params) + self.assertTrue(isinstance(step_controller.step_controller, GeometricStepController)) + + step_controller = CompoundStepController(0.4, 10.0, params) + self.assertTrue(isinstance(step_controller.step_controller, GeometricStepController)) + + with self.assertRaises(RuntimeError): + step_controller = CompoundStepController(10.0, 10.19, params) + +if __name__ == '__main__': + KratosMultiphysics.Logger.GetDefaultOutput().SetSeverity(KratosMultiphysics.Logger.Severity.WARNING) + KratosUnittest.main() \ No newline at end of file