KratosSystemIdentificationApplication 10.4.2__2-cp313-cp313-win_amd64.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.
- KratosMultiphysics/.libs/KratosSystemIdentificationApplication.pyd +0 -0
- KratosMultiphysics/.libs/KratosSystemIdentificationCore.dll +0 -0
- KratosMultiphysics/.libs/KratosSystemIdentificationCore.lib +0 -0
- KratosMultiphysics/SystemIdentificationApplication/__init__.py +13 -0
- KratosMultiphysics/SystemIdentificationApplication/controls/data_values_control.py +210 -0
- KratosMultiphysics/SystemIdentificationApplication/controls/material_properties_control.py +212 -0
- KratosMultiphysics/SystemIdentificationApplication/convergence_criteria/sensor_error.py +93 -0
- KratosMultiphysics/SystemIdentificationApplication/processes/sensor_output_process.py +83 -0
- KratosMultiphysics/SystemIdentificationApplication/python_registry_lists.py +9 -0
- KratosMultiphysics/SystemIdentificationApplication/responses/damage_detection_response.py +182 -0
- KratosMultiphysics/SystemIdentificationApplication/responses/pressure_detection_response.py +20 -0
- KratosMultiphysics/SystemIdentificationApplication/responses/temperature_detection_response.py +20 -0
- KratosMultiphysics/SystemIdentificationApplication/sensor_generator_analysis.py +130 -0
- KratosMultiphysics/SystemIdentificationApplication/sensor_io/sensor_model_part_controller.py +49 -0
- KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/response_sensitivity_analysis.py +41 -0
- KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/response_sensitivity_interface.py +34 -0
- KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/sensor_sensitivity_adjoint_static_solver.py +50 -0
- KratosMultiphysics/SystemIdentificationApplication/sensor_sensitivity_solvers/system_identification_static_analysis.py +52 -0
- KratosMultiphysics/SystemIdentificationApplication/utilities/data_utils.py +67 -0
- KratosMultiphysics/SystemIdentificationApplication/utilities/sensor_utils.py +161 -0
- KratosMultiphysics/SystemIdentificationApplication/utilities/tensor_adaptor_utils.py +63 -0
- kratossystemidentificationapplication-10.4.2.dist-info/METADATA +11 -0
- kratossystemidentificationapplication-10.4.2.dist-info/RECORD +24 -0
- kratossystemidentificationapplication-10.4.2.dist-info/WHEEL +5 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
|