Skip to content

Commit

Permalink
Merge pull request #11355 from KratosMultiphysics/core/multistage-pro…
Browse files Browse the repository at this point in the history
…ject

[Core] Project-based multistage
  • Loading branch information
rubenzorrilla authored Jul 11, 2023
2 parents 78d3be7 + dd213d7 commit 27b1981
Show file tree
Hide file tree
Showing 17 changed files with 801 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@
python_processes_to_be_registered = [
"apply_boussinesq_force_process.ApplyBoussinesqForceProcess",
"apply_inlet_process.ApplyInletProcess"
]
]

python_stages_to_be_registered = [
"fluid_dynamics_analysis.FluidDynamicsAnalysis"
]

python_orchestrators_to_be_registered = []
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import KratosMultiphysics as Kratos
from KratosMultiphysics.analysis_stage import AnalysisStage
from KratosMultiphysics.multistage_analysis import MultistageAnalysis
from KratosMultiphysics.project import Project
from KratosMultiphysics.orchestrators.orchestrator import Orchestrator
from KratosMultiphysics.OptimizationApplication.execution_policies.execution_policy import ExecutionPolicy
from KratosMultiphysics.OptimizationApplication.utilities.helper_utilities import GetClassModuleFromKratos

Expand All @@ -22,21 +23,24 @@ def __init__(self, name: str, model: Kratos.Model, parameters: Kratos.Parameters
self.model = model

default_settings = Kratos.Parameters("""{
"analysis_module" : "KratosMultiphysics",
"analysis_type" : "",
"analysis_settings": {}
"analysis_module" : "KratosMultiphysics",
"analysis_type" : "",
"analysis_model_part_name": "",
"analysis_settings" : {}
}""")

parameters.ValidateAndAssignDefaults(default_settings)

self.analysis_module = parameters["analysis_module"].GetString()
self.analysis_type = parameters["analysis_type"].GetString()
self.analysis_settings = parameters["analysis_settings"]
self.analysis_model_part_name = parameters["analysis_model_part_name"].GetString()

if self.analysis_module == "KratosMultiphysics":
self.analysis_module = GetClassModuleFromKratos(self.analysis_type)
self.analysis_full_module, self.analysis_type = GetClassModuleFromKratos(self.analysis_type)
else:
self.analysis_full_module = f"{self.analysis_module}.{Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(self.analysis_type)}"

self.analysis_full_module = f"{self.analysis_module}.{Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(self.analysis_type)}"

def Initialize(self) -> None:
pass
Expand All @@ -48,11 +52,27 @@ def Finalize(self) -> None:
pass

def Execute(self):
self.current_analysis: Union[AnalysisStage, MultistageAnalysis] = getattr(import_module(self.analysis_full_module), self.analysis_type)(self.model, self.analysis_settings.Clone())
analysis_type = getattr(import_module(self.analysis_full_module), self.analysis_type)
if AnalysisStage in analysis_type.mro():
# the analysis type is derrived from AnalysisStage
self.current_analysis: AnalysisStage = getattr(import_module(self.analysis_full_module), self.analysis_type)(self.model, self.analysis_settings.Clone())
elif Orchestrator in analysis_type.mro():
# the analysis type is derrive from the Orchestrator
project = Project(self.analysis_settings.Clone())
self.current_analysis: Orchestrator = getattr(import_module(self.analysis_full_module), self.analysis_type)(project)

self.current_analysis.Run()

def GetAnalysisModelPart(self):
if self.current_analysis is not None:
return self.current_analysis._GetSolver().GetComputingModelPart()
if isinstance(self.current_analysis, AnalysisStage):
if self.analysis_model_part_name == "" or self.analysis_model_part_name == self.current_analysis._GetSolver().GetComputingModelPart().FullName():
return self.current_analysis._GetSolver().GetComputingModelPart()
else:
raise RuntimeError(f"The specified analysis model part name mismatch [ specified analysis model part name = {self.analysis_model_part_name}, used analysis model part name = {self.current_analysis._GetSolver().GetComputingModelPart().FullName()} ].")
elif isinstance(self.current_analysis, Orchestrator):
return self.current_analysis.GetProject().GetModel()[self.analysis_model_part_name]
else:
raise RuntimeError(f"Unsupported analysis type = {self.current_analysis}.")
else:
raise RuntimeError("The analysis is not run yet.")
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ def __init__(self, name: str, model: Kratos.Model, parameters: Kratos.Parameters
analysis_settings = parameters["analysis_settings"]

if analysis_module == "KratosMultiphysics":
analysis_module = GetClassModuleFromKratos(analysis_type)
analysis_full_module, analysis_type = GetClassModuleFromKratos(analysis_type)
else:
analysis_full_module = f"{analysis_module}.{Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(analysis_type)}"

self.model_parts: 'list[Kratos.ModelPart]' = []
analysis_full_module = f"{analysis_module}.{Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(analysis_type)}"
self.analysis: AnalysisStage = getattr(import_module(analysis_full_module), analysis_type)(self.model, analysis_settings.Clone())

analysis_output_settings = self.parameters["analysis_output_settings"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from KratosMultiphysics.analysis_stage import AnalysisStage
from KratosMultiphysics.OptimizationApplication.execution_policies.execution_policy import ExecutionPolicy
from KratosMultiphysics.OptimizationApplication.utilities.helper_utilities import GetClassModuleFromKratos
from KratosMultiphysics.OptimizationApplication.utilities.optimization_problem import OptimizationProblem

def Factory(model: Kratos.Model, parameters: Kratos.Parameters, _) -> ExecutionPolicy:
if not parameters.Has("name"):
Expand Down Expand Up @@ -33,10 +32,11 @@ def __init__(self, name: str, model: Kratos.Model, parameters: Kratos.Parameters
analysis_settings = parameters["analysis_settings"]

if analysis_module == "KratosMultiphysics":
analysis_module = GetClassModuleFromKratos(analysis_type)
analysis_full_module, analysis_type = GetClassModuleFromKratos(analysis_type)
else:
analysis_full_module = f"{analysis_module}.{Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(analysis_type)}"

self.model_parts = []
analysis_full_module = f"{analysis_module}.{Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(analysis_type)}"
self.analysis: AnalysisStage = getattr(import_module(analysis_full_module), analysis_type)(self.model, analysis_settings.Clone())

def GetAnalysisModelPart(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,35 @@
from KratosMultiphysics.kratos_utilities import GetKratosMultiphysicsPath
from KratosMultiphysics.OptimizationApplication.utilities.union_utilities import ContainerExpressionTypes

def GetClassModuleFromKratos(class_name: str) -> str:
snake_case_class_name = Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(class_name)
def GetClassModuleFromKratos(full_class_name: str) -> str:
sub_module_paths = full_class_name.split(".")

if not sub_module_paths:
raise RuntimeError("Empty class names are not allowed.")

relative_sub_module = Kratos.StringUtilities.ConvertCamelCaseToSnakeCase(sub_module_paths[-1])
if len(sub_module_paths) > 1:
relative_sub_module = ".".join(sub_module_paths[:-1]) + f".{relative_sub_module}"

relative_sub_module_path = relative_sub_module.replace(".", "/")
kratos_path = GetKratosMultiphysicsPath()

# check whether it is in Kratos core
if Path(f"{kratos_path}/{snake_case_class_name}.py").is_file():
return "KratosMultiphysics"
if Path(f"{kratos_path}/{relative_sub_module_path}.py").is_file():
return f"KratosMultiphysics.{relative_sub_module}", sub_module_paths[-1]

# now check if it is found in any of the compiled applications
list_of_available_appliacations = GetListOfAvailableApplications()

module_application = ""
for application in list_of_available_appliacations:
if Path(f"{kratos_path}/{application}/{snake_case_class_name}.py").is_file():
module_application = f"KratosMultiphysics.{application}"
if Path(f"{kratos_path}/{application}/{relative_sub_module_path}.py").is_file():
module_application = f"KratosMultiphysics.{application}.{relative_sub_module}"

if module_application != "":
return module_application
return module_application, sub_module_paths[-1]
else:
raise RuntimeError(f"{class_name} is not found in KratosMultiphysics core or any of the available application root directories in Kratos. Available applications:\n\t" + "\n\t".join(list_of_available_appliacations))
raise RuntimeError(f"{full_class_name} is not found in KratosMultiphysics core or any of the available application directories in Kratos. Available applications:\n\t" + "\n\t".join(list_of_available_appliacations))

def CallOnAll(list_of_objects: 'list[Any]', method: Any, *args, **kwargs):
for obj in list_of_objects:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ class TestExecutionPolicies(kratos_unittest.TestCase):
def test_IndependentAnalysisExecutionPolicy(self):
model = Kratos.Model()
parameters = Kratos.Parameters("""{
"name" : "test",
"type": "independent_analysis_execution_policy",
"name" : "test",
"type" : "independent_analysis_execution_policy",
"settings": {
"analysis_type" : "MultistageAnalysis",
"analysis_type" : "orchestrators.SequentialOrchestrator",
"analysis_settings": {
"stages": [],
"execution_list":[]
"orchestrator": {
"settings": {
"stage_checkpoints": false
}
},
"stages":[]
}
}
}""")
Expand Down
9 changes: 9 additions & 0 deletions kratos/python_interface/python_registry_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,13 @@
"assign_vector_variable_to_elements_process.AssignVectorVariableToElementsProcess",
"assign_vector_variable_to_entities_process.AssignVectorVariableToEntitiesProcess",
"assign_vector_variable_to_nodes_process.AssignVectorVariableToNodesProcess"
]

python_stages_to_be_registered = [
"analysis_stage.AnalysisStage"
]

python_orchestrators_to_be_registered = [
"orchestrators.orchestrator.Orchestrator",
"orchestrators.sequential_orchestrator.SequentialOrchestrator"
]
10 changes: 10 additions & 0 deletions kratos/python_interface/python_registry_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ def RegisterAll(PythonModuleName, PythonRegistryListModule):
RegisterModelersList(PythonModuleName, PythonRegistryListModule)
RegisterOperationsList(PythonModuleName, PythonRegistryListModule)
RegisterProcessesList(PythonModuleName, PythonRegistryListModule)
RegisterStagesList(PythonModuleName, PythonRegistryListModule)
RegisterOrchestratorsList(PythonModuleName, PythonRegistryListModule)

def RegisterModelersList(PythonModuleName, PythonRegistryListModule):
__CheckRegistryListIsInModule(PythonRegistryListModule, "modelers")
Expand All @@ -17,6 +19,14 @@ def RegisterProcessesList(PythonModuleName, PythonRegistryListModule):
__CheckRegistryListIsInModule(PythonRegistryListModule, "processes")
__RegisterItemList(PythonModuleName, PythonRegistryListModule.python_processes_to_be_registered, "Processes")

def RegisterStagesList(PythonModuleName, PythonRegistryListModule):
__CheckRegistryListIsInModule(PythonRegistryListModule, "stages")
__RegisterItemList(PythonModuleName, PythonRegistryListModule.python_stages_to_be_registered, "Stages")

def RegisterOrchestratorsList(PythonModuleName, PythonRegistryListModule):
__CheckRegistryListIsInModule(PythonRegistryListModule, "orchestrators")
__RegisterItemList(PythonModuleName, PythonRegistryListModule.python_orchestrators_to_be_registered, "Orchestrators")

def __CheckRegistryListIsInModule(PythonRegistryListModule, ListKeyword):
list_variable_name = f"python_{ListKeyword}_to_be_registered"
if not list_variable_name in dir(PythonRegistryListModule):
Expand Down
28 changes: 22 additions & 6 deletions kratos/python_scripts/analysis_stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ def Finalize(self):

def GetFinalData(self):
"""Returns the final data dictionary.
The main purpose of this function is to retrieve any data (in a key-value format) from outside the stage.
Note that even though it can be called at any point, it is intended to be called at the end of the stage run.
Note that even though it can be called at any point, it is intended to be called at the end of the stage run.
"""

return {}

def InitializeSolutionStep(self):
Expand Down Expand Up @@ -211,6 +211,22 @@ def ChangeMaterialProperties(self):
"""this function is where the user could change material parameters as a part of the solution step """
pass

def Save(self, serializer: KratosMultiphysics.StreamSerializer) -> None:
"""Serializes current analysis stage instance
This method is intended to make the class pure Python (pickable). This means serialize all the Kratos objects,
that is to say all the objects coming from Pybind, with the provided serializer. After the serialization, it is
required to assign None value to all the objects in order to make the class pickable.
"""
pass

def Load(self, serializer: KratosMultiphysics.StreamSerializer) -> None:
"""Loads current analysis stage instance
From the given serializer, this method restores current class from a pure Python status (pickable) to the one in the serializer.
"""
pass

def _GetSolver(self):
if not hasattr(self, '_solver'):
self._solver = self._CreateSolver()
Expand All @@ -220,13 +236,13 @@ def _CreateSolver(self):
"""Create the solver
"""
raise Exception("Creation of the solver must be implemented in the derived class.")

def _AdvanceTime(self):
""" Computes the following time
""" Computes the following time
The default method simply calls the solver
"""
return self._GetSolver().AdvanceInTime(self.time)

### Modelers
def _ModelersSetupGeometryModel(self):
# Import or generate geometry models from external input.
Expand Down
Loading

0 comments on commit 27b1981

Please sign in to comment.