KratosSystemIdentificationApplication 10.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. KratosMultiphysics/.libs/KratosSystemIdentificationApplication.so +0 -0
  2. KratosMultiphysics/.libs/libKratosSystemIdentificationCore.dylib +0 -0
  3. KratosMultiphysics/SystemIdentificationApplication/__init__.py +13 -0
  4. KratosMultiphysics/SystemIdentificationApplication/controls/data_values_control.py +210 -0
  5. KratosMultiphysics/SystemIdentificationApplication/controls/material_properties_control.py +212 -0
  6. KratosMultiphysics/SystemIdentificationApplication/convergence_criteria/sensor_error.py +93 -0
  7. KratosMultiphysics/SystemIdentificationApplication/processes/sensor_output_process.py +83 -0
  8. KratosMultiphysics/SystemIdentificationApplication/python_registry_lists.py +9 -0
  9. KratosMultiphysics/SystemIdentificationApplication/responses/damage_detection_response.py +190 -0
  10. KratosMultiphysics/SystemIdentificationApplication/responses/pressure_detection_response.py +20 -0
  11. KratosMultiphysics/SystemIdentificationApplication/responses/temperature_detection_response.py +20 -0
  12. KratosMultiphysics/SystemIdentificationApplication/sensor_generator_analysis.py +130 -0
  13. KratosMultiphysics/SystemIdentificationApplication/sensor_io/sensor_model_part_controller.py +58 -0
  14. KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/response_sensitivity_analysis.py +41 -0
  15. KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/response_sensitivity_interface.py +34 -0
  16. KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/sensor_sensitivity_adjoint_static_solver.py +50 -0
  17. KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/system_identification_static_analysis.py +52 -0
  18. KratosMultiphysics/SystemIdentificationApplication/utilities/data_utils.py +67 -0
  19. KratosMultiphysics/SystemIdentificationApplication/utilities/sensor_utils.py +196 -0
  20. KratosMultiphysics/SystemIdentificationApplication/utilities/tensor_adaptor_utils.py +63 -0
  21. kratossystemidentificationapplication-10.4.4.dist-info/METADATA +11 -0
  22. kratossystemidentificationapplication-10.4.4.dist-info/RECORD +27 -0
  23. kratossystemidentificationapplication-10.4.4.dist-info/WHEEL +6 -0
  24. kratossystemidentificationapplication.dylibs/libKratosCore.dylib +4 -0
  25. kratossystemidentificationapplication.dylibs/libgcc_s.1.1.dylib +0 -0
  26. kratossystemidentificationapplication.dylibs/libgomp.1.dylib +0 -0
  27. kratossystemidentificationapplication.dylibs/libstdc++.6.dylib +0 -0
@@ -0,0 +1,13 @@
1
+ # Application dependent names and paths
2
+ from KratosMultiphysics import _ImportApplication, python_registry_utilities
3
+ from KratosSystemIdentificationApplication import *
4
+
5
+ application = KratosSystemIdentificationApplication()
6
+ application_name = "KratosSystemIdentificationApplication"
7
+
8
+ _ImportApplication(application, application_name)
9
+
10
+ from . import python_registry_lists
11
+ python_registry_utilities.RegisterModelersList("KratosMultiphysics.SystemIdentificationApplication", python_registry_lists)
12
+ python_registry_utilities.RegisterOperationsList("KratosMultiphysics.SystemIdentificationApplication", python_registry_lists)
13
+ python_registry_utilities.RegisterProcessesList("KratosMultiphysics.SystemIdentificationApplication", python_registry_lists)
@@ -0,0 +1,210 @@
1
+ import math, numpy
2
+ from typing import Optional
3
+
4
+ import KratosMultiphysics as Kratos
5
+ import KratosMultiphysics.SystemIdentificationApplication as KratosSI
6
+ from KratosMultiphysics.OptimizationApplication.controls.control import Control
7
+ from KratosMultiphysics.OptimizationApplication.utilities.union_utilities import SupportedSensitivityFieldVariableTypes
8
+ from KratosMultiphysics.OptimizationApplication.utilities.model_part_utilities import ModelPartOperation
9
+ from KratosMultiphysics.OptimizationApplication.utilities.logger_utilities import TimeLogger
10
+ from KratosMultiphysics.OptimizationApplication.utilities.component_data_view import ComponentDataView
11
+ from KratosMultiphysics.OptimizationApplication.utilities.optimization_problem import OptimizationProblem
12
+ from KratosMultiphysics.SystemIdentificationApplication.utilities.tensor_adaptor_utils import TensorAdaptorBoundingManager, GetTensorAdaptor
13
+ from KratosMultiphysics.OptimizationApplication.filtering.filter import Factory as FilterFactory
14
+
15
+ def Factory(model: Kratos.Model, parameters: Kratos.Parameters, optimization_problem: OptimizationProblem) -> Control:
16
+ if not parameters.Has("name"):
17
+ raise RuntimeError(f"DataValuesControl instantiation requires a \"name\" in parameters [ parameters = {parameters}].")
18
+ if not parameters.Has("settings"):
19
+ raise RuntimeError(f"DataValuesControl instantiation requires a \"settings\" in parameters [ parameters = {parameters}].")
20
+
21
+ return DataValuesControl(parameters["name"].GetString(), model, parameters["settings"], optimization_problem)
22
+
23
+
24
+ class DataValuesControl(Control):
25
+ """Data values control
26
+
27
+ This is a generic data values control which creates a control
28
+ for the specified control variable. This does not do any filtering.
29
+
30
+ """
31
+ def __init__(self, name: str, model: Kratos.Model, parameters: Kratos.Parameters, optimization_problem: OptimizationProblem):
32
+ super().__init__(name)
33
+
34
+ default_settings = Kratos.Parameters("""{
35
+ "control_variable_name" : "",
36
+ "control_variable_bounds" : [0.0, 0.0],
37
+ "container_type" : "",
38
+ "output_all_fields" : false,
39
+ "filter_settings" : {},
40
+ "model_part_names": [
41
+ {
42
+ "primal_model_part_name" : "PLEASE_PROVIDE_MODEL_PART_NAME",
43
+ "adjoint_model_part_name": "PLEASE_PROVIDE_MODEL_PART_NAME"
44
+ }
45
+ ]
46
+ }""")
47
+
48
+ parameters.ValidateAndAssignDefaults(default_settings)
49
+
50
+ allowed_container_types = {
51
+ "nodal_historical": Kratos.Globals.DataLocation.NodeHistorical,
52
+ "nodal_nonhistorical": Kratos.Globals.DataLocation.NodeNonHistorical,
53
+ "condition": Kratos.Globals.DataLocation.Condition,
54
+ "element": Kratos.Globals.DataLocation.Element
55
+ }
56
+ container_type = parameters["container_type"].GetString()
57
+ if container_type not in allowed_container_types.keys():
58
+ raise RuntimeError(f"Unsupported container type = \"{container_type}\". Allowed container types are:\n\t" + "\n\t".join(list(allowed_container_types.keys())))
59
+ else:
60
+ self.container_type = allowed_container_types[container_type]
61
+
62
+ self.model : Kratos.Model = model
63
+ self.parameters = parameters
64
+ self.optimization_problem = optimization_problem
65
+
66
+ self.output_all_fields = parameters["output_all_fields"].GetBool()
67
+ control_variable_name = parameters["control_variable_name"].GetString()
68
+ control_variable_type = Kratos.KratosGlobals.GetVariableType(control_variable_name)
69
+ if control_variable_type != "Double":
70
+ raise RuntimeError(f"{control_variable_name} with {control_variable_type} type is not supported. Only supports double variables")
71
+ self.controlled_physical_variable: SupportedSensitivityFieldVariableTypes = Kratos.KratosGlobals.GetVariable(control_variable_name)
72
+
73
+ controlled_model_part_names: 'list[Kratos.Parameters]' = parameters["model_part_names"].values()
74
+ if len(controlled_model_part_names) == 0:
75
+ raise RuntimeError(f"No model parts were provided for DataValuesControl. [ control name = \"{self.GetName()}\"]")
76
+
77
+ self.primal_model_part_operation = ModelPartOperation(
78
+ self.model,
79
+ ModelPartOperation.OperationType.UNION,
80
+ f"control_primal_{self.GetName()}",
81
+ [param["primal_model_part_name"].GetString() for param in controlled_model_part_names],
82
+ False)
83
+ self.adjoint_model_part_operation = ModelPartOperation(
84
+ self.model,
85
+ ModelPartOperation.OperationType.UNION,
86
+ f"control_adjoint_{self.GetName()}",
87
+ [param["adjoint_model_part_name"].GetString() for param in controlled_model_part_names],
88
+ False)
89
+
90
+ # filter needs to be based on the primal model part
91
+ # because, filter may keep pointers for the elements to get their center positions
92
+ # for filtering. The adjoint model part may re-assign adjoint elements based on
93
+ # the primal model part rendering the filter element pointers useless and to segfault.
94
+ # hence filter is using the primal model part to get the locations.
95
+ self.filter = FilterFactory(self.model, self.primal_model_part_operation.GetModelPartFullName(), self.controlled_physical_variable, self.container_type, self.parameters["filter_settings"])
96
+
97
+ self.primal_model_part: Optional[Kratos.ModelPart] = None
98
+ self.adjoint_model_part: Optional[Kratos.ModelPart] = None
99
+
100
+ control_variable_bounds = parameters["control_variable_bounds"].GetVector()
101
+
102
+ # use the clamper in the unit interval
103
+ self.interval_bounder = TensorAdaptorBoundingManager(control_variable_bounds)
104
+ self.clamper = KratosSI.SmoothClamper(0, 1)
105
+
106
+ def Initialize(self) -> None:
107
+ self.primal_model_part = self.primal_model_part_operation.GetModelPart()
108
+ self.adjoint_model_part = self.adjoint_model_part_operation.GetModelPart()
109
+
110
+ # initialize the filter
111
+ self.filter.SetComponentDataView(ComponentDataView(self, self.optimization_problem))
112
+ self.filter.Initialize()
113
+
114
+ physical_field = self.GetPhysicalField()
115
+
116
+ # get the phi field which is in [0, 1] range
117
+ self.physical_phi_field = self.clamper.ProjectBackward(self.interval_bounder.GetBoundedTensorAdaptor(physical_field))
118
+
119
+ # compute the control phi field
120
+ self.control_phi_field = self.filter.UnfilterField(self.physical_phi_field)
121
+
122
+ self.physical_phi_derivative_field = self.clamper.CalculateForwardProjectionGradient(self.physical_phi_field)
123
+ self.physical_phi_derivative_field.data[:] *= self.interval_bounder.GetBoundGap()
124
+
125
+ self._UpdateAndOutputFields(self.GetEmptyField())
126
+
127
+ def Check(self) -> None:
128
+ self.filter.Check()
129
+
130
+ def Finalize(self) -> None:
131
+ self.filter.Finalize()
132
+
133
+ def GetPhysicalKratosVariables(self) -> 'list[SupportedSensitivityFieldVariableTypes]':
134
+ return [self.controlled_physical_variable]
135
+
136
+ def GetEmptyField(self) -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
137
+ field = GetTensorAdaptor(self.primal_model_part, self.container_type, self.controlled_physical_variable)
138
+ field.data[:] = 0.0
139
+ return field
140
+
141
+ def GetControlField(self) -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
142
+ return self.control_phi_field.Clone()
143
+
144
+ def GetPhysicalField(self) -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
145
+ physical_thickness_field = GetTensorAdaptor(self.primal_model_part, self.container_type, self.controlled_physical_variable)
146
+ physical_thickness_field.CollectData()
147
+ return physical_thickness_field
148
+
149
+ def MapGradient(self, physical_variable_gradient_map: 'dict[SupportedSensitivityFieldVariableTypes, Kratos.TensorAdaptors.DoubleTensorAdaptor]') -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
150
+ with TimeLogger("DataValuesControl::MapGradient", None, "Finished",False):
151
+ keys = physical_variable_gradient_map.keys()
152
+ if len(keys) != 1:
153
+ raise RuntimeError(f"Provided more than required gradient fields for control \"{self.GetName()}\". Following are the variables:\n\t" + "\n\t".join([k.Name() for k in keys]))
154
+ if self.controlled_physical_variable not in keys:
155
+ raise RuntimeError(f"The required gradient for control \"{self.GetName()}\" w.r.t. {self.controlled_physical_variable.Name()} not found. Followings are the variables:\n\t" + "\n\t".join([k.Name() for k in keys]))
156
+
157
+ physical_gradient = physical_variable_gradient_map[self.controlled_physical_variable]
158
+ if physical_gradient.GetContainer() != self.GetEmptyField().GetContainer():
159
+ raise RuntimeError(f"Gradients for the required element container not found for control \"{self.GetName()}\". [ required model part name: {self.primal_model_part.FullName()} ]")
160
+
161
+ # dj/dE -> physical_gradient
162
+ # dj/dphi = dj/dphysical * dphysical/dphi
163
+ dj_dphi = physical_gradient.Clone()
164
+ dj_dphi.data[:] *= self.physical_phi_derivative_field.data
165
+ return self.filter.BackwardFilterIntegratedField(dj_dphi)
166
+
167
+ def Update(self, new_control_field: Kratos.TensorAdaptors.DoubleTensorAdaptor) -> bool:
168
+ if new_control_field.GetContainer() != self.GetEmptyField().GetContainer():
169
+ raise RuntimeError(f"Updates for the required element container not found for control \"{self.GetName()}\". [ required model part name: {self.primal_model_part.FullName()} ]")
170
+
171
+ update = new_control_field.Clone()
172
+ update.data[:] -= self.control_phi_field.data
173
+ if not math.isclose(numpy.linalg.norm(update.data), 0.0, abs_tol=1e-16):
174
+ with TimeLogger(self.__class__.__name__, f"Updating {self.GetName()}...", f"Finished updating of {self.GetName()}.",False):
175
+ # update the control thickness field
176
+ self.control_phi_field.data[:] = new_control_field.data
177
+ # now update the physical field
178
+ self._UpdateAndOutputFields(update)
179
+ return True
180
+ return False
181
+
182
+ def _UpdateAndOutputFields(self, update: Kratos.TensorAdaptors.DoubleTensorAdaptor) -> None:
183
+ # filter the control field
184
+ filtered_phi_field_update = self.filter.ForwardFilterField(update)
185
+ self.physical_phi_field.data[:] += filtered_phi_field_update.data
186
+
187
+ # project forward the filtered thickness field to get clamped physical field
188
+ physical_field = self.interval_bounder.GetUnboundedTensorAdaptor(self.clamper.ProjectForward(self.physical_phi_field))
189
+
190
+ # now update physical field
191
+ ta = GetTensorAdaptor(self.primal_model_part, self.container_type, self.controlled_physical_variable)
192
+ ta.data[:] = physical_field.data
193
+ ta.StoreData()
194
+
195
+ # compute and store projection derivatives for consistent filtering of the sensitivities
196
+ # this is dphi/dphysical -> physical_phi_derivative_field
197
+ self.physical_phi_derivative_field = self.clamper.CalculateForwardProjectionGradient(self.physical_phi_field)
198
+ self.physical_phi_derivative_field.data[:] *= self.interval_bounder.GetBoundGap()
199
+
200
+ # now output the fields
201
+ un_buffered_data = ComponentDataView(self, self.optimization_problem).GetUnBufferedData()
202
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}", physical_field.Clone(), overwrite=True)
203
+ if self.output_all_fields:
204
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_physical_phi_update", filtered_phi_field_update.Clone(), overwrite=True)
205
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_control_phi", self.control_phi_field.Clone(), overwrite=True)
206
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_physical_phi", self.physical_phi_field.Clone(), overwrite=True)
207
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_physical_phi_derivative", self.physical_phi_derivative_field.Clone(), overwrite=True)
208
+
209
+ def __str__(self) -> str:
210
+ return f"Control [type = {self.__class__.__name__}, name = {self.GetName()}, model part name = {self.adjoint_model_part_operation.GetModelPartFullName()}, control variable = {self.controlled_physical_variable.Name()}"
@@ -0,0 +1,212 @@
1
+ import math, numpy
2
+ from typing import Optional
3
+
4
+ import KratosMultiphysics as Kratos
5
+ import KratosMultiphysics.OptimizationApplication as KratosOA
6
+ import KratosMultiphysics.SystemIdentificationApplication as KratosSI
7
+ from KratosMultiphysics.OptimizationApplication.controls.control import Control
8
+ from KratosMultiphysics.OptimizationApplication.utilities.union_utilities import SupportedSensitivityFieldVariableTypes
9
+ from KratosMultiphysics.OptimizationApplication.utilities.model_part_utilities import ModelPartOperation
10
+ from KratosMultiphysics.OptimizationApplication.utilities.logger_utilities import TimeLogger
11
+ from KratosMultiphysics.OptimizationApplication.utilities.component_data_view import ComponentDataView
12
+ from KratosMultiphysics.OptimizationApplication.utilities.optimization_problem import OptimizationProblem
13
+ from KratosMultiphysics.SystemIdentificationApplication.utilities.tensor_adaptor_utils import TensorAdaptorBoundingManager
14
+ from KratosMultiphysics.OptimizationApplication.filtering.filter import Factory as FilterFactory
15
+
16
+ def Factory(model: Kratos.Model, parameters: Kratos.Parameters, optimization_problem: OptimizationProblem) -> Control:
17
+ if not parameters.Has("name"):
18
+ raise RuntimeError(f"MaterialPropertiesControl instantiation requires a \"name\" in parameters [ parameters = {parameters}].")
19
+ if not parameters.Has("settings"):
20
+ raise RuntimeError(f"MaterialPropertiesControl instantiation requires a \"settings\" in parameters [ parameters = {parameters}].")
21
+
22
+ return MaterialPropertiesControl(parameters["name"].GetString(), model, parameters["settings"], optimization_problem)
23
+
24
+ class MaterialPropertiesControl(Control):
25
+ """Material properties control
26
+
27
+ This is a generic material properties control which creates a control
28
+ for the specified control variable. This does not do any filtering.
29
+
30
+ """
31
+ def __init__(self, name: str, model: Kratos.Model, parameters: Kratos.Parameters, optimization_problem: OptimizationProblem):
32
+ super().__init__(name)
33
+
34
+ default_settings = Kratos.Parameters("""{
35
+ "control_variable_name" : "",
36
+ "control_variable_bounds" : [0.0, 0.0],
37
+ "output_all_fields" : false,
38
+ "consider_recursive_property_update": false,
39
+ "filter_settings" : {},
40
+ "model_part_names": [
41
+ {
42
+ "primal_model_part_name" : "PLEASE_PROVIDE_MODEL_PART_NAME",
43
+ "adjoint_model_part_name": "PLEASE_PROVIDE_MODEL_PART_NAME"
44
+ }
45
+ ]
46
+ }""")
47
+ parameters.ValidateAndAssignDefaults(default_settings)
48
+
49
+ self.model = model
50
+ self.parameters = parameters
51
+ self.optimization_problem = optimization_problem
52
+
53
+ self.output_all_fields = parameters["output_all_fields"].GetBool()
54
+ control_variable_name = parameters["control_variable_name"].GetString()
55
+ control_variable_type = Kratos.KratosGlobals.GetVariableType(control_variable_name)
56
+ if control_variable_type != "Double":
57
+ raise RuntimeError(f"{control_variable_name} with {control_variable_type} type is not supported. Only supports double variables")
58
+ self.controlled_physical_variable: SupportedSensitivityFieldVariableTypes = Kratos.KratosGlobals.GetVariable(control_variable_name)
59
+
60
+ controlled_model_part_names: 'list[Kratos.Parameters]' = parameters["model_part_names"].values()
61
+ if len(controlled_model_part_names) == 0:
62
+ raise RuntimeError(f"No model parts were provided for MaterialPropertiesControl. [ control name = \"{self.GetName()}\"]")
63
+
64
+ self.primal_model_part_operation = ModelPartOperation(
65
+ self.model,
66
+ ModelPartOperation.OperationType.UNION,
67
+ f"control_primal_{self.GetName()}",
68
+ [param["primal_model_part_name"].GetString() for param in controlled_model_part_names],
69
+ False)
70
+ self.adjoint_model_part_operation = ModelPartOperation(
71
+ self.model,
72
+ ModelPartOperation.OperationType.UNION,
73
+ f"control_adjoint_{self.GetName()}",
74
+ [param["adjoint_model_part_name"].GetString() for param in controlled_model_part_names],
75
+ False)
76
+
77
+ self.consider_recursive_property_update = parameters["consider_recursive_property_update"].GetBool()
78
+
79
+
80
+ # filter needs to be based on the primal model part
81
+ # because, filter may keep pointers for the elements to get their center positions
82
+ # for filtering. The adjoint model part may re-assign adjoint elements based on
83
+ # the primal model part rendering the filter element pointers useless and to segfault.
84
+ # hence filter is using the primal model part to get the locations.
85
+ self.filter = FilterFactory(self.model, self.primal_model_part_operation.GetModelPartFullName(), self.controlled_physical_variable, Kratos.Globals.DataLocation.Element, self.parameters["filter_settings"])
86
+
87
+ self.primal_model_part: Optional[Kratos.ModelPart] = None
88
+ self.adjoint_model_part: Optional[Kratos.ModelPart] = None
89
+
90
+ control_variable_bounds = parameters["control_variable_bounds"].GetVector()
91
+
92
+ # use the clamper in the unit interval
93
+ self.interval_bounder = TensorAdaptorBoundingManager(control_variable_bounds)
94
+ self.clamper = KratosSI.SmoothClamper(0, 1)
95
+
96
+ def Initialize(self) -> None:
97
+ self.primal_model_part = self.primal_model_part_operation.GetModelPart()
98
+ self.adjoint_model_part = self.adjoint_model_part_operation.GetModelPart()
99
+
100
+ if not KratosOA.OptAppModelPartUtils.CheckModelPartStatus(self.primal_model_part, "element_specific_properties_created"):
101
+ KratosOA.OptimizationUtils.CreateEntitySpecificPropertiesForContainer(self.primal_model_part, self.primal_model_part.Elements, self.consider_recursive_property_update)
102
+ KratosOA.OptAppModelPartUtils.LogModelPartStatus(self.primal_model_part, "element_specific_properties_created")
103
+
104
+ if self.primal_model_part != self.adjoint_model_part:
105
+ if KratosOA.OptAppModelPartUtils.CheckModelPartStatus(self.adjoint_model_part, "element_specific_properties_created"):
106
+ raise RuntimeError(f"Trying to create element specific properties for {self.adjoint_model_part.FullName()} which already has element specific properties.")
107
+
108
+ # now assign the properties of primal to the adjoint model parts
109
+ KratosSI.ControlUtils.AssignEquivalentProperties(self.primal_model_part.Elements, self.adjoint_model_part.Elements)
110
+ KratosOA.OptAppModelPartUtils.LogModelPartStatus(self.adjoint_model_part, "element_specific_properties_created")
111
+
112
+ # initialize the filter
113
+ self.filter.SetComponentDataView(ComponentDataView(self, self.optimization_problem))
114
+ self.filter.Initialize()
115
+
116
+ physical_field = self.GetPhysicalField()
117
+
118
+ # get the phi field which is in [0, 1] range
119
+ self.physical_phi_field = self.clamper.ProjectBackward(self.interval_bounder.GetBoundedTensorAdaptor(physical_field))
120
+
121
+ # compute the control phi field
122
+ self.control_phi_field = self.filter.UnfilterField(self.physical_phi_field)
123
+
124
+ self.physical_phi_derivative_field = self.clamper.CalculateForwardProjectionGradient(self.physical_phi_field)
125
+ self.physical_phi_derivative_field.data[:] *= self.interval_bounder.GetBoundGap()
126
+
127
+ self._UpdateAndOutputFields(self.GetEmptyField())
128
+
129
+ def Check(self) -> None:
130
+ self.filter.Check()
131
+
132
+ def Finalize(self) -> None:
133
+ self.filter.Finalize()
134
+
135
+ def GetPhysicalKratosVariables(self) -> 'list[SupportedSensitivityFieldVariableTypes]':
136
+ return [self.controlled_physical_variable]
137
+
138
+ def GetEmptyField(self) -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
139
+ field = Kratos.TensorAdaptors.VariableTensorAdaptor(self.primal_model_part.Elements, self.controlled_physical_variable)
140
+ field.data[:] = 0.0
141
+ return field
142
+
143
+ def GetControlField(self) -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
144
+ return self.control_phi_field.Clone()
145
+
146
+ def GetPhysicalField(self) -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
147
+ physical_thickness_field = KratosOA.TensorAdaptors.PropertiesVariableTensorAdaptor(self.primal_model_part.Elements, self.controlled_physical_variable)
148
+ physical_thickness_field.CollectData()
149
+ return physical_thickness_field
150
+
151
+ def MapGradient(self, physical_variable_gradient_map: 'dict[SupportedSensitivityFieldVariableTypes, Kratos.TensorAdaptors.DoubleTensorAdaptor]') -> Kratos.TensorAdaptors.DoubleTensorAdaptor:
152
+ with TimeLogger("ShellThicknessControl::MapGradient", None, "Finished",False):
153
+ keys = physical_variable_gradient_map.keys()
154
+ if len(keys) != 1:
155
+ raise RuntimeError(f"Provided more than required gradient fields for control \"{self.GetName()}\". Following are the variables:\n\t" + "\n\t".join([k.Name() for k in keys]))
156
+ if self.controlled_physical_variable not in keys:
157
+ raise RuntimeError(f"The required gradient for control \"{self.GetName()}\" w.r.t. {self.controlled_physical_variable.Name()} not found. Followings are the variables:\n\t" + "\n\t".join([k.Name() for k in keys]))
158
+
159
+ physical_gradient = physical_variable_gradient_map[self.controlled_physical_variable]
160
+ if physical_gradient.GetContainer() != self.primal_model_part.Elements:
161
+ raise RuntimeError(f"Gradients for the required element container not found for control \"{self.GetName()}\". [ required model part name: {self.primal_model_part.FullName()} ]")
162
+
163
+ # dj/dE -> physical_gradient
164
+ # dj/dphi = dj/dphysical * dphysical/dphi
165
+ dj_dphi = physical_gradient.Clone()
166
+ dj_dphi.data[:] *= self.physical_phi_derivative_field.data
167
+ return self.filter.BackwardFilterIntegratedField(dj_dphi)
168
+
169
+ def Update(self, new_control_field: Kratos.TensorAdaptors.DoubleTensorAdaptor) -> bool:
170
+ if new_control_field.GetContainer() != self.primal_model_part.Elements:
171
+ raise RuntimeError(f"Updates for the required element container not found for control \"{self.GetName()}\". [ required model part name: {self.primal_model_part.FullName()} ]")
172
+
173
+ update = new_control_field.Clone()
174
+ update.data[:] -= self.control_phi_field.data
175
+ if not math.isclose(numpy.linalg.norm(update.data), 0.0, abs_tol=1e-16):
176
+ with TimeLogger(self.__class__.__name__, f"Updating {self.GetName()}...", f"Finished updating of {self.GetName()}.",False):
177
+ # update the control thickness field
178
+ self.control_phi_field.data[:] = new_control_field.data
179
+ # now update the physical field
180
+ self._UpdateAndOutputFields(update)
181
+ return True
182
+ return False
183
+
184
+ def _UpdateAndOutputFields(self, update: Kratos.TensorAdaptors.DoubleTensorAdaptor) -> None:
185
+ # filter the control field
186
+ filtered_phi_field_update = self.filter.ForwardFilterField(update)
187
+ self.physical_phi_field.data[:] += filtered_phi_field_update.data
188
+
189
+ # project forward the filtered thickness field to get clamped physical field
190
+ physical_field = self.interval_bounder.GetUnboundedTensorAdaptor(self.clamper.ProjectForward(self.physical_phi_field))
191
+
192
+ # now update physical field
193
+ KratosOA.TensorAdaptors.PropertiesVariableTensorAdaptor(physical_field, self.controlled_physical_variable, copy=False).StoreData()
194
+ if self.consider_recursive_property_update:
195
+ KratosOA.OptimizationUtils.UpdatePropertiesVariableWithRootValueRecursively(physical_field.GetContainer(), self.controlled_physical_variable)
196
+
197
+ # compute and store projection derivatives for consistent filtering of the sensitivities
198
+ # this is dphi/dphysical -> physical_phi_derivative_field
199
+ self.physical_phi_derivative_field = self.clamper.CalculateForwardProjectionGradient(self.physical_phi_field)
200
+ self.physical_phi_derivative_field.data[:] *= self.interval_bounder.GetBoundGap()
201
+
202
+ # now output the fields
203
+ un_buffered_data = ComponentDataView(self, self.optimization_problem).GetUnBufferedData()
204
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}", physical_field.Clone(), overwrite=True)
205
+ if self.output_all_fields:
206
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_physical_phi_update", filtered_phi_field_update.Clone(), overwrite=True)
207
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_control_phi", self.control_phi_field.Clone(), overwrite=True)
208
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_physical_phi", self.physical_phi_field.Clone(), overwrite=True)
209
+ un_buffered_data.SetValue(f"{self.controlled_physical_variable.Name()}_physical_phi_derivative", self.physical_phi_derivative_field.Clone(), overwrite=True)
210
+
211
+ def __str__(self) -> str:
212
+ return f"Control [type = {self.__class__.__name__}, name = {self.GetName()}, model part name = {self.adjoint_model_part_operation.GetModelPartFullName()}, control variable = {self.controlled_physical_variable.Name()}"
@@ -0,0 +1,93 @@
1
+ import os
2
+ import typing
3
+ import KratosMultiphysics as Kratos
4
+ import KratosMultiphysics.SystemIdentificationApplication as KratosSI
5
+ from KratosMultiphysics.OptimizationApplication.utilities.optimization_problem import OptimizationProblem
6
+ from KratosMultiphysics.OptimizationApplication.utilities.component_data_view import ComponentDataView
7
+ from KratosMultiphysics.OptimizationApplication.convergence_criteria.convergence_criterion import ConvergenceCriterion
8
+ from KratosMultiphysics.OptimizationApplication.utilities.logger_utilities import time_decorator
9
+ from KratosMultiphysics.SystemIdentificationApplication.utilities.sensor_utils import GetSensors
10
+
11
+ def Factory(_: Kratos.Model, parameters: Kratos.Parameters, optimization_problem: OptimizationProblem) -> ConvergenceCriterion:
12
+ if not parameters.Has("settings"):
13
+ raise RuntimeError(f"MaxSensorErrorCriterion instantiation requires a \"settings\" in parameters [ parameters = {parameters}].")
14
+ return MaxSensorErrorCriterion(parameters["settings"], optimization_problem)
15
+
16
+ class MaxSensorErrorCriterion(ConvergenceCriterion):
17
+ """
18
+ MaxSensorErrorCriterion is a convergence criterion based on the maximum sensor error in a system identification problem.
19
+
20
+ This class checks whether the maximum sensor error falls below a specified tolerance,
21
+ indicating convergence. The criterion is configurable via parameters.
22
+ """
23
+ @classmethod
24
+ def GetDefaultParameters(cls):
25
+ return Kratos.Parameters("""{
26
+ "sensor_group_name": "sensors",
27
+ "tolerance" : 1e-9,
28
+ "output_to_file" : true,
29
+ "sensor_error_output_file_name" : "max_sensor_error.csv"
30
+ }""")
31
+
32
+ def __init__(self, parameters: Kratos.Parameters, optimization_problem: OptimizationProblem):
33
+ parameters.ValidateAndAssignDefaults(self.GetDefaultParameters())
34
+ self.__sensor_group_name = parameters["sensor_group_name"].GetString()
35
+ self.__tolerance = parameters["tolerance"].GetDouble()
36
+ self.__optimization_problem = optimization_problem
37
+ self.__output_to_file = parameters["output_to_file"].GetBool()
38
+ self.__sensor_error_output_file_name = parameters["sensor_error_output_file_name"].GetString()
39
+
40
+ def Initialize(self):
41
+ if self.__output_to_file:
42
+ with open(self.__sensor_error_output_file_name, "w") as file_output:
43
+ file_output.write("iteration,max_sensor_error\n")
44
+
45
+ @time_decorator()
46
+ def IsConverged(self) -> bool:
47
+ """
48
+ Check if the convergence criterion based on sensor error is satisfied.
49
+ This method evaluates convergence by comparing the maximum sensor error across
50
+ all sensors in the sensor group against a predefined tolerance threshold.
51
+ The method performs the following steps:
52
+ 1. Retrieves sensor group data from the optimization problem
53
+ 2. Extracts all sensors from the sensor group
54
+ 3. Computes the maximum absolute error across all sensors
55
+ 4. Compares the maximum error against the tolerance threshold
56
+ 5. Writes the maximum sensor error to a file for monitoring
57
+ Returns:
58
+ bool: True if the maximum sensor error is less than or equal to the
59
+ tolerance threshold, False otherwise.
60
+ """
61
+ sensor_group_data = ComponentDataView(self.__sensor_group_name, self.__optimization_problem)
62
+ self.list_of_sensors = GetSensors(sensor_group_data)
63
+
64
+ max_sensor_error = 0.0
65
+ for sensor in self.list_of_sensors:
66
+ max_sensor_error = max(max_sensor_error, abs(sensor.GetNode().GetValue(KratosSI.SENSOR_ERROR)))
67
+
68
+ self.__max_sensor_error = max_sensor_error
69
+ self.__conv = self.__max_sensor_error <= self.__tolerance
70
+ self.WriteMaxSensorErrorToFile()
71
+ return self.__conv
72
+
73
+ def GetInfo(self) -> 'list[tuple[str, typing.Union[int, float, str]]]':
74
+ info = [
75
+ ('type' , 'sensor_error'),
76
+ ('max_sensor_error', self.__max_sensor_error),
77
+ ('tolerance' , self.__tolerance),
78
+ ('sensor_group_name', self.__sensor_group_name),
79
+ ('status' , str("converged" if self.__conv else "not converged"))
80
+ ]
81
+ return info
82
+
83
+ def Finalize(self):
84
+ pass
85
+
86
+ def WriteMaxSensorErrorToFile(self) -> None:
87
+ if self.__output_to_file:
88
+ iteration = self.__optimization_problem.GetStep()
89
+ file_exists = os.path.isfile(self.__sensor_error_output_file_name)
90
+ with open(self.__sensor_error_output_file_name, "a") as file_output:
91
+ if not file_exists:
92
+ file_output.write("iteration,max_sensor_error\n")
93
+ file_output.write(f"{iteration},{self.__max_sensor_error}\n")
@@ -0,0 +1,83 @@
1
+ from pathlib import Path
2
+ import typing
3
+ import KratosMultiphysics as Kratos
4
+ import KratosMultiphysics.SystemIdentificationApplication as KratosSI
5
+
6
+ from KratosMultiphysics.SystemIdentificationApplication.utilities.sensor_utils import CreateSensors
7
+ from KratosMultiphysics.SystemIdentificationApplication.utilities.sensor_utils import PrintSensorListToCSV
8
+ from KratosMultiphysics.OptimizationApplication.utilities.optimization_problem import OptimizationProblem
9
+ from KratosMultiphysics.OptimizationApplication.utilities.component_data_view import ComponentDataView
10
+ from KratosMultiphysics.OptimizationApplication.responses.response_function import ResponseFunction
11
+
12
+ def Factory(arg1: Kratos.Parameters, arg2: Kratos.Model, optimization_problem: 'typing.Optional[OptimizationProblem]' = None):
13
+ if isinstance(arg1, Kratos.Parameters) and isinstance(arg2, Kratos.Model):
14
+ # this is to use SensorOutputProcess as a normal kratos process
15
+ return SensorOutputProcess(arg2, arg1["Parameters"], optimization_problem)
16
+ elif isinstance(arg1, Kratos.Model) and isinstance(arg2, Kratos.Parameters):
17
+ # this is to use SensorOutputProcess as a process for optimization application where optimization_problem cannot be none
18
+ return SensorOutputProcess(arg1, arg2["settings"], optimization_problem)
19
+ else:
20
+ raise RuntimeError("Argument mismatch.")
21
+
22
+ class SensorOutputProcess(Kratos.OutputProcess):
23
+ def __init__(self, model: Kratos.Model, settings: Kratos.Parameters, optimization_problem: 'typing.Optional[OptimizationProblem]' = None):
24
+ Kratos.OutputProcess.__init__(self)
25
+
26
+ default_settings = Kratos.Parameters("""{
27
+ "model_part_name" : "PLEASE_PROVIDE_A_MODEL_PART_NAME",
28
+ "sensor_model_part_name": "SensorModelPart",
29
+ "output_file_name": "",
30
+ "properties_list": [
31
+ "type",
32
+ "name",
33
+ "location",
34
+ "value"
35
+ ],
36
+ "list_of_sensors": []
37
+ }""")
38
+
39
+ if optimization_problem is not None:
40
+ default_settings.RemoveValue("list_of_sensors")
41
+ default_settings.AddEmptyValue("response_name")
42
+ default_settings["response_name"].SetString("")
43
+ settings.ValidateAndAssignDefaults(default_settings)
44
+
45
+ self.model_part = model[settings["model_part_name"].GetString()]
46
+ self.properties_list: 'list[str]' = settings["properties_list"].GetStringArray()
47
+ self.output_file_name = Path(settings["output_file_name"].GetString())
48
+ self.optimization_problem = optimization_problem
49
+
50
+ self.list_of_sensors: 'list[KratosSI.Sensors.Sensor]' = []
51
+ if optimization_problem is None:
52
+ self.initialized_sensors = True
53
+ self.compute_sensor_value = True
54
+ sensor_model_part_name = settings["sensor_model_part_name"].GetString()
55
+ if not model.HasModelPart(sensor_model_part_name):
56
+ model.CreateModelPart(sensor_model_part_name)
57
+ self.list_of_sensors = CreateSensors(model[sensor_model_part_name], self.model_part, settings["list_of_sensors"].values())
58
+ else:
59
+ self.initialized_sensors = False
60
+ self.compute_sensor_value = False
61
+ self.response_name = settings["response_name"].GetString()
62
+
63
+ def PrintOutput(self) -> None:
64
+ s_name = str(self.output_file_name)
65
+
66
+ if not self.initialized_sensors:
67
+ self.list_of_sensors = ComponentDataView(self.optimization_problem.GetComponent(self.response_name, ResponseFunction), self.optimization_problem).GetUnBufferedData().GetValue("sensors")
68
+ self.initialized_sensors = True
69
+ s_name = s_name.replace("<step>", f"{self.optimization_problem.GetStep():06d}")
70
+ else:
71
+ s_name = s_name.replace("<step>", f"{self.model_part.ProcessInfo[Kratos.STEP]:06d}")
72
+
73
+ # calculate and assign values for each sensor
74
+ if self.compute_sensor_value:
75
+ for sensor in self.list_of_sensors:
76
+ sensor.SetSensorValue(sensor.CalculateValue(self.model_part))
77
+
78
+ PrintSensorListToCSV(Path(s_name), self.list_of_sensors, self.properties_list)
79
+
80
+
81
+
82
+
83
+
@@ -0,0 +1,9 @@
1
+ python_modelers_to_be_registered = []
2
+
3
+ python_operations_to_be_registered = []
4
+
5
+ python_processes_to_be_registered = []
6
+
7
+ python_stages_to_be_registered = []
8
+
9
+ python_orchestrators_to_be_registered = []