Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OptApp] Adding the ability to have arithmetic operators for responses #12580

Merged
merged 39 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4d3aad0
introduce response utilities
sunethwarna Jul 23, 2024
2c5feee
add binary operator resp func
sunethwarna Jul 23, 2024
c533979
minor
sunethwarna Jul 23, 2024
59cd935
add literal value response function
sunethwarna Jul 23, 2024
611aac0
Merge remote-tracking branch 'origin/master' into optapp/responses/ar…
sunethwarna Jul 27, 2024
2826339
update binary operator resp func
sunethwarna Jul 28, 2024
1adf72e
update response utilities
sunethwarna Jul 28, 2024
ed91d62
add tests
sunethwarna Jul 28, 2024
2f45422
bug fix
sunethwarna Jul 28, 2024
6df4f1f
add more tests
sunethwarna Jul 28, 2024
994b27d
add log response func
sunethwarna Jul 28, 2024
28fbcac
update to while loop
sunethwarna Jul 28, 2024
46200d4
fix division operator
sunethwarna Jul 28, 2024
0e8ff56
add bracket usage for response expression
sunethwarna Jul 28, 2024
edc1eb7
add tests
sunethwarna Jul 28, 2024
ab522e5
minor
sunethwarna Jul 28, 2024
c3a7480
fix binary opt response func
sunethwarna Jul 28, 2024
e0e7c87
fix error msg dic_val_res func
sunethwarna Jul 28, 2024
c495f74
update for gradients -> response_utils
sunethwarna Jul 28, 2024
9cd411c
test gradients
sunethwarna Jul 28, 2024
002b1b0
add tests to suite
sunethwarna Jul 28, 2024
e103eb8
Merge remote-tracking branch 'origin/master' into optapp/responses/ar…
sunethwarna Jul 28, 2024
1502175
minor
sunethwarna Jul 29, 2024
c7eb813
remove model dependence
sunethwarna Jul 29, 2024
a316e70
Merge remote-tracking branch 'origin/master' into optapp/responses/ar…
sunethwarna Jul 29, 2024
8796d89
Update applications/OptimizationApplication/tests/test_response_utili…
sunethwarna Aug 4, 2024
d23ddcd
add GetChildResponses method
sunethwarna Aug 4, 2024
ed9bd08
update GetChildResponses
sunethwarna Aug 4, 2024
94655d9
add EvaluationResponseFunction
sunethwarna Aug 4, 2024
8cc75c4
update response utilities
sunethwarna Aug 4, 2024
33e58b3
update tests
sunethwarna Aug 4, 2024
8a0540e
add more tests
sunethwarna Aug 4, 2024
eb24c27
minor
sunethwarna Aug 4, 2024
65710c7
minor
sunethwarna Aug 4, 2024
9f02bb8
fix recursion
sunethwarna Aug 4, 2024
16c966c
fix resp utils
sunethwarna Aug 4, 2024
ed815c1
minor fix
sunethwarna Aug 5, 2024
932884f
name change
sunethwarna Aug 5, 2024
fee23b9
repeated eval fix
sunethwarna Aug 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from typing import Optional
from math import log

import KratosMultiphysics as Kratos
import KratosMultiphysics.OptimizationApplication as KratosOA
from KratosMultiphysics.OptimizationApplication.responses.response_function import ResponseFunction
from KratosMultiphysics.OptimizationApplication.responses.response_function import SupportedSensitivityFieldVariableTypes
from KratosMultiphysics.OptimizationApplication.utilities.union_utilities import SupportedSensitivityFieldVariableTypes
from KratosMultiphysics.OptimizationApplication.utilities.model_part_utilities import ModelPartOperation
from KratosMultiphysics.OptimizationApplication.utilities.optimization_problem import OptimizationProblem
from KratosMultiphysics.OptimizationApplication.utilities.component_data_view import ComponentDataView
from KratosMultiphysics.OptimizationApplication.utilities.response_utilities import EvaluateValue
from KratosMultiphysics.OptimizationApplication.utilities.response_utilities import EvaluateGradient
from KratosMultiphysics.OptimizationApplication.utilities.response_utilities import BinaryOperator

class BinaryOperatorResponseFunction(ResponseFunction):
def __init__(self, response_function_1: ResponseFunction, response_function_2: ResponseFunction, binary_operator: BinaryOperator, optimization_problem: OptimizationProblem):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add class describtion please

if binary_operator == BinaryOperator.DIVIDE:
# this is because, the optimization_problem data container uses "/" as a path separator.
super().__init__(f"({response_function_1.GetName()}÷{response_function_2.GetName()})")
else:
super().__init__(f"({response_function_1.GetName()}{binary_operator.value}{response_function_2.GetName()})")

self.optimization_problem = optimization_problem
self.response_function_1 = response_function_1
self.response_function_2 = response_function_2
self.binary_operator = binary_operator
self.model_part: Optional[Kratos.ModelPart] = None

def GetImplementedPhysicalKratosVariables(self) -> 'list[SupportedSensitivityFieldVariableTypes]':
vars_list = self.response_function_1.GetImplementedPhysicalKratosVariables()
vars_list.extend(self.response_function_2.GetImplementedPhysicalKratosVariables())
return vars_list

def Initialize(self) -> None:
self.response_function_1.Initialize()
self.response_function_2.Initialize()

if len(self.response_function_1.GetImplementedPhysicalKratosVariables()) != 0 and len(self.response_function_2.GetImplementedPhysicalKratosVariables()) != 0:
self.model_part = ModelPartOperation(self.response_function_1.GetInfluencingModelPart().GetModel(), ModelPartOperation.OperationType.UNION, f"response_{self.GetName()}", [self.response_function_1.GetInfluencingModelPart().FullName(), self.response_function_2.GetInfluencingModelPart().FullName()], False).GetModelPart()
elif len(self.response_function_1.GetImplementedPhysicalKratosVariables()) != 0:
self.model_part = self.response_function_1.GetInfluencingModelPart()
elif len(self.response_function_2.GetImplementedPhysicalKratosVariables()) != 0:
self.model_part = self.response_function_2.GetInfluencingModelPart()

def Check(self) -> None:
self.response_function_1.Check()
self.response_function_2.Check()

def Finalize(self) -> None:
self.response_function_1.Finalize()
self.response_function_2.Finalize()

def GetInfluencingModelPart(self) -> Kratos.ModelPart:
return self.model_part

def CalculateValue(self) -> float:
v1 = EvaluateValue(self.response_function_1, self.optimization_problem)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the concept of evaluate. Why can't we just use Response Routine and it deifnes if smth should be computed or return from value directly ? It looks like dublication or bad design of response / response routine .

Copy link
Member Author

@sunethwarna sunethwarna Aug 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assume the scenario where "r1 + r2 * r1", where r1 needs to be evaluated twice. Instead of evaluating them twice, I use the EvaluteValue method to store the first evaluation of r1, and then call it in the second evaluation. I agree to your point that, instead of saving it in the OptProb, under respones, I will use a different place to save it. So, saving the value under the OptProb is decided by the response routine, and the call to CalculateValue will be normal per one evaluation. That means, for one call to CalculateValue/CalculateGradients of "r1 + r2 * r1", it will only evaluate r1 once, but for multiple CalculateValue/CalculateGradients, r1 and r2 will be evaluated once per CalculateValue/CalculateGradients calls.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for the gradients

v2 = EvaluateValue(self.response_function_2, self.optimization_problem)

# now do the binary arithmetics.
if self.binary_operator == BinaryOperator.ADD:
return v1 + v2
elif self.binary_operator == BinaryOperator.SUBTRACT:
return v1 - v2
elif self.binary_operator == BinaryOperator.MULTIPLY:
return v1 * v2
elif self.binary_operator == BinaryOperator.DIVIDE:
return v1 / v2
elif self.binary_operator == BinaryOperator.POWER:
return v1 ** v2

def CalculateGradient(self, physical_variable_collective_expressions: 'dict[SupportedSensitivityFieldVariableTypes, KratosOA.CollectiveExpression]') -> None:
v1 = EvaluateValue(self.response_function_1, self.optimization_problem)
v2 = EvaluateValue(self.response_function_2, self.optimization_problem)
resp_1_physical_variable_collective_expressions = EvaluateGradient(self.response_function_1, physical_variable_collective_expressions, self.optimization_problem)
resp_2_physical_variable_collective_expressions = EvaluateGradient(self.response_function_2, physical_variable_collective_expressions, self.optimization_problem)

for variable, collective_expression in physical_variable_collective_expressions.items():
for result, g1, g2 in zip(collective_expression.GetContainerExpressions(), resp_1_physical_variable_collective_expressions[variable].GetContainerExpressions(), resp_2_physical_variable_collective_expressions[variable].GetContainerExpressions()):
if self.binary_operator == BinaryOperator.ADD:
result.SetExpression((g1 + g2).GetExpression())
elif self.binary_operator == BinaryOperator.SUBTRACT:
result.SetExpression((g1 - g2).GetExpression())
elif self.binary_operator == BinaryOperator.MULTIPLY:
result.SetExpression((g1 * v2 + g2 * v1).GetExpression())
elif self.binary_operator == BinaryOperator.DIVIDE:
result.SetExpression((g1 / v2 - g2 * (v1 / v2 ** 2)).GetExpression())
elif self.binary_operator == BinaryOperator.POWER:
result.SetExpression(((g1 * (v2 / v1) + g2 * log(v1)) * (v1 ** v2)).GetExpression())

def __str__(self) -> str:
if self.model_part is not None:
return f"Response [type = {self.__class__.__name__}, name = {self.GetName()}, model part name = {self.model_part.FullName()}]"
else:
return f"Response [type = {self.__class__.__name__}, name = {self.GetName()}, model part name = n/a ]"
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@ def CalculateGradient(self, physical_variable_collective_expressions: 'dict[Supp
exp.SetExpression(exp.GetExpression() + (((values - value_i) ** (-2)) * (values - value_i) * 2.0).GetExpression())
exp.SetExpression(Kratos.Expression.Utils.Collapse(exp).GetExpression())
else:
raise RuntimeError(f"Unsupported sensitivity w.r.t. {physical_variable.Name()} requested. Followings are supported sensitivity variables:\n\tSHAPE")
raise RuntimeError(f"Unsupported sensitivity w.r.t. {physical_variable.Name()} requested. Followings are supported sensitivity variables:\n\t{self.variable.Name()}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import KratosMultiphysics as Kratos
import KratosMultiphysics.OptimizationApplication as KratosOA
from KratosMultiphysics.OptimizationApplication.responses.response_function import ResponseFunction
from KratosMultiphysics.OptimizationApplication.responses.response_function import SupportedSensitivityFieldVariableTypes
from KratosMultiphysics.OptimizationApplication.utilities.union_utilities import SupportedSensitivityFieldVariableTypes

class LiteralValueResponseFunction(ResponseFunction):
def __init__(self, value: float):
super().__init__(str(value))

self.value = value

def GetImplementedPhysicalKratosVariables(self) -> 'list[SupportedSensitivityFieldVariableTypes]':
return []

def Initialize(self) -> None:
pass

def Check(self) -> None:
pass

def Finalize(self) -> None:
pass

def GetInfluencingModelPart(self) -> Kratos.ModelPart:
raise RuntimeError(f"The literal value response function does not have an influencing model part.")

def CalculateValue(self) -> float:
return self.value

def CalculateGradient(self, _: 'dict[SupportedSensitivityFieldVariableTypes, KratosOA.CollectiveExpression]') -> None:
raise RuntimeError(f"The literal value response function does not depend on any variable, hence no gradients.")

def __str__(self) -> str:
return f"Response [type = {self.__class__.__name__}, name = {self.GetName()}]"
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from math import log
import KratosMultiphysics as Kratos
import KratosMultiphysics.OptimizationApplication as KratosOA
from KratosMultiphysics.OptimizationApplication.responses.response_function import ResponseFunction
from KratosMultiphysics.OptimizationApplication.responses.response_function import SupportedSensitivityFieldVariableTypes
from KratosMultiphysics.OptimizationApplication.utilities.union_utilities import SupportedSensitivityFieldVariableTypes
from KratosMultiphysics.OptimizationApplication.utilities.optimization_problem import OptimizationProblem
from KratosMultiphysics.OptimizationApplication.utilities.response_utilities import EvaluateValue
from KratosMultiphysics.OptimizationApplication.utilities.response_utilities import EvaluateGradient

class LogResponseFunction(ResponseFunction):
def __init__(self, response_function: ResponseFunction, optimization_problem: OptimizationProblem):
super().__init__(f"log({response_function.GetName()})")
self.response_function = response_function
self.optimization_problem = optimization_problem

def GetImplementedPhysicalKratosVariables(self) -> 'list[SupportedSensitivityFieldVariableTypes]':
return self.response_function.GetImplementedPhysicalKratosVariables()

def Initialize(self) -> None:
self.response_function.Initialize()

def Check(self) -> None:
self.response_function.Check()

def Finalize(self) -> None:
self.response_function.Finalize()

def GetInfluencingModelPart(self) -> Kratos.ModelPart:
return self.response_function.GetInfluencingModelPart()

def CalculateValue(self) -> float:
return log(EvaluateValue(self.response_function, self.optimization_problem))

def CalculateGradient(self, physical_variable_collective_expressions: 'dict[SupportedSensitivityFieldVariableTypes, KratosOA.CollectiveExpression]') -> None:
v = EvaluateValue(self.response_function, self.optimization_problem)
resp_physical_variable_collective_expressions = EvaluateGradient(self.response_function, physical_variable_collective_expressions, self.optimization_problem)

for variable, collective_expression in physical_variable_collective_expressions.items():
for result, g in zip(collective_expression.GetContainerExpressions(), resp_physical_variable_collective_expressions[variable].GetContainerExpressions()):
result.SetExpression((g / v).GetExpression())

def __str__(self) -> str:
return f"Response [type = {self.__class__.__name__}, name = {self.GetName()}]"
Loading
Loading