How to create unitary tests

From KratosWiki
(Difference between revisions)
Jump to: navigation, search
 
(18 intermediate revisions by 3 users not shown)
Line 1: Line 1:
Kratos Multiphysics has a mechanism to automatically test your code called "KratosUnittest". If you are familiar with python, this module is basically an extension of Unittest[https://docs.python.org/2/library/unittest.html] and you can expect to find every functionality that exists on in as well in KratosUnittest.
+
Kratos Multiphysics has a mechanism to automatically test your code called "KratosUnittest". If you are familiar with Python, this module is basically an extension of Unittest[https://docs.python.org/2/library/unittest.html] and you can expect to find every functionality that exists there in KratosUnittest as well.
  
In order for your application to be robust, is recommended that you add unittests to it. Here we present some guidelines on how to do it.
+
In order for your application to be robust, it is recommended to add unittests to it. Here we present some guidelines on how to do it.
  
 
== How to run tests ==
 
== How to run tests ==
Line 13: Line 13:
 
you can specify the following options:
 
you can specify the following options:
  
  -l,--level:        Select the suit. Values: "All", "Nighlty", "Small"(default)
+
  -l,--level:        Select the suit. Values: "All", "Nightly", "Small"(default)
  -v,--vervosity:    Select the vervosity level of the output. Values: 0, 1 (default) , 2
+
  -v,--verbosity:    Select the verbosity level of the output. Values: 0, 1 (default) , 2
  -a,--applications: List of applications to run separated by ":". For example "-a KratosKore:IncompresibleFluidApplication"
+
  -a,--applications: List of applications to run separated by ":". For example "-a KratosKore:IncompressibleFluidApplication"
 
                     All applications compiled are run by default.
 
                     All applications compiled are run by default.
  
Line 22: Line 22:
 
Tests are defined in a python script. To keep tests organized we recommend you to create the tests in your "application/tests/" directory.  
 
Tests are defined in a python script. To keep tests organized we recommend you to create the tests in your "application/tests/" directory.  
  
Tests are organized in suites. Suites are collection of tests that will be run together as a package. In Kratos, we define three basic suites:
+
Tests are organized in suites. Suites are a collection of tests that will be run together as a package. In Kratos, we define three basic suites:
  
 
* All:    which should contain all the tests
 
* All:    which should contain all the tests
* Nighlty: which should contain a set of tests that could be executed in less than 10 min
+
* Nightly: which should contain a set of tests that could be executed in less than 10 min
 
* Small:  which should contain a set of tests that could be executed in less than 1 min
 
* Small:  which should contain a set of tests that could be executed in less than 1 min
  
  
All applications should implement this packages as they can be automatically run, for example in the "nighlty" runs.
+
All applications should implement those packages as they can be automatically run, for example in the "nightly" runs.
In order to add tests you should create at least a couple of files, one to define the tests
+
In order to add tests you should create at least a couple of files, one to define the tests and one to define the suites:
 
+
and one to define the suits:
+
  
 
  "application/tests/test_NAME_OF_OUR_APPLICATION.py"
 
  "application/tests/test_NAME_OF_OUR_APPLICATION.py"
Line 45: Line 43:
 
This file will define the suites to run the tests. We define three different levels of suites in kratos:
 
This file will define the suites to run the tests. We define three different levels of suites in kratos:
 
* '''All''': which should contain all the tests
 
* '''All''': which should contain all the tests
* '''Nighlty''': which should contain a set of tests that could be executed in less than 10 min
+
* '''Nightly''': which should contain a set of tests that could be executed in less than 10 min
 
* '''Small''': which should contain a set of tests that could be executed in less than 1 min
 
* '''Small''': which should contain a set of tests that could be executed in less than 1 min
  
  
In order to add test to some of these suits, one can do it as shown in this example:
+
In order to add a test to some of these suites, one can do it as shown in this example:
  
 
  from __future__ import print_function, absolute_import, division
 
  from __future__ import print_function, absolute_import, division
Line 63: Line 61:
 
  from test_my_app_example_tests_2 import TestCase2 as TestCase2
 
  from test_my_app_example_tests_2 import TestCase2 as TestCase2
 
   
 
   
  def AssambleTestSuites():
+
  def AssembleTestSuites():
 
     ''' Populates the test suites to run.
 
     ''' Populates the test suites to run.
 
      
 
      
     Populates the test suites to run. At least, it should pupulate the suites:
+
     Populates the test suites to run. At least, it should populate the suites:
     "small", "nighlty" and "all"
+
     "small", "nightly" and "all"
 
      
 
      
 
     Return
 
     Return
Line 76: Line 74:
 
     '''
 
     '''
 
      
 
      
     # Get the already defined suits
+
     # Get the already defined suites
 
     suites = KratosUnittest.KratosSuites
 
     suites = KratosUnittest.KratosSuites
 
      
 
      
     # Get the small suit and populate it with some tests from TestCase1 and TestCase2
+
     # Get the small suite and populate it with some tests from TestCase1 and TestCase2
 
     smallSuite = suites['small']
 
     smallSuite = suites['small']
 
     smallSuite.addTest(TestCase1('test_example_small_boo_1'))
 
     smallSuite.addTest(TestCase1('test_example_small_boo_1'))
 
     smallSuite.addTest(TestCase2('test_example_small_foo_1'))
 
     smallSuite.addTest(TestCase2('test_example_small_foo_1'))
 
      
 
      
     # Get the small suit and populate it with some tests from TestCase1 and TestCase2
+
     # Get the small suite and populate it with some tests from TestCase1 and TestCase2
 
     nightSuite = suites['nightly']
 
     nightSuite = suites['nightly']
 
     smallSuite.addTest(TestCase1('test_example_nightly_boo_1'))
 
     smallSuite.addTest(TestCase1('test_example_nightly_boo_1'))
Line 90: Line 88:
 
     smallSuite.addTest(TestCase2('test_example_nightly_foo_1'))
 
     smallSuite.addTest(TestCase2('test_example_nightly_foo_1'))
 
      
 
      
     # Get the small suit and populate it with all tests from TestCase1 and TestCase2
+
     # Get the small suite and populate it with all tests from TestCase1 and TestCase2
 
     allSuite = suites['all']
 
     allSuite = suites['all']
 
     allSuite.addTests(
 
     allSuite.addTests(
Line 104: Line 102:
 
  # The main function executes the tests
 
  # The main function executes the tests
 
  if __name__ == '__main__':
 
  if __name__ == '__main__':
     KratosUnittest.runTests(AssambleTestSuites())
+
     KratosUnittest.runTests(AssembleTestSuites())
  
 
= Structure for examples =
 
= Structure for examples =
  
In order to compute some examples (the examples that use to be located in the folder "test_examples") some additional considerations must to be taken into account. These considerations can be observed in the following example that is located in the StructuralMechanicsApplication. In order to compute a Patch Test, with bending and membrane     behaviours, the following files has been considered, first the main file that will be the one launched.
+
Examples are cases that test your code/application, but are more complex than the unitary tests, take longer and involve many functionalities. A typical example is a Finite Element calculation of few time steps and a few elements.
 +
In order to compute some examples (the examples that used to be located in the folder "test_examples") some additional considerations must to be taken into account. These considerations can be observed in the following example that is located in the StructuralMechanicsApplication/tests. In order to compute a dynamic test (Bossak and Newmark) and a patch test (with bending and membrane behaviours), the following files has been considered, first the main file, which will be the one launched.
  
 
     # import Kratos
 
     # import Kratos
 
     from KratosMultiphysics import *
 
     from KratosMultiphysics import *
 +
    from KratosMultiphysics.ExternalSolversApplication import *
 
     from KratosMultiphysics.SolidMechanicsApplication import *
 
     from KratosMultiphysics.SolidMechanicsApplication import *
 
     from KratosMultiphysics.StructuralMechanicsApplication import *
 
     from KratosMultiphysics.StructuralMechanicsApplication import *
Line 118: Line 118:
 
     import KratosMultiphysics.KratosUnittest as KratosUnittest
 
     import KratosMultiphysics.KratosUnittest as KratosUnittest
 
      
 
      
     # Import the tests o test_classes to create the suits
+
     # Import the tests o test_classes to create the suites
     from SmallTests import SprismTests as TSprismTests
+
    ## SMALL TESTS
 +
    from SmallTests import DynamicBossakTests as TDynamicBossakTests
 +
    from SmallTests import DynamicNewmarkTests as TDynamicNewmarkTests
 +
    from SmallTests import SprismMembranePatchTests as TSprismMembranePatchTests
 +
    from SmallTests import SprismBendingPatchTests as TSprismBendingPatchTests
 +
    from SmallTests import ShellQ4ThickBendingRollUpTests as TShellQ4ThickBendingRollUpTests
 +
    from SmallTests import ShellQ4ThickDrillingRollUpTests as TShellQ4ThickDrillingRollUpTests
 +
    from SmallTests import ShellT3ThinBendingRollUpTests as TShellT3ThinBendingRollUpTests
 +
     from SmallTests import ShellT3ThinDrillingRollUpTests as TShellT3ThinDrillingRollUpTests
 
      
 
      
     def AssambleTestSuites():
+
    ## NIGHTLY TESTS
 +
    from NightlyTests import ShellT3IsotropicScordelisTests as TShellT3IsotropicScordelisTests
 +
   
 +
     def AssembleTestSuites():
 
         ''' Populates the test suites to run.
 
         ''' Populates the test suites to run.
 
      
 
      
         Populates the test suites to run. At least, it should pupulate the suites:
+
         Populates the test suites to run. At least, it should populate the suites:
         "small", "nighlty" and "all"
+
         "small", "nightly" and "all"
 
      
 
      
 
         Return
 
         Return
Line 137: Line 148:
 
         # Create a test suit with the selected tests (Small tests):
 
         # Create a test suit with the selected tests (Small tests):
 
         smallSuite = suites['small']
 
         smallSuite = suites['small']
         smallSuite.addTest(TSprismTests('test_MembranePacth'))
+
         smallSuite.addTest(TDynamicBossakTests('test_execution'))
         smallSuite.addTest(TSprismTests('test_BendingPacth'))
+
         smallSuite.addTest(TDynamicNewmarkTests('test_execution'))
 +
        smallSuite.addTest(TSprismMembranePatchTests('test_execution'))
 +
        smallSuite.addTest(TSprismBendingPatchTests('test_execution'))
 +
        smallSuite.addTest(TShellQ4ThickBendingRollUpTests('test_execution'))
 +
        smallSuite.addTest(TShellQ4ThickDrillingRollUpTests('test_execution'))
 +
        smallSuite.addTest(TShellT3ThinBendingRollUpTests('test_execution'))
 +
        smallSuite.addTest(TShellT3ThinDrillingRollUpTests('test_execution'))
 
      
 
      
         # Create a test suit with the selected tests plus all small tests
+
         # Create a test suite with the selected tests plus all small tests
 
         nightSuite = suites['nightly']
 
         nightSuite = suites['nightly']
 
         nightSuite.addTests(smallSuite)
 
         nightSuite.addTests(smallSuite)
 +
        nightSuite.addTest(TShellT3IsotropicScordelisTests('test_execution'))
 
      
 
      
         # Create a test suit that contains all the tests:
+
         # Create a test suite that contains all the tests:
 
         allSuite = suites['all']
 
         allSuite = suites['all']
 
         allSuite.addTests(
 
         allSuite.addTests(
 
             KratosUnittest.TestLoader().loadTestsFromTestCases([
 
             KratosUnittest.TestLoader().loadTestsFromTestCases([
                 TSprismTests
+
                 TDynamicBossakTests,
 +
                TDynamicNewmarkTests,
 +
                TSprismMembranePatchTests,
 +
                TSprismBendingPatchTests,
 +
                TShellQ4ThickBendingRollUpTests,
 +
                TShellQ4ThickDrillingRollUpTests,
 +
                TShellT3ThinBendingRollUpTests,
 +
                TShellT3ThinDrillingRollUpTests,
 +
                TShellT3IsotropicScordelisTests
 
             ])
 
             ])
 
         )
 
         )
Line 159: Line 185:
 
The following can be added as commentary to clarify what the script is doing:
 
The following can be added as commentary to clarify what the script is doing:
  
*The tests are added using the string 'test_nametest' from the imported class.
+
* The tests are added using the string 'test_nametest' from the imported class.
* Nightly should consider at least all the small test, in the same way all should consider all the nightly test.
+
* Nightly should consider at least all the small tests, in the same way all should consider all the nightly tests.
  
The following corresponds with the Script that content the test examples:
+
The following corresponds to the script that contains the test examples:
 +
   
 +
    import os
 
      
 
      
 
     # Import Kratos
 
     # Import Kratos
 
     from KratosMultiphysics import *
 
     from KratosMultiphysics import *
    from KratosMultiphysics.SolidMechanicsApplication import *
 
    from KratosMultiphysics.StructuralMechanicsApplication import *
 
 
      
 
      
 
     # Import KratosUnittest
 
     # Import KratosUnittest
 
     import KratosMultiphysics.KratosUnittest as KratosUnittest
 
     import KratosMultiphysics.KratosUnittest as KratosUnittest
 +
    import Kratos_Execute_Solid_Test as Execute_Test
 
      
 
      
    # Additional imports
 
    import constitutive_law_python_utility as constitutive_law_utils
 
 
      
 
      
     def GetFilePath(fileName):
+
     # This utility will control the execution scope in case we need to access files or we depend
        return os.path.dirname(__file__) + "/" + fileName
+
    # on specific relative locations of the files.
 
      
 
      
     class SprismTests(KratosUnittest.TestCase):
+
     class controlledExecutionScope:
 +
        def __init__(self, scope):
 +
            self.currentPath = os.getcwd()
 +
            self.scope = scope
 +
   
 +
        def __enter__(self):
 +
            os.chdir(self.scope)
 +
   
 +
        def __exit__(self, type, value, traceback):
 +
            os.chdir(self.currentPath)
 +
   
 +
   
 +
    class StructuralMechanichsTestFactory(KratosUnittest.TestCase):
 
      
 
      
 
         def setUp(self):
 
         def setUp(self):
             # Modelpart for the solid
+
             # Within this location context:
             model_part = ModelPart("StructuralPart")
+
             with controlledExecutionScope(os.path.dirname(os.path.realpath(__file__))):
            model_part.AddNodalSolutionStepVariable(ALPHA_EAS)
+
                # Initialize GiD  I/O
            model_part.AddNodalSolutionStepVariable(DISPLACEMENT)
+
                parameter_file = open(self.file_name + "_parameters.json", 'r')
            model_part.AddNodalSolutionStepVariable(REACTION)
+
                ProjectParameters = Parameters(parameter_file.read())
 
      
 
      
            # Initialize GiD  I/O
+
                # Creating the model part
            input_file_name = GetFilePath("SPRISM3D6N/patch_test")
+
                self.test = Execute_Test.Kratos_Execute_Test(ProjectParameters)
 
      
 
      
            # Reading the fluid part
+
        def test_execution(self):
            model_part_io_fluid = ModelPartIO(input_file_name)
+
             # Within this location context:
            model_part_io_fluid.ReadModelPart(model_part)
+
             with controlledExecutionScope(os.path.dirname(os.path.realpath(__file__))):
           
+
                self.test.Solve()
             # Find neighbours if required
+
             sprism_neighbour_search = SprismNeighbours(model_part)
+
            sprism_neighbour_search.Execute()
+
 
      
 
      
            # Setting up the buffer size
+
        def tearDown(self):
             model_part.SetBufferSize(3)
+
             pass
 
      
 
      
            # Adding dofs
 
            for node in model_part.Nodes:
 
            node.AddDof(DISPLACEMENT_X, REACTION_X)
 
            node.AddDof(DISPLACEMENT_Y, REACTION_Y)
 
            node.AddDof(DISPLACEMENT_Z, REACTION_Z)
 
           
 
            # Set the constitutive law
 
            constitutive_law = constitutive_law_utils.ConstitutiveLawUtility(model_part, 3);
 
            constitutive_law.Initialize();
 
 
      
 
      
            max_iters = 30
+
    class DynamicBossakTests(StructuralMechanichsTestFactory):
 +
        file_name = "dynamic_test/dynamic_bossak_test"
 
      
 
      
            linear_solver = SkylineLUFactorizationSolver()
+
    class DynamicNewmarkTests(StructuralMechanichsTestFactory):
 +
        file_name = "dynamic_test/dynamic_newmark_test"
 
      
 
      
            # Create solver
+
    class SprismMembranePatchTests(StructuralMechanichsTestFactory):
            builder_and_solver = ResidualBasedBuilderAndSolver(linear_solver)
+
        file_name = "sprism_test/patch_membrane_test"
            mechanical_scheme = ResidualBasedStaticScheme()
+
            mechanical_convergence_criterion = ResidualCriteria(1e-4, 1e-4)
+
 
      
 
      
            self.mechanical_solver = ResidualBasedNewtonRaphsonStrategy(
+
    class SprismBendingPatchTests(StructuralMechanichsTestFactory):
                model_part,
+
        file_name = "sprism_test/patch_bending_test"
                mechanical_scheme,
+
                linear_solver,
+
                mechanical_convergence_criterion,
+
                builder_and_solver,
+
                max_iters,
+
                False ,
+
                True,
+
                True)
+
 
      
 
      
            self.mechanical_solver.SetEchoLevel(0)
+
    class ShellQ4ThickBendingRollUpTests(StructuralMechanichsTestFactory):
            self.mechanical_solver.Initialize()
+
        file_name = "shell_test/Shell_Q4_Thick__BendingRollUp_test"
 
      
 
      
            self.model_part = model_part
+
    class ShellQ4ThickDrillingRollUpTests(StructuralMechanichsTestFactory):
 +
        file_name = "shell_test/Shell_Q4_Thick__DrillingRollUp_test"
 
      
 
      
        def test_MembranePacth(self):
+
    class ShellT3ThinBendingRollUpTests(StructuralMechanichsTestFactory):
            step = 0
+
        file_name = "shell_test/Shell_T3_Thin__BendingRollUp_test"
            time = 0.0
+
            Dt = 1.0
+
            final_time = 1.0
+
 
      
 
      
            # Add BC
+
    class ShellT3ThinDrillingRollUpTests(StructuralMechanichsTestFactory):
            for node in self.model_part.Nodes:
+
        file_name = "shell_test/Shell_T3_Thin__DrillingRollUp_test"
                if (node.X >2.40000e-01 -1.0e-5) | (node.Y > 1.20000e-01 -1.0e-5) | (node.X < 1.0e-5) | (node.Y < 1.0e-5):
+
 
                    node.Fix(DISPLACEMENT_X)
+
This file calls the main file, which is very similar to the one used by the GiD's problem type:
                    node.SetSolutionStepValue(
+
 
                        DISPLACEMENT_X,
+
    from __future__ import print_function, absolute_import, division #makes KratosMultiphysics backward compatible with python 2.6 and 2.7
                        0,
+
                        1.0e-7 * (node.X + node.Y / 2))
+
                    node.Fix(DISPLACEMENT_Y)
+
                    node.SetSolutionStepValue(
+
                        DISPLACEMENT_Y,
+
                        0,
+
                        1.0e-7 * (node.Y + node.X / 2))
+
 
      
 
      
            while(time <= final_time):
+
    from KratosMultiphysics import *
 +
    from KratosMultiphysics.SolidMechanicsApplication import *
 +
    from KratosMultiphysics.StructuralMechanicsApplication import *
 
      
 
      
                self.model_part.CloneTimeStep(time)
+
    import json
 
      
 
      
                time += Dt
+
    import process_factory
                step += 1
+
 
      
 
      
                self.mechanical_solver.Solve()
+
    class Kratos_Execute_Test:
 
      
 
      
            for node in self.model_part.Nodes:
+
    def __init__(self, ProjectParameters):
                value = node.GetSolutionStepValue(DISPLACEMENT_X,0)
+
        self.ProjectParameters = ProjectParameters
                self.assertAlmostEqual(value, 1.0e-7 * (node.X + node.Y / 2))
+
        self.main_model_part = ModelPart(self.ProjectParameters["problem_data"]["model_part_name"].GetString())
                value = node.GetSolutionStepValue(DISPLACEMENT_Y,0)
+
        self.main_model_part.ProcessInfo.SetValue(DOMAIN_SIZE, self.ProjectParameters["problem_data"]["domain_size"].GetInt())
                self.assertAlmostEqual(value, 1.0e-7 * (node.Y + node.X / 2))
+
       
 +
        self.Model = {self.ProjectParameters["problem_data"]["model_part_name"].GetString() : self.main_model_part}
 
      
 
      
         def test_BendingPacth(self):
+
         #construct the solver (main setting methods are located in the solver_module)
            step = 0
+
        solver_module = __import__(self.ProjectParameters["solver_settings"]["solver_type"].GetString())
            time = 0.0
+
        self.solver = solver_module.CreateSolver(self.main_model_part, self.ProjectParameters["solver_settings"])
            Dt = 1.0
+
            final_time = 1.0
+
 
      
 
      
 +
        #add variables (always before importing the model part) (it must be integrated in the ImportModelPart)
 +
        # if we integrate it in the model part we cannot use combined solvers
 +
        self.solver.AddVariables()
 +
        ### Temporal
 +
        #self.main_model_part.AddNodalSolutionStepVariable(ALPHA_EAS)
 +
   
 +
        #read model_part (note: the buffer_size is set here) (restart can be read here)
 +
        self.solver.ImportModelPart()
 +
   
 +
        #add dofs (always after importing the model part) (it must be integrated in the ImportModelPart)
 +
        # if we integrate it in the model part we cannot use combined solvers
 +
        self.solver.AddDofs()
 +
   
 +
        #build sub_model_parts or submeshes (rearrange parts for the application of custom processes)
 +
        ##get the list of the submodel part in the object Model
 +
        for i in range(self.ProjectParameters["solver_settings"]["processes_sub_model_part_list"].size()):
 +
        part_name = self.ProjectParameters["solver_settings"]["processes_sub_model_part_list"][i].GetString()
 +
        self.Model.update({part_name: self.main_model_part.GetSubModelPart(part_name)})
 +
   
 +
        #obtain the list of the processes to be applied
 +
        self.list_of_processes = process_factory.KratosProcessFactory(self.Model).ConstructListOfProcesses( self.ProjectParameters["constraints_process_list"] )
 +
        self.list_of_processes += process_factory.KratosProcessFactory(self.Model).ConstructListOfProcesses( self.ProjectParameters["loads_process_list"] )
 +
        self.list_of_processes += process_factory.KratosProcessFactory(self.Model).ConstructListOfProcesses( self.ProjectParameters["list_other_processes"] )
 +
   
 +
        for process in self.list_of_processes:
 +
        process.ExecuteInitialize()
 +
       
 +
        #### START SOLUTION ####
 +
        self.computing_model_part = self.solver.GetComputeModelPart()
 +
   
 +
        #### output settings start ####
 +
        self.problem_path = os.getcwd()
 +
        self.problem_name = self.ProjectParameters["problem_data"]["problem_name"].GetString()
 +
   
 +
        #### output settings start ####
 +
   
 +
        ## Sets strategies, builders, linear solvers, schemes and solving info, and fills the buffer
 +
        self.solver.Initialize()
 +
   
 +
    def Solve(self):
 +
        for process in self.list_of_processes:
 +
        process.ExecuteBeforeSolutionLoop()
 +
       
 +
        ## Stepping and time settings (get from process info or solving info)
 +
        #delta time
 +
        delta_time = self.ProjectParameters["problem_data"]["time_step"].GetDouble()
 +
        #start step
 +
        step      = 0
 +
        #start time
 +
        time      = self.ProjectParameters["problem_data"]["start_time"].GetDouble()
 +
        #end time
 +
        end_time  = self.ProjectParameters["problem_data"]["end_time"].GetDouble()
 +
       
 +
        # solving the problem (time integration)
 +
        while(time <= end_time):
 +
        time = time + delta_time
 +
        step = step + 1
 +
        self.main_model_part.CloneTimeStep(time)
 +
       
 +
        for process in self.list_of_processes:
 +
            process.ExecuteInitializeSolutionStep()
 +
           
 +
        self.solver.Solve()
 +
   
 +
        for process in self.list_of_processes:
 +
            process.ExecuteFinalizeSolutionStep()
 +
   
 +
        for process in self.list_of_processes:
 +
            process.ExecuteBeforeOutputStep()
 +
   
 +
        for process in self.list_of_processes:
 +
            process.ExecuteAfterOutputStep()
 +
   
 +
        for process in self.list_of_processes:
 +
            process.ExecuteFinalize()
 +
 +
The main is always the same, just the processes from the project parameters inside the json are changed, look for example the json that corresponds to the Bossak dynamic test:
 +
 +
    {
 +
        "problem_data"            : {
 +
            "problem_name"    : "dynamic_test",
 +
            "model_part_name" : "Structure",
 +
            "domain_size"    : 2,
 +
            "time_step"      : 0.001,
 +
            "start_time"      : 0.001,
 +
            "end_time"        : 1.00,
 +
            "echo_level"      : 0
 +
        },
 +
        "solver_settings"          : {
 +
            "solver_type"                        : "solid_mechanics_implicit_dynamic_solver",
 +
            "echo_level"                        : 0,
 +
            "solution_type"                      : "Dynamic",
 +
            "time_integration_method"            : "Implicit",
 +
            "scheme_type"                        : "Bossak",
 +
            "model_import_settings"              : {
 +
                "input_type"    : "mdpa",
 +
                "input_filename" : "dynamic_test/dynamic_test"
 +
            },
 +
            "line_search"                        : false,
 +
            "convergence_criterion"              : "Residual_criterion",
 +
            "displacement_relative_tolerance"    : 0.0001,
 +
            "displacement_absolute_tolerance"    : 1e-9,
 +
            "residual_relative_tolerance"        : 0.0001,
 +
            "residual_absolute_tolerance"        : 1e-9,
 +
            "max_iteration"                      : 10,
 +
            "linear_solver_settings"            : {
 +
                    "solver_type": "Super LU",
 +
                    "max_iteration": 500,
 +
                    "tolerance": 1e-9,
 +
                    "scaling": false,
 +
                    "verbosity": 1
 +
            },
 +
            "problem_domain_sub_model_part_list" : ["Parts_Parts_Auto1"],
 +
            "processes_sub_model_part_list"      : ["DISPLACEMENT_Displacement_Auto1"]
 +
        },
 +
        "constraints_process_list" : [
 +
        {
 +
            "implemented_in_file"  : "apply_displacement_process",
 +
            "implemented_in_module" : "KratosMultiphysics.SolidMechanicsApplication",
 +
            "help"                  : "",
 +
            "process_name"          : "ApplyDisplacementProcess",
 +
            "Parameters"            : {
 +
                "mesh_id"        : 0,
 +
                "model_part_name" : "DISPLACEMENT_Displacement_Auto1",
 +
                "is_fixed_x"      : false,
 +
                "is_fixed_y"      : true,
 +
                "is_fixed_z"      : true,
 +
                "variable_name"  : "DISPLACEMENT",
 +
                "direction"      : [0.1, 0.0, 0.0]
 +
            }
 +
        }
 +
        ],
 +
        "loads_process_list" : [],
 +
        "list_other_processes" :[
 +
        {
 +
            "implemented_in_file"  : "from_json_check_result_process",
 +
            "implemented_in_module" : "KratosMultiphysics",
 +
            "help"                  : "",
 +
            "process_name"          : "FromJsonCheckResultProcess",
 +
            "Parameters"            : {
 +
                "check_variables" : ["DISPLACEMENT_X","VELOCITY_X","ACCELERATION_X"],
 +
                "input_file_name" : "dynamic_test/dynamic_bossak_test_results.json",
 +
                "model_part_name"  : "DISPLACEMENT_Displacement_Auto1",
 +
                "time_frequency"  : 0.01
 +
            }
 +
        }
 +
        ],
 +
        "print_output_process" : [
 +
        {
 +
            "implemented_in_file"  : "json_output_process",
 +
            "implemented_in_module" : "KratosMultiphysics",
 +
            "help"                  : "",
 +
            "process_name"          : "JsonOutputProcess",
 +
            "Parameters"            : {
 +
                "output_variables" : ["DISPLACEMENT_X","VELOCITY_X","ACCELERATION_X"],
 +
                "output_file_name" : "dynamic_test/dynamic_bossak_test_results.json",
 +
                "model_part_name"  : "DISPLACEMENT_Displacement_Auto1",
 +
                "time_frequency"  : 0.01
 +
            }
 +
        }
 +
        ],
 +
        "check_json_results_process" : [
 +
        {
 +
            "implemented_in_file"  : "from_json_check_result_process",
 +
            "implemented_in_module" : "KratosMultiphysics",
 +
            "help"                  : "",
 +
            "process_name"          : "FromJsonCheckResultProcess",
 +
            "Parameters"            : {
 +
                "check_variables" : ["DISPLACEMENT_X","VELOCITY_X","ACCELERATION_X"],
 +
                "input_file_name" : "dynamic_test/dynamic_bossak_test_results.json",
 +
                "model_part_name"  : "DISPLACEMENT_Displacement_Auto1",
 +
                "time_frequency"  : 0.01
 +
            }
 +
        }
 +
        ],
 +
        "check_analytic_results_process" : [
 +
        {
 +
            "implemented_in_file"  : "from_analytic_check_result_process",
 +
            "implemented_in_module" : "KratosMultiphysics",
 +
            "help"                  : "",
 +
            "process_name"          : "FromAnalyticCheckResultProcess",
 +
            "Parameters"            : {
 +
                "variable_name"    : "DISPLACEMENT_X",
 +
                "mesh_id"          : 0,
 +
                "f(x,y,z,t)="      : "cos(10.0*t)",
 +
                "model_part_name"  : "DISPLACEMENT_Displacement_Auto1",
 +
                "time_frequency"    : 0.01
 +
            }
 +
        }
 +
        ],   
 +
        "apply_custom_function_process" : [],
 +
        "restart_options"          : {
 +
            "SaveRestart"      : false,
 +
            "RestartFrequency" : 0,
 +
            "LoadRestart"      : false,
 +
            "Restart_Step"    : 0
 +
        },
 +
        "constraints_data"        : {
 +
            "incremental_load"        : false,
 +
            "incremental_displacement" : false
 +
        }
 +
    }
 +
 +
For some specific problems, where the common processes are not enough to define your test, you can define a local process, like the following one, where the displacement is imposed in the boundary and after solving the displacements they are checked in all the domain with 'AssertAlmostEqual':
 +
   
 +
    from __future__ import print_function, absolute_import, division #makes KratosMultiphysics backward compatible with python 2.6 and 2.7
 +
    # Importing the Kratos Library
 +
    from KratosMultiphysics import *
 +
    from KratosMultiphysics.SolidMechanicsApplication import *
 +
    from KratosMultiphysics.StructuralMechanicsApplication import *
 +
   
 +
    CheckForPreviousImport()
 +
   
 +
    # Import KratosUnittest
 +
    import KratosMultiphysics.KratosUnittest as KratosUnittest
 +
   
 +
    def Factory(settings, Model):
 +
        if (type(settings) != Parameters):
 +
            raise Exception("Expected input shall be a Parameters object, encapsulating a json string")
 +
        return ApplyLocalProcess(Model, settings["Parameters"])
 +
   
 +
    class ApplyLocalProcess(Process, KratosUnittest.TestCase):
 +
   
 +
        def __init__(self,model_part,params):
 +
   
 +
            self.model_part = model_part[params["model_part_name"].GetString()]
 +
            self.params = params
 +
           
 +
        def ExecuteInitialize(self):
 +
            # Find neighbours if required
 +
            sprism_neighbour_search = SprismNeighbours(self.model_part)
 +
            sprism_neighbour_search.Execute()
 +
            #pass
 +
           
 +
        def ExecuteBeforeSolutionLoop(self):
 
             # Add BC
 
             # Add BC
 
             for node in self.model_part.Nodes:
 
             for node in self.model_part.Nodes:
 
                 if (node.X >2.40000e-01 -1.0e-5) | (node.Y > 1.20000e-01 -1.0e-5) | (node.X < 1.0e-5) | (node.Y < 1.0e-5):
 
                 if (node.X >2.40000e-01 -1.0e-5) | (node.Y > 1.20000e-01 -1.0e-5) | (node.X < 1.0e-5) | (node.Y < 1.0e-5):
 
                     node.Fix(DISPLACEMENT_X)
 
                     node.Fix(DISPLACEMENT_X)
                     node.SetSolutionStepValue(
+
                     node.SetSolutionStepValue(DISPLACEMENT_X, 0, -1.0e-7 * (node.Z - 0.0005) * (node.X + node.Y / 2))
                        DISPLACEMENT_X, 0,
+
                        -1.0e-7 * (node.Z - 0.0005) * (node.X + node.Y / 2))
+
 
                     node.Fix(DISPLACEMENT_Y)
 
                     node.Fix(DISPLACEMENT_Y)
                     node.SetSolutionStepValue(
+
                     node.SetSolutionStepValue(DISPLACEMENT_Y, 0, -1.0e-7 * (node.Z - 0.0005) * (node.Y + node.X / 2))
                        DISPLACEMENT_Y, 0,
+
                        -1.0e-7 * (node.Z - 0.0005) * (node.Y + node.X / 2))
+
 
                     node.Fix(DISPLACEMENT_Z)
 
                     node.Fix(DISPLACEMENT_Z)
                     node.SetSolutionStepValue(
+
                     node.SetSolutionStepValue(DISPLACEMENT_Z, 0, 0.5 * 1.0e-7 * (node.X ** 2 + node.X * node.Y + node.Y ** 2))
                        DISPLACEMENT_Z, 0,
+
                        0.5 * 1.0e-7 * (node.X ** 2 + node.X * node.Y + node.Y ** 2))
+
 
      
 
      
            while(time <= final_time):
+
       
   
+
        def ExecuteInitializeSolutionStep(self):
                self.model_part.CloneTimeStep(time)
+
            pass
   
+
                time += Dt
+
                step += 1
+
               
+
                self.mechanical_solver.Solve()
+
 
      
 
      
 +
        def ExecuteFinalizeSolutionStep(self):
 
             for node in self.model_part.Nodes:
 
             for node in self.model_part.Nodes:
 
                 value = node.GetSolutionStepValue(DISPLACEMENT_X,0)
 
                 value = node.GetSolutionStepValue(DISPLACEMENT_X,0)
                 self.assertAlmostEqual(
+
                 self.assertAlmostEqual(value, -1.0e-7 * (node.Z - 0.0005) * (node.X + node.Y / 2))
                    value,
+
                    -1.0e-7 * (node.Z - 0.0005) * (node.X + node.Y / 2))
+
 
                 value = node.GetSolutionStepValue(DISPLACEMENT_Y,0)
 
                 value = node.GetSolutionStepValue(DISPLACEMENT_Y,0)
                 self.assertAlmostEqual(
+
                 self.assertAlmostEqual(value,-1.0e-7 * (node.Z - 0.0005) * (node.Y + node.X / 2))
                    value,
+
                    -1.0e-7 * (node.Z - 0.0005) * (node.Y + node.X / 2))
+
 
                 value = node.GetSolutionStepValue(DISPLACEMENT_Z,0)
 
                 value = node.GetSolutionStepValue(DISPLACEMENT_Z,0)
                 self.assertAlmostEqual(value,
+
                 self.assertAlmostEqual(value, 0.5 * 1.0e-7 * (node.X ** 2 + node.X * node.Y + node.Y **2 ))
                    0.5 * 1.0e-7 * (node.X ** 2 + node.X * node.Y + node.Y **2 ))
+
               
 +
        def ExecuteBeforeOutputStep(self):
 +
            pass
 
      
 
      
         def tearDown(self):
+
         def ExecuteAfterOutputStep(self):
 +
            pass
 +
   
 +
        def ExecuteFinalize(self):
 
             pass
 
             pass
  
 
You can download this example from here[http://kratos-wiki.cimne.upc.edu/images/6/6a/Test_reference.tar.gz]  
 
You can download this example from here[http://kratos-wiki.cimne.upc.edu/images/6/6a/Test_reference.tar.gz]  
  
Some interesting comments to add to clarify:
+
Important additional comments:
  
* The solver should be created inside the setup
+
* The solver, constitutive laws, etc..., everything is created from the main file, that should be ALWAYS the same, and just change the processes, or even include your local processes, but never change the main.
* To be recognized all the test need to have the name "test_" at the beginning of the definition ("test_MembranePacth" and "test_BendingPacth" in this example).  
+
* You can create a json with data from previous simulations for future benchmarking using the process json_output_process.py and check it with from_json_check_result_process.py.
* The "tearDown" doesn't do anything, it just close files, clear memory...
+
* In order to be recognized automatically, all tests need to have the name "test_" at the beginning of the definition ("test_MembranePatch" and "test_BendingPatch" in this example).  
 +
* The "tearDown" doesn't do anything, it just closes files, clears memory...
 
* "imports" should be at the beginning of the file to avoid conflicts
 
* "imports" should be at the beginning of the file to avoid conflicts
* '''NO PRINTS''', is not necessary, the unittest already prints all the necessary information.
+
* '''NO PRINTS''': please don't print anything on screen by calling the print() function. It is not necessary, the unittest already prints all the necessary information.
  
 
= Some common commands in Unittest=
 
= Some common commands in Unittest=
Line 335: Line 570:
 
* assertEqual: Check if the two inputs are '''exactly equal''', if the inputs are doubles it is recommended to use the next assert, in order to avoid precision errors.
 
* assertEqual: Check if the two inputs are '''exactly equal''', if the inputs are doubles it is recommended to use the next assert, in order to avoid precision errors.
 
* assertAlmostEqual: Check if the solution is almost equal in the two inputs, with a certain precision (check the unittest python wiki for more details[https://docs.python.org/2/library/unittest.html#unittest.TestCase.assertAlmostEqual])
 
* assertAlmostEqual: Check if the solution is almost equal in the two inputs, with a certain precision (check the unittest python wiki for more details[https://docs.python.org/2/library/unittest.html#unittest.TestCase.assertAlmostEqual])
*
 

Latest revision as of 16:36, 7 September 2016

Kratos Multiphysics has a mechanism to automatically test your code called "KratosUnittest". If you are familiar with Python, this module is basically an extension of Unittest[1] and you can expect to find every functionality that exists there in KratosUnittest as well.

In order for your application to be robust, it is recommended to add unittests to it. Here we present some guidelines on how to do it.

Contents

How to run tests

Kratos unittest are executed using the "run_tests.py" script located in the "kratos/kratos/python_scripts" folder.

Usage is the following:

python run_tests.py

you can specify the following options:

-l,--level:        Select the suit. Values: "All", "Nightly", "Small"(default)
-v,--verbosity:    Select the verbosity level of the output. Values: 0, 1 (default) , 2
-a,--applications: List of applications to run separated by ":". For example "-a KratosKore:IncompressibleFluidApplication"
                   All applications compiled are run by default.

Basic structure

Tests are defined in a python script. To keep tests organized we recommend you to create the tests in your "application/tests/" directory.

Tests are organized in suites. Suites are a collection of tests that will be run together as a package. In Kratos, we define three basic suites:

  • All: which should contain all the tests
  • Nightly: which should contain a set of tests that could be executed in less than 10 min
  • Small: which should contain a set of tests that could be executed in less than 1 min


All applications should implement those packages as they can be automatically run, for example in the "nightly" runs. In order to add tests you should create at least a couple of files, one to define the tests and one to define the suites:

"application/tests/test_NAME_OF_OUR_APPLICATION.py"


for example:

kratos/applications/example_application/tests/test_example_application.py

General structure

This file will define the suites to run the tests. We define three different levels of suites in kratos:

  • All: which should contain all the tests
  • Nightly: which should contain a set of tests that could be executed in less than 10 min
  • Small: which should contain a set of tests that could be executed in less than 1 min


In order to add a test to some of these suites, one can do it as shown in this example:

from __future__ import print_function, absolute_import, division

# import Kratos
from KratosMultiphysics import *

# Import Kratos "wrapper" for unittests
import KratosMultiphysics.KratosUnittest as KratosUnittest

# Import the tests o test_classes to create the suites. For example
from test_my_app_example_tests_1 import TestCase1 as TestCase1
from test_my_app_example_tests_2 import TestCase2 as TestCase2

def AssembleTestSuites():
     Populates the test suites to run.
    
    Populates the test suites to run. At least, it should populate the suites:
    "small", "nightly" and "all"
    
    Return
    ------
    
    suites: A dictionary of suites
        The set of suites with its test_cases added.
    
    
    # Get the already defined suites
    suites = KratosUnittest.KratosSuites
    
    # Get the small suite and populate it with some tests from TestCase1 and TestCase2
    smallSuite = suites['small']
    smallSuite.addTest(TestCase1('test_example_small_boo_1'))
    smallSuite.addTest(TestCase2('test_example_small_foo_1'))
    
    # Get the small suite and populate it with some tests from TestCase1 and TestCase2
    nightSuite = suites['nightly']
    smallSuite.addTest(TestCase1('test_example_nightly_boo_1'))
    smallSuite.addTest(TestCase1('test_example_nightly_boo_2'))
    smallSuite.addTest(TestCase2('test_example_nightly_foo_1'))
    
    # Get the small suite and populate it with all tests from TestCase1 and TestCase2
    allSuite = suites['all']
    allSuite.addTests(
        KratosUnittest.TestLoader().loadTestsFromTestCases([
            TestCase1,
            TestCase2
        ])
    )
    
    # Return the suites
    return suites
    
# The main function executes the tests
if __name__ == '__main__':
    KratosUnittest.runTests(AssembleTestSuites())

Structure for examples

Examples are cases that test your code/application, but are more complex than the unitary tests, take longer and involve many functionalities. A typical example is a Finite Element calculation of few time steps and a few elements. In order to compute some examples (the examples that used to be located in the folder "test_examples") some additional considerations must to be taken into account. These considerations can be observed in the following example that is located in the StructuralMechanicsApplication/tests. In order to compute a dynamic test (Bossak and Newmark) and a patch test (with bending and membrane behaviours), the following files has been considered, first the main file, which will be the one launched.

   # import Kratos
   from KratosMultiphysics import *
   from KratosMultiphysics.ExternalSolversApplication import *
   from KratosMultiphysics.SolidMechanicsApplication import *
   from KratosMultiphysics.StructuralMechanicsApplication import *
   
   # Import Kratos "wrapper" for unittests
   import KratosMultiphysics.KratosUnittest as KratosUnittest
   
   # Import the tests o test_classes to create the suites
   ## SMALL TESTS
   from SmallTests import DynamicBossakTests as TDynamicBossakTests
   from SmallTests import DynamicNewmarkTests as TDynamicNewmarkTests
   from SmallTests import SprismMembranePatchTests as TSprismMembranePatchTests
   from SmallTests import SprismBendingPatchTests as TSprismBendingPatchTests
   from SmallTests import ShellQ4ThickBendingRollUpTests as TShellQ4ThickBendingRollUpTests
   from SmallTests import ShellQ4ThickDrillingRollUpTests as TShellQ4ThickDrillingRollUpTests
   from SmallTests import ShellT3ThinBendingRollUpTests as TShellT3ThinBendingRollUpTests
   from SmallTests import ShellT3ThinDrillingRollUpTests as TShellT3ThinDrillingRollUpTests
   
   ## NIGHTLY TESTS
   from NightlyTests import ShellT3IsotropicScordelisTests as TShellT3IsotropicScordelisTests
   
   def AssembleTestSuites():
        Populates the test suites to run.
   
       Populates the test suites to run. At least, it should populate the suites:
       "small", "nightly" and "all"
   
       Return
       ------
   
       suites: A dictionary of suites
           The set of suites with its test_cases added.
       
       suites = KratosUnittest.KratosSuites
   
       # Create a test suit with the selected tests (Small tests):
       smallSuite = suites['small']
       smallSuite.addTest(TDynamicBossakTests('test_execution'))
       smallSuite.addTest(TDynamicNewmarkTests('test_execution'))
       smallSuite.addTest(TSprismMembranePatchTests('test_execution'))
       smallSuite.addTest(TSprismBendingPatchTests('test_execution'))
       smallSuite.addTest(TShellQ4ThickBendingRollUpTests('test_execution'))
       smallSuite.addTest(TShellQ4ThickDrillingRollUpTests('test_execution'))
       smallSuite.addTest(TShellT3ThinBendingRollUpTests('test_execution'))
       smallSuite.addTest(TShellT3ThinDrillingRollUpTests('test_execution'))
   
       # Create a test suite with the selected tests plus all small tests
       nightSuite = suites['nightly']
       nightSuite.addTests(smallSuite)
       nightSuite.addTest(TShellT3IsotropicScordelisTests('test_execution'))
   
       # Create a test suite that contains all the tests:
       allSuite = suites['all']
       allSuite.addTests(
           KratosUnittest.TestLoader().loadTestsFromTestCases([
               TDynamicBossakTests,
               TDynamicNewmarkTests,
               TSprismMembranePatchTests,
               TSprismBendingPatchTests,
               TShellQ4ThickBendingRollUpTests,
               TShellQ4ThickDrillingRollUpTests,
               TShellT3ThinBendingRollUpTests,
               TShellT3ThinDrillingRollUpTests,
               TShellT3IsotropicScordelisTests
           ])
       )
   
       return suites
   
   if __name__ == '__main__':
       KratosUnittest.runTests(AssambleTestSuites())

The following can be added as commentary to clarify what the script is doing:

  • The tests are added using the string 'test_nametest' from the imported class.
  • Nightly should consider at least all the small tests, in the same way all should consider all the nightly tests.

The following corresponds to the script that contains the test examples:

   import os
   
   # Import Kratos
   from KratosMultiphysics import *
   
   # Import KratosUnittest
   import KratosMultiphysics.KratosUnittest as KratosUnittest
   import Kratos_Execute_Solid_Test as Execute_Test
   
   
   # This utility will control the execution scope in case we need to access files or we depend
   # on specific relative locations of the files.
   
   class controlledExecutionScope:
       def __init__(self, scope):
           self.currentPath = os.getcwd()
           self.scope = scope
   
       def __enter__(self):
           os.chdir(self.scope)
   
       def __exit__(self, type, value, traceback):
           os.chdir(self.currentPath)
   
   
   class StructuralMechanichsTestFactory(KratosUnittest.TestCase):
   
       def setUp(self):
           # Within this location context:
           with controlledExecutionScope(os.path.dirname(os.path.realpath(__file__))):
               # Initialize GiD  I/O
               parameter_file = open(self.file_name + "_parameters.json", 'r')
               ProjectParameters = Parameters(parameter_file.read())
   
               # Creating the model part
               self.test = Execute_Test.Kratos_Execute_Test(ProjectParameters)
   
       def test_execution(self):
           # Within this location context:
           with controlledExecutionScope(os.path.dirname(os.path.realpath(__file__))):
               self.test.Solve()
   
       def tearDown(self):
           pass
   
   
   class DynamicBossakTests(StructuralMechanichsTestFactory):
       file_name = "dynamic_test/dynamic_bossak_test"
   
   class DynamicNewmarkTests(StructuralMechanichsTestFactory):
       file_name = "dynamic_test/dynamic_newmark_test"
   
   class SprismMembranePatchTests(StructuralMechanichsTestFactory):
       file_name = "sprism_test/patch_membrane_test"
   
   class SprismBendingPatchTests(StructuralMechanichsTestFactory):
       file_name = "sprism_test/patch_bending_test"
   
   class ShellQ4ThickBendingRollUpTests(StructuralMechanichsTestFactory):
       file_name = "shell_test/Shell_Q4_Thick__BendingRollUp_test"
   
   class ShellQ4ThickDrillingRollUpTests(StructuralMechanichsTestFactory):
       file_name = "shell_test/Shell_Q4_Thick__DrillingRollUp_test"
   
   class ShellT3ThinBendingRollUpTests(StructuralMechanichsTestFactory):
       file_name = "shell_test/Shell_T3_Thin__BendingRollUp_test"
   
   class ShellT3ThinDrillingRollUpTests(StructuralMechanichsTestFactory):
       file_name = "shell_test/Shell_T3_Thin__DrillingRollUp_test"

This file calls the main file, which is very similar to the one used by the GiD's problem type:

   from __future__ import print_function, absolute_import, division #makes KratosMultiphysics backward compatible with python 2.6 and 2.7
   
   from KratosMultiphysics import *
   from KratosMultiphysics.SolidMechanicsApplication import *
   from KratosMultiphysics.StructuralMechanicsApplication import *
   
   import json
   
   import process_factory
   
   class Kratos_Execute_Test:
   
   def __init__(self, ProjectParameters):
       self.ProjectParameters = ProjectParameters
       self.main_model_part = ModelPart(self.ProjectParameters["problem_data"]["model_part_name"].GetString())
       self.main_model_part.ProcessInfo.SetValue(DOMAIN_SIZE, self.ProjectParameters["problem_data"]["domain_size"].GetInt())
       
       self.Model = {self.ProjectParameters["problem_data"]["model_part_name"].GetString() : self.main_model_part}
   
       #construct the solver (main setting methods are located in the solver_module)
       solver_module = __import__(self.ProjectParameters["solver_settings"]["solver_type"].GetString())
       self.solver = solver_module.CreateSolver(self.main_model_part, self.ProjectParameters["solver_settings"])
   
       #add variables (always before importing the model part) (it must be integrated in the ImportModelPart)
       # if we integrate it in the model part we cannot use combined solvers
       self.solver.AddVariables()
       ### Temporal 
       #self.main_model_part.AddNodalSolutionStepVariable(ALPHA_EAS)
   
       #read model_part (note: the buffer_size is set here) (restart can be read here)
       self.solver.ImportModelPart()
   
       #add dofs (always after importing the model part) (it must be integrated in the ImportModelPart)
       # if we integrate it in the model part we cannot use combined solvers
       self.solver.AddDofs()
   
       #build sub_model_parts or submeshes (rearrange parts for the application of custom processes)
       ##get the list of the submodel part in the object Model
       for i in range(self.ProjectParameters["solver_settings"]["processes_sub_model_part_list"].size()):
       part_name = self.ProjectParameters["solver_settings"]["processes_sub_model_part_list"][i].GetString()
       self.Model.update({part_name: self.main_model_part.GetSubModelPart(part_name)})
   
       #obtain the list of the processes to be applied
       self.list_of_processes = process_factory.KratosProcessFactory(self.Model).ConstructListOfProcesses( self.ProjectParameters["constraints_process_list"] )
       self.list_of_processes += process_factory.KratosProcessFactory(self.Model).ConstructListOfProcesses( self.ProjectParameters["loads_process_list"] )
       self.list_of_processes += process_factory.KratosProcessFactory(self.Model).ConstructListOfProcesses( self.ProjectParameters["list_other_processes"] )
   
       for process in self.list_of_processes:
       process.ExecuteInitialize()
       
       #### START SOLUTION ####
       self.computing_model_part = self.solver.GetComputeModelPart()
   
       #### output settings start ####
       self.problem_path = os.getcwd()
       self.problem_name = self.ProjectParameters["problem_data"]["problem_name"].GetString()
   
       #### output settings start ####
   
       ## Sets strategies, builders, linear solvers, schemes and solving info, and fills the buffer
       self.solver.Initialize()
   
   def Solve(self):
       for process in self.list_of_processes:
       process.ExecuteBeforeSolutionLoop()
       
       ## Stepping and time settings (get from process info or solving info)
       #delta time
       delta_time = self.ProjectParameters["problem_data"]["time_step"].GetDouble()
       #start step
       step       = 0
       #start time
       time       = self.ProjectParameters["problem_data"]["start_time"].GetDouble()
       #end time
       end_time   = self.ProjectParameters["problem_data"]["end_time"].GetDouble()
       
       # solving the problem (time integration)
       while(time <= end_time):
       time = time + delta_time
       step = step + 1
       self.main_model_part.CloneTimeStep(time)
       
       for process in self.list_of_processes:
           process.ExecuteInitializeSolutionStep()
           
       self.solver.Solve()
   
       for process in self.list_of_processes:
           process.ExecuteFinalizeSolutionStep()
   
       for process in self.list_of_processes:
           process.ExecuteBeforeOutputStep()
   
       for process in self.list_of_processes:
           process.ExecuteAfterOutputStep()
   
       for process in self.list_of_processes:
           process.ExecuteFinalize()

The main is always the same, just the processes from the project parameters inside the json are changed, look for example the json that corresponds to the Bossak dynamic test:

   {
       "problem_data"             : {
           "problem_name"    : "dynamic_test",
           "model_part_name" : "Structure",
           "domain_size"     : 2,
           "time_step"       : 0.001,
           "start_time"      : 0.001,
           "end_time"        : 1.00,
           "echo_level"      : 0
       },
       "solver_settings"          : {
           "solver_type"                        : "solid_mechanics_implicit_dynamic_solver",
           "echo_level"                         : 0,
           "solution_type"                      : "Dynamic",
           "time_integration_method"            : "Implicit",
           "scheme_type"                        : "Bossak",
           "model_import_settings"              : {
               "input_type"     : "mdpa",
               "input_filename" : "dynamic_test/dynamic_test"
           },
           "line_search"                        : false,
           "convergence_criterion"              : "Residual_criterion",
           "displacement_relative_tolerance"    : 0.0001,
           "displacement_absolute_tolerance"    : 1e-9,
           "residual_relative_tolerance"        : 0.0001,
           "residual_absolute_tolerance"        : 1e-9,
           "max_iteration"                      : 10,
           "linear_solver_settings"             : {
                   "solver_type": "Super LU",
                   "max_iteration": 500,
                   "tolerance": 1e-9,
                   "scaling": false,
                   "verbosity": 1
           },
           "problem_domain_sub_model_part_list" : ["Parts_Parts_Auto1"],
           "processes_sub_model_part_list"      : ["DISPLACEMENT_Displacement_Auto1"]
       },
       "constraints_process_list" : [
       {
           "implemented_in_file"   : "apply_displacement_process",
           "implemented_in_module" : "KratosMultiphysics.SolidMechanicsApplication",
           "help"                  : "",
           "process_name"          : "ApplyDisplacementProcess",
           "Parameters"            : {
               "mesh_id"         : 0,
               "model_part_name" : "DISPLACEMENT_Displacement_Auto1",
               "is_fixed_x"      : false,
               "is_fixed_y"      : true,
               "is_fixed_z"      : true,
               "variable_name"   : "DISPLACEMENT",
               "direction"       : [0.1, 0.0, 0.0]
           }
       }
       ],
       "loads_process_list" : [],
       "list_other_processes" :[
       {
           "implemented_in_file"   : "from_json_check_result_process",
           "implemented_in_module" : "KratosMultiphysics",
           "help"                  : "",
           "process_name"          : "FromJsonCheckResultProcess",
           "Parameters"            : {
               "check_variables" : ["DISPLACEMENT_X","VELOCITY_X","ACCELERATION_X"],
               "input_file_name" : "dynamic_test/dynamic_bossak_test_results.json",
               "model_part_name"  : "DISPLACEMENT_Displacement_Auto1",
               "time_frequency"   : 0.01
           }
       }
       ],
       "print_output_process" : [
       {
           "implemented_in_file"   : "json_output_process",
           "implemented_in_module" : "KratosMultiphysics",
           "help"                  : "",
           "process_name"          : "JsonOutputProcess",
           "Parameters"            : {
               "output_variables" : ["DISPLACEMENT_X","VELOCITY_X","ACCELERATION_X"],
               "output_file_name" : "dynamic_test/dynamic_bossak_test_results.json",
               "model_part_name"  : "DISPLACEMENT_Displacement_Auto1",
               "time_frequency"   : 0.01
           }
       }
       ],
       "check_json_results_process" : [
       {
           "implemented_in_file"   : "from_json_check_result_process",
           "implemented_in_module" : "KratosMultiphysics",
           "help"                  : "",
           "process_name"          : "FromJsonCheckResultProcess",
           "Parameters"            : {
               "check_variables" : ["DISPLACEMENT_X","VELOCITY_X","ACCELERATION_X"],
               "input_file_name" : "dynamic_test/dynamic_bossak_test_results.json",
               "model_part_name"  : "DISPLACEMENT_Displacement_Auto1",
               "time_frequency"   : 0.01
           }
       }
       ],
       "check_analytic_results_process" : [
       {
           "implemented_in_file"   : "from_analytic_check_result_process",
           "implemented_in_module" : "KratosMultiphysics",
           "help"                  : "",
           "process_name"          : "FromAnalyticCheckResultProcess",
           "Parameters"            : {
               "variable_name"     : "DISPLACEMENT_X",
               "mesh_id"           : 0,
               "f(x,y,z,t)="       : "cos(10.0*t)",
               "model_part_name"   : "DISPLACEMENT_Displacement_Auto1",
               "time_frequency"    : 0.01
           }
       }
       ],    
       "apply_custom_function_process" : [],
       "restart_options"          : {
           "SaveRestart"      : false,
           "RestartFrequency" : 0,
           "LoadRestart"      : false,
           "Restart_Step"     : 0
       },
       "constraints_data"         : {
           "incremental_load"         : false,
           "incremental_displacement" : false
       }
   }

For some specific problems, where the common processes are not enough to define your test, you can define a local process, like the following one, where the displacement is imposed in the boundary and after solving the displacements they are checked in all the domain with 'AssertAlmostEqual':

   from __future__ import print_function, absolute_import, division #makes KratosMultiphysics backward compatible with python 2.6 and 2.7
   # Importing the Kratos Library
   from KratosMultiphysics import *
   from KratosMultiphysics.SolidMechanicsApplication import *
   from KratosMultiphysics.StructuralMechanicsApplication import *
   
   CheckForPreviousImport()
   
   # Import KratosUnittest
   import KratosMultiphysics.KratosUnittest as KratosUnittest
   
   def Factory(settings, Model):
       if (type(settings) != Parameters):
           raise Exception("Expected input shall be a Parameters object, encapsulating a json string")
       return ApplyLocalProcess(Model, settings["Parameters"])
   
   class ApplyLocalProcess(Process, KratosUnittest.TestCase):
   
       def __init__(self,model_part,params):
   
           self.model_part = model_part[params["model_part_name"].GetString()]
           self.params = params
           
       def ExecuteInitialize(self):
           # Find neighbours if required
           sprism_neighbour_search = SprismNeighbours(self.model_part)
           sprism_neighbour_search.Execute()
           #pass
           
       def ExecuteBeforeSolutionLoop(self):
           # Add BC
           for node in self.model_part.Nodes:
               if (node.X >2.40000e-01 -1.0e-5) | (node.Y > 1.20000e-01 -1.0e-5) | (node.X < 1.0e-5) | (node.Y < 1.0e-5):
                   node.Fix(DISPLACEMENT_X)
                   node.SetSolutionStepValue(DISPLACEMENT_X, 0, -1.0e-7 * (node.Z - 0.0005) * (node.X + node.Y / 2))
                   node.Fix(DISPLACEMENT_Y)
                   node.SetSolutionStepValue(DISPLACEMENT_Y, 0, -1.0e-7 * (node.Z - 0.0005) * (node.Y + node.X / 2))
                   node.Fix(DISPLACEMENT_Z)
                   node.SetSolutionStepValue(DISPLACEMENT_Z, 0, 0.5 * 1.0e-7 * (node.X ** 2 + node.X * node.Y + node.Y ** 2))
   
       
       def ExecuteInitializeSolutionStep(self):
           pass
   
       def ExecuteFinalizeSolutionStep(self):
           for node in self.model_part.Nodes:
               value = node.GetSolutionStepValue(DISPLACEMENT_X,0)
               self.assertAlmostEqual(value, -1.0e-7 * (node.Z - 0.0005) * (node.X + node.Y / 2))
               value = node.GetSolutionStepValue(DISPLACEMENT_Y,0)
               self.assertAlmostEqual(value,-1.0e-7 * (node.Z - 0.0005) * (node.Y + node.X / 2))
               value = node.GetSolutionStepValue(DISPLACEMENT_Z,0)
               self.assertAlmostEqual(value, 0.5 * 1.0e-7 * (node.X ** 2 + node.X * node.Y + node.Y **2 ))
               
       def ExecuteBeforeOutputStep(self):
           pass
   
       def ExecuteAfterOutputStep(self):
           pass
   
       def ExecuteFinalize(self):
           pass

You can download this example from here[2]

Important additional comments:

  • The solver, constitutive laws, etc..., everything is created from the main file, that should be ALWAYS the same, and just change the processes, or even include your local processes, but never change the main.
  • You can create a json with data from previous simulations for future benchmarking using the process json_output_process.py and check it with from_json_check_result_process.py.
  • In order to be recognized automatically, all tests need to have the name "test_" at the beginning of the definition ("test_MembranePatch" and "test_BendingPatch" in this example).
  • The "tearDown" doesn't do anything, it just closes files, clears memory...
  • "imports" should be at the beginning of the file to avoid conflicts
  • NO PRINTS: please don't print anything on screen by calling the print() function. It is not necessary, the unittest already prints all the necessary information.

Some common commands in Unittest

The link[3] contents the commands that can be used with the original Unittest from Python, not all the commands from the python Unittest are available in Kratos right now, but they will be available in a near future. The following are the most common and essential commands in Kratos Unittest:

  • assertTrue/assertFalse:They check if the two inputs are true or false respectively.
  • assertEqual: Check if the two inputs are exactly equal, if the inputs are doubles it is recommended to use the next assert, in order to avoid precision errors.
  • assertAlmostEqual: Check if the solution is almost equal in the two inputs, with a certain precision (check the unittest python wiki for more details[4])
Personal tools
Categories