From b9764e1a1c15f81023b7008b18fe325941a48391 Mon Sep 17 00:00:00 2001 From: Carlos Ortega Date: Mon, 24 Jun 2024 11:26:25 +0200 Subject: [PATCH 01/13] initialising the repository with code from sostrades-core --- README.md | 1 + platform_version_required.txt | 1 + pytest.ini | 5 + requirements.in | 3 + ruff.toml | 8 + sostrades_optimization_plugins/__init__.py | 0 .../models/__init__.py | 0 .../models/design_var/__init__.py | 0 .../models/design_var/design_var.py | 200 +++ .../models/design_var/design_var_disc.py | 413 +++++ .../documentation/BSpline_example.PNG | Bin 0 -> 47016 bytes .../documentation/design_var.markdown | 13 + .../models/func_manager/__init__.py | 0 .../documentation/func_manager_disc.markdown | 61 + .../documentation/residuals_wo_smoothlog.png | Bin 0 -> 25589 bytes .../documentation/scheme_funcmanager.png | Bin 0 -> 54262 bytes .../documentation/smooth_maximum.png | Bin 0 -> 49005 bytes .../models/func_manager/func_manager.py | 391 +++++ .../models/func_manager/func_manager_disc.py | 1490 +++++++++++++++++ .../sos_processes/__init__.py | 0 .../sos_processes/test/__init__.py | 0 .../__init__.py | 15 + .../process.py | 50 + .../usecase.py | 50 + .../test_sellar_opt_w_design_var/__init__.py | 0 .../test_sellar_opt_w_design_var/process.py | 68 + .../test_sellar_opt_w_design_var/usecase.py | 115 ++ .../__init__.py | 15 + .../process.py | 65 + .../usecase.py | 108 ++ .../__init__.py | 0 .../test_sellar_opt_w_func_manager/process.py | 60 + .../test_sellar_opt_w_func_manager/usecase.py | 100 ++ .../__init__.py | 0 .../process.py | 57 + .../usecase.py | 98 ++ .../__init__.py | 15 + .../process.py | 71 + .../usecase.py | 102 ++ .../sos_wrapping/__init__.py | 0 .../tests/__init__.py | 0 ...optim_scenario.cpython-39-pytest-7.4.3.pyc | Bin 0 -> 2980 bytes ...h_func_manager.cpython-39-pytest-7.4.3.pyc | Bin 0 -> 2558 bytes ...4_func_manager.cpython-39-pytest-7.4.3.pyc | Bin 0 -> 19024 bytes ..._var_in_sellar.cpython-39-pytest-7.4.3.pyc | Bin 0 -> 6476 bytes ...n_sellar.cpython-39-pytest-7.4.3.pyc.14916 | Bin 0 -> 6461 bytes ..._63_design_var.cpython-39-pytest-7.4.3.pyc | Bin 0 -> 10179 bytes ...esign_var_disc.cpython-39-pytest-7.4.3.pyc | Bin 0 -> 7552 bytes ...tim_subprocess.cpython-39-pytest-7.4.3.pyc | Bin 0 -> 18638 bytes ...Test_dataframe_fill_one_column_for_key.pkl | Bin 0 -> 242 bytes ...ne_column_for_key_1element_deactivated.pkl | Bin 0 -> 250 bytes ...ne_column_for_key_2element_deactivated.pkl | Bin 0 -> 253 bytes .../jacobian_Test_default_dataframe_fill.pkl | Bin 0 -> 242 bytes .../jacobian_design_var_bspline.pkl | Bin 0 -> 506 bytes ...obian_obj_vs_design_var_sellar_test_02.pkl | Bin 0 -> 297 bytes ...obian_obj_vs_design_var_sellar_test_03.pkl | Bin 0 -> 295 bytes ...obian_obj_vs_design_var_sellar_test_04.pkl | Bin 0 -> 299 bytes ...obian_obj_vs_design_var_sellar_test_05.pkl | Bin 0 -> 329 bytes ...obian_obj_vs_design_var_sellar_test_06.pkl | Bin 0 -> 300 bytes ...obian_obj_vs_design_var_sellar_test_07.pkl | Bin 0 -> 295 bytes ...obian_obj_vs_design_var_sellar_test_08.pkl | Bin 0 -> 299 bytes ...obian_obj_vs_design_var_sellar_test_11.pkl | Bin 0 -> 426 bytes ...est_14_optim_scenario_with_func_manager.py | 71 + .../tests/l0_test_44_func_manager.py | 711 ++++++++ .../tests/l0_test_62_design_var_in_sellar.py | 174 ++ .../tests/l0_test_63_design_var.py | 214 +++ .../l1_test_01_gradient_design_var_disc.py | 276 +++ ...1_test_gradient_sellar_optim_subprocess.py | 643 +++++++ .../tools/__init__.py | 0 .../tools/cst_manager/__init__.py | 0 .../tools/cst_manager/common_config.py | 34 + .../tools/cst_manager/constraint_manager.py | 360 ++++ .../tools/cst_manager/constraint_object.py | 43 + .../tools/cst_manager/database.py | 144 ++ .../tools/cst_manager/fileutils.py | 32 + .../tools/cst_manager/func_manager_common.py | 260 +++ 76 files changed, 6537 insertions(+) create mode 100644 README.md create mode 100644 platform_version_required.txt create mode 100644 pytest.ini create mode 100644 requirements.in create mode 100644 ruff.toml create mode 100644 sostrades_optimization_plugins/__init__.py create mode 100644 sostrades_optimization_plugins/models/__init__.py create mode 100644 sostrades_optimization_plugins/models/design_var/__init__.py create mode 100644 sostrades_optimization_plugins/models/design_var/design_var.py create mode 100644 sostrades_optimization_plugins/models/design_var/design_var_disc.py create mode 100644 sostrades_optimization_plugins/models/design_var/documentation/BSpline_example.PNG create mode 100644 sostrades_optimization_plugins/models/design_var/documentation/design_var.markdown create mode 100644 sostrades_optimization_plugins/models/func_manager/__init__.py create mode 100644 sostrades_optimization_plugins/models/func_manager/documentation/func_manager_disc.markdown create mode 100644 sostrades_optimization_plugins/models/func_manager/documentation/residuals_wo_smoothlog.png create mode 100644 sostrades_optimization_plugins/models/func_manager/documentation/scheme_funcmanager.png create mode 100644 sostrades_optimization_plugins/models/func_manager/documentation/smooth_maximum.png create mode 100644 sostrades_optimization_plugins/models/func_manager/func_manager.py create mode 100644 sostrades_optimization_plugins/models/func_manager/func_manager_disc.py create mode 100644 sostrades_optimization_plugins/sos_processes/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/process.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/process.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/process.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/process.py create mode 100644 sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py create mode 100644 sostrades_optimization_plugins/sos_wrapping/__init__.py create mode 100644 sostrades_optimization_plugins/tests/__init__.py create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario.cpython-39-pytest-7.4.3.pyc create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario_with_func_manager.cpython-39-pytest-7.4.3.pyc create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_44_func_manager.cpython-39-pytest-7.4.3.pyc create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_62_design_var_in_sellar.cpython-39-pytest-7.4.3.pyc create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_62_design_var_in_sellar.cpython-39-pytest-7.4.3.pyc.14916 create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_63_design_var.cpython-39-pytest-7.4.3.pyc create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l1_test_01_gradient_design_var_disc.cpython-39-pytest-7.4.3.pyc create mode 100644 sostrades_optimization_plugins/tests/__pycache__/l1_test_gradient_sellar_optim_subprocess.cpython-39-pytest-7.4.3.pyc create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_dataframe_fill_one_column_for_key.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_dataframe_fill_one_column_for_key_1element_deactivated.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_dataframe_fill_one_column_for_key_2element_deactivated.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_default_dataframe_fill.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_design_var_bspline.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_02.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_03.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_04.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_05.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_06.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_07.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_08.pkl create mode 100644 sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_11.pkl create mode 100644 sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py create mode 100644 sostrades_optimization_plugins/tests/l0_test_44_func_manager.py create mode 100644 sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py create mode 100644 sostrades_optimization_plugins/tests/l0_test_63_design_var.py create mode 100644 sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py create mode 100644 sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py create mode 100644 sostrades_optimization_plugins/tools/__init__.py create mode 100644 sostrades_optimization_plugins/tools/cst_manager/__init__.py create mode 100644 sostrades_optimization_plugins/tools/cst_manager/common_config.py create mode 100644 sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py create mode 100644 sostrades_optimization_plugins/tools/cst_manager/constraint_object.py create mode 100644 sostrades_optimization_plugins/tools/cst_manager/database.py create mode 100644 sostrades_optimization_plugins/tools/cst_manager/fileutils.py create mode 100644 sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..2daac5d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# sostrades-optimization-plugin \ No newline at end of file diff --git a/platform_version_required.txt b/platform_version_required.txt new file mode 100644 index 0000000..bbbf720 --- /dev/null +++ b/platform_version_required.txt @@ -0,0 +1 @@ +v4.0.3 \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..82142fc --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +python_files = l*_test*.py +testpaths = + sostrades_optimization_plugins/tests +addopts = --numprocesses=auto diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..5b2b094 --- /dev/null +++ b/requirements.in @@ -0,0 +1,3 @@ +numpy==1.24.4 +pandas==2.2.2 +plotly==5.3.0 \ No newline at end of file diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..d0d15c1 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,8 @@ +[lint] +# extend-select = ["ALL"] +extend-select = ["I"] + +ignore = ["E722", "F841", "E501"] +# E722 Do not use bare `except` +# F841 Local variable `xxx` is assigned to but never used +# E501 Line too long diff --git a/sostrades_optimization_plugins/__init__.py b/sostrades_optimization_plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/models/__init__.py b/sostrades_optimization_plugins/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/models/design_var/__init__.py b/sostrades_optimization_plugins/models/design_var/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/models/design_var/design_var.py b/sostrades_optimization_plugins/models/design_var/design_var.py new file mode 100644 index 0000000..2d9bd11 --- /dev/null +++ b/sostrades_optimization_plugins/models/design_var/design_var.py @@ -0,0 +1,200 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2023/07/25-2024/05/16 Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import numpy as np +from pandas import DataFrame, concat + +from sostrades_core.tools.bspline.bspline import BSpline +from sostrades_core.execution_engine.sos_wrapp import SoSWrapp + + +class DesignVar(object): + """ + Class Design variable + """ + ACTIVATED_ELEM_LIST = "activated_elem" + VARIABLES = "variable" + VALUE = "value" + DATAFRAME_FILL = SoSWrapp.DATAFRAME_FILL + COLUMNS_NAMES = SoSWrapp.COLUMNS_NAMES + ONE_COLUMN_PER_KEY = SoSWrapp.ONE_COLUMN_PER_KEY + ONE_COLUMN_FOR_KEY = SoSWrapp.ONE_COLUMN_FOR_KEY + DATAFRAME_FILL_POSSIBLE_VALUES = [ONE_COLUMN_PER_KEY, ONE_COLUMN_FOR_KEY] + DESIGN_SPACE = 'design_space' + DESIGN_VAR_DESCRIPTOR = 'design_var_descriptor' + INDEX = 'index' + INDEX_NAME = 'index_name' + OUT_TYPE = 'out_type' + OUT_NAME = 'out_name' + FILL_ACTIVATED_ELEMENTS = 'fill_activated_elements' + LAST_ELEMENT_ACTIVATED = 'last element activated' + INITIAL_VALUE = 'initial_value' + FILL_ACTIVATED_ELEMENTS_POSSIBLE_VALUES = [LAST_ELEMENT_ACTIVATED, INITIAL_VALUE] + + def __init__(self, inputs_dict, logger): + ''' + Constructor + ''' + self.design_var_descriptor = inputs_dict[self.DESIGN_VAR_DESCRIPTOR] + self.output_dict = {} + self.bspline_dict = {} + self.dspace = inputs_dict[self.DESIGN_SPACE] + self.logger = logger + + def configure(self, inputs_dict): + ''' + Configure with inputs_dict from the discipline + ''' + + self.output_dict = {} + list_ctrl = self.design_var_descriptor.keys() + + for elem in list_ctrl: + l_activated = self.dspace.loc[self.dspace[self.VARIABLES] + == elem, self.ACTIVATED_ELEM_LIST].to_list()[0] + # check output length and compute BSpline only if necessary + # remark: float do not require any BSpline usage + output_length = len(self.design_var_descriptor[elem][self.INDEX]) + deactivated_elem_flag = not all(l_activated) + if deactivated_elem_flag: + final_value, gradient = self.rebuild_input_array_with_activated_elements(inputs_dict, elem) + else: + final_value = inputs_dict[elem] + gradient = np.identity(output_length) + + if len(final_value) == output_length: + self.bspline_dict[elem] = { + 'bspline': None, 'eval_t': final_value, 'b_array': gradient} + else: + self.create_array_with_bspline(elem, final_value, output_length, + deactivated_elem_flag) + + # loop over design_var_descriptor to build output + self.build_output_with_design_var_descriptor(elem, final_value) + + def create_array_with_bspline(self, elem, final_value, output_length, deactivated_elem_flag): + + list_t = np.linspace(0.0, 1.0, output_length) + bspline = BSpline(n_poles=len(final_value)) + bspline.set_ctrl_pts(final_value) + eval_t, b_array = bspline.eval_list_t(list_t) + b_array = bspline.update_b_array(b_array) + if deactivated_elem_flag: + b_array = self.update_gradient_with_deactivated_elements_first_value(b_array) + self.bspline_dict[elem] = { + 'bspline': bspline, 'eval_t': eval_t, 'b_array': b_array} + + def rebuild_input_array_with_activated_elements(self, inputs_dict, elem): + ''' + + If some elements are desactivated wit the option activated_elem in design space, the function rebuild the correct array depending on the method + + ''' + # checks that dspace and activated elements are coherent with input element size + l_activated = self.dspace.loc[self.dspace[self.VARIABLES] + == elem, self.ACTIVATED_ELEM_LIST].to_list()[0] + + elem_input_value = list(inputs_dict[elem]) + input_length = len(elem_input_value) + if sum(l_activated) != input_length: + self.logger.error( + f'The size of the input element {elem} is not coherent with the design space and its activated elements : {sum(l_activated)} activated elements and elem of length {len(elem_input_value)}') + + final_value = [] + # TODO compute the gradient for each case + gradient = [] + initial_gradient = np.identity(input_length).tolist() + # We fill deactivated elements with the last element activated in the array + if self.FILL_ACTIVATED_ELEMENTS in self.design_var_descriptor[elem] and self.design_var_descriptor[elem][ + self.FILL_ACTIVATED_ELEMENTS] == self.LAST_ELEMENT_ACTIVATED: + + for activated_bool in l_activated: + if activated_bool: + gradient.append(initial_gradient.pop(0)) + final_value.append(elem_input_value.pop(0)) + else: + final_value.append(final_value[-1]) + # TODO gradient is not null but depend on last value + # need to fix it + gradient.append([0.] * input_length) + # by default we use initial value to fill the deactivated elements + else: + initial_value = self.dspace.loc[self.dspace[self.VARIABLES] + == elem, self.VALUE].to_list()[0] + + for i, activated_bool in enumerate(l_activated): + if activated_bool: + final_value.append(elem_input_value.pop(0)) + gradient.append(initial_gradient.pop(0)) + else: + final_value.append(initial_value[i]) + gradient.append([0.] * input_length) + return np.array(final_value), np.array(gradient) + + def update_gradient_with_deactivated_elements_first_value(self, initial_gradient): + ### TO DO generalize it with other deactivated elements + + result = np.delete(initial_gradient, 0, axis=1) + return result + + def build_output_with_design_var_descriptor(self, elem, final_value): + + out_name = self.design_var_descriptor[elem][self.OUT_NAME] + out_type = self.design_var_descriptor[elem][self.OUT_TYPE] + + if out_type == 'float': + if final_value.size != 1: + raise ValueError(" The input must be of size 1 for a float output") + self.output_dict[out_name] = final_value[0] + elif out_type == 'array': + self.output_dict[out_name] = self.bspline_dict[elem]['eval_t'] + elif out_type == 'dataframe': + # dataframe fill is optional ,by default we fill the dataframe with one column per key + if self.DATAFRAME_FILL in self.design_var_descriptor[elem]: + dataframe_fill = self.design_var_descriptor[elem][self.DATAFRAME_FILL] + else: + dataframe_fill = self.ONE_COLUMN_PER_KEY + index = self.design_var_descriptor[elem][self.INDEX] + index_name = self.design_var_descriptor[elem][self.INDEX_NAME] + if dataframe_fill == self.ONE_COLUMN_PER_KEY: + # for the method one column per key we create a dataframe if it does not exists + if self.design_var_descriptor[elem][self.OUT_NAME] not in self.output_dict.keys(): + # init output dataframes with index + + self.output_dict[out_name] = DataFrame({index_name: index}) + # we use the key 'key' in the design_var_descriptor for the name of the column and the column to the dataframe + col_name = self.design_var_descriptor[elem]['key'] + self.output_dict[out_name][col_name] = self.bspline_dict[elem]['eval_t'] + elif dataframe_fill == self.ONE_COLUMN_FOR_KEY: + + column_names = self.design_var_descriptor[elem][self.COLUMNS_NAMES] + # # create a dataframe using column_names, in this method the dataframe will ALWAYS have 2 columns + # first column will store the key + # second column the value + + df_to_merge = DataFrame( + {index_name: index, + column_names[0]: self.design_var_descriptor[elem]['key'], + column_names[1]: self.bspline_dict[elem]['eval_t']}) + # if the dataframe still not exists werite it + if self.design_var_descriptor[elem][self.OUT_NAME] not in self.output_dict.keys(): + self.output_dict[out_name] = df_to_merge + # if it exists, concatenate it in order to have multiple lines in the dataframe for each key + else: + self.output_dict[out_name] = concat([self.output_dict[out_name], df_to_merge], + ignore_index=True) + else: + raise (ValueError('Output type not yet supported')) diff --git a/sostrades_optimization_plugins/models/design_var/design_var_disc.py b/sostrades_optimization_plugins/models/design_var/design_var_disc.py new file mode 100644 index 0000000..3734a28 --- /dev/null +++ b/sostrades_optimization_plugins/models/design_var/design_var_disc.py @@ -0,0 +1,413 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2023/04/13-2024/05/16 Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import numpy as np +import plotly.colors as plt_color +from plotly import graph_objects as go + +from sostrades_optimization_plugins.models.design_var.design_var import DesignVar +from sostrades_core.execution_engine.sos_wrapp import SoSWrapp +from sostrades_core.tools.post_processing.charts.chart_filter import ChartFilter +from sostrades_core.tools.post_processing.plotly_native_charts.instantiated_plotly_native_chart import ( + InstantiatedPlotlyNativeChart, +) + +color_list = plt_color.qualitative.Plotly +color_list.extend(plt_color.qualitative.Alphabet) + + +class DesignVarDiscipline(SoSWrapp): + # ontology information + _ontology_data = { + 'label': 'Design Variable Model', + 'type': 'Test', + 'source': 'SoSTrades Project', + 'validated': '', + 'validated_by': 'SoSTrades Project', + 'last_modification_date': '', + 'category': '', + 'definition': '', + 'icon': 'fas fa-drafting-compass fa-fw', + 'version': '', + } + WRITE_XVECT = 'write_xvect' + LOG_DVAR = 'log_designvar' + EXPORT_XVECT = 'export_xvect' + OUT_TYPE = 'out_type' + OUT_NAME = 'out_name' + OUT_TYPES = ['float', 'array', 'dataframe'] + VARIABLES = "variable" + VALUES = "value" + UPPER_BOUND = "upper_bnd" + LOWER_BOUND = "lower_bnd" + TYPE = "type" + ENABLE_VARIABLE_BOOL = "enable_variable" + LIST_ACTIVATED_ELEM = "activated_elem" + INDEX = DesignVar.INDEX + INDEX_NAME = DesignVar.INDEX_NAME + DATAFRAME_FILL = DesignVar.DATAFRAME_FILL + COLUMNS_NAMES = DesignVar.COLUMNS_NAMES + DATAFRAME_FILL_POSSIBLE_VALUES = DesignVar.DATAFRAME_FILL_POSSIBLE_VALUES + DESIGN_VAR_DESCRIPTOR = DesignVar.DESIGN_VAR_DESCRIPTOR + DESIGN_SPACE = DesignVar.DESIGN_SPACE + FILL_ACTIVATED_ELEMENTS = DesignVar.FILL_ACTIVATED_ELEMENTS + DESC_IN = { + DESIGN_VAR_DESCRIPTOR: {'type': 'dict', 'editable': True, 'structuring': True, 'user_level': 3}, + DESIGN_SPACE: {'type': 'dataframe', 'dataframe_descriptor': {VARIABLES: ('string', None, True), + VALUES: ('multiple', None, True), + LOWER_BOUND: ('multiple', None, True, True), + UPPER_BOUND: ('multiple', None, True, True), + ENABLE_VARIABLE_BOOL: ('bool', None, True), + LIST_ACTIVATED_ELEM: ('list', None, True), }, + 'visibility': SoSWrapp.SHARED_VISIBILITY, 'namespace': 'ns_optim'}, + LOG_DVAR: {'type': 'bool', 'default': False, 'user_level': 3}, + WRITE_XVECT: {'type': 'bool', 'default': False, 'user_level': 3}, + } + + DESC_OUT = { + 'design_space_last_ite': {'type': 'dataframe', 'user_level': 3} + } + + def setup_sos_disciplines(self): + + dynamic_inputs = {} + dynamic_outputs = {} + + inputs_dict = self.get_sosdisc_inputs() + + # loops over the output descriptor to add dynamic inputs and outputs from the loaded usecase. + # The structure of the output descriptor dict is checked prior its use + if self.DESIGN_VAR_DESCRIPTOR in inputs_dict.keys(): + design_var_descriptor = inputs_dict[self.DESIGN_VAR_DESCRIPTOR] + if design_var_descriptor is not None: + if self._check_descriptor(design_var_descriptor): + for key in design_var_descriptor.keys(): + # if we are using the dataframe fill method 'one column for key, one for value', + # a string column is created and should not be taken into account for gradients or MDA computations + # We need to modify the DEFAULT_EXCLUDED_COLUMNS of the Discipline woth the column name for the key column + if self.DATAFRAME_FILL in design_var_descriptor[key] and design_var_descriptor[key][ + self.DATAFRAME_FILL] == self.DATAFRAME_FILL_POSSIBLE_VALUES[1]: + self.DEFAULT_EXCLUDED_COLUMNS = SoSWrapp.DEFAULT_EXCLUDED_COLUMNS + [self.COLUMNS_NAMES[0]] + + dynamic_inputs[key] = {'type': 'array', + 'visibility': SoSWrapp.SHARED_VISIBILITY, + 'namespace': design_var_descriptor[key]['namespace_in']} + dynamic_outputs[design_var_descriptor[key][self.OUT_NAME]] = { + 'type': design_var_descriptor[key]['out_type'], + 'visibility': SoSWrapp.SHARED_VISIBILITY, + 'namespace': design_var_descriptor[key]['namespace_out']} + if self.WRITE_XVECT in inputs_dict.keys(): + write_xvect = inputs_dict[self.WRITE_XVECT] + if write_xvect: + dynamic_outputs['all_iteration_dict'] = {'type': 'dict'} + + self.add_inputs(dynamic_inputs) + self.add_outputs(dynamic_outputs) + self.inst_desc_in = dynamic_inputs + self.inst_desc_out = dynamic_outputs + + self.iter = 0 + + def init_execution(self): + super().init_execution() + inputs_dict = self.get_sosdisc_inputs() + self.design = DesignVar(inputs_dict, self.logger) + self.all_iterations_dict = {} + self.dict_last_ite = None + + def run(self): + inputs_dict = self.get_sosdisc_inputs() + + self.design.configure(inputs_dict) + outputs_dict = self.design.output_dict + + # retrieve the design space and update values with current iteration + # values + dspace_in = self.get_sosdisc_inputs(self.DESIGN_SPACE) + dspace_out = dspace_in.copy(deep=True) + dict_diff = {} + dict_current = {} + for dvar in dspace_in.variable: + for disc_var in inputs_dict.keys(): + if dvar in disc_var: + val = self.get_sosdisc_inputs(disc_var) + if isinstance(val, np.ndarray): + dict_current[dvar] = val + + val = str(val.tolist()) + # dict_diff[dvar] + # dict_diff[dvar] + if isinstance(val, list): + dict_current[dvar] = np.array(val) + dspace_out.loc[dspace_out.variable == + dvar, "value"] = str(val) + + # option to log difference between two iterations to track optimization + if inputs_dict[self.LOG_DVAR]: + if self.dict_last_ite is None: + self.logger.info('x0' + str(dict_current)) + + self.dict_last_ite = dict_current + + else: + dict_diff = { + key: dict_current[key] - self.dict_last_ite[key] for key in dict_current} + self.logger.info( + 'difference between two iterations' + str(dict_diff)) + # update output dictionary with dspace + outputs_dict.update({'design_space_last_ite': dspace_out}) + + + if self.get_sosdisc_inputs(self.WRITE_XVECT): + #dspace_out.to_csv(f"dspace_ite_{self.iter}.csv", index=False) + + # write all iterations into a dictionnary + self.all_iterations_dict.update({f"iteration {self.iter}": dict_current}) + outputs_dict.update({'all_iteration_dict': self.all_iterations_dict}) + + self.store_sos_outputs_values(self.design.output_dict) + self.iter += 1 + + def compute_sos_jacobian(self): + + design_var_descriptor = self.get_sosdisc_inputs(self.DESIGN_VAR_DESCRIPTOR) + + for key in design_var_descriptor.keys(): + out_type = design_var_descriptor[key][self.OUT_TYPE] + out_name = design_var_descriptor[key][self.OUT_NAME] + if out_type == 'array': + self.set_partial_derivative( + out_name, key, self.design.bspline_dict[key]['b_array']) + elif out_type == 'dataframe': + asset_name = design_var_descriptor[key]['key'] + # specific processing occurs in the partial derivative computation depending on the way the + # dataframe was filled + self.set_partial_derivative_for_other_types( + (out_name, asset_name), (key,), self.design.bspline_dict[key]['b_array']) + elif out_type == 'float': + self.set_partial_derivative(out_name, key, np.array([1.])) + else: + raise (ValueError('Output type not yet supported')) + + def _check_descriptor(self, design_var_descriptor): + """ + + :param design_var_descriptor: dict input of Design Var Discipline containing all information necessary to build its dynamic inputs and outputs. + For each input, data needed are the key, type, and namespace_in and for output out_name, out_type, namespace_out and depending on its type, index, index name and key. + :return: True if the dict has the requested data, False otherwise. + """ + test = True + + for key in design_var_descriptor.keys(): + needed_keys = [self.OUT_TYPE, 'namespace_in', + self.OUT_NAME, 'namespace_out'] + messages = [f'Supported output types are {self.OUT_TYPES}.', + 'Please set the input namespace.', + 'Please set output_name.', + 'Please set the output namespace.', + ] + dvar_descriptor_key = design_var_descriptor[key] + for n_key in needed_keys: + if n_key not in dvar_descriptor_key.keys(): + test = False + raise (ValueError( + f'Discipline {self.sos_name} design_var_descriptor[{key}] is missing "{n_key}" element. {messages[needed_keys.index(n_key)]}')) + else: + out_type = dvar_descriptor_key[self.OUT_TYPE] + if out_type == 'float': + continue + elif out_type == 'array': + array_needs = [self.INDEX, self.INDEX_NAME] + array_mess = [ + f'Please set an index describing the length of the output array of {key} (index is also used for post proc representations).', + f'Please set an index name describing for the output array of {key} (index_name is also used for post proc representations).', + ] + for k in array_needs: + if k not in dvar_descriptor_key.keys(): + test = False + raise ( + ValueError( + f'Discipline {self.sos_name} design_var_descriptor[{key}] is missing "{k}" element. {array_mess[array_needs.index(k)]}')) + elif out_type == 'dataframe': + + if self.DATAFRAME_FILL in dvar_descriptor_key: + dataframe_fill = dvar_descriptor_key[self.DATAFRAME_FILL] + # check if the dataframe fill is among possible values + if dataframe_fill not in self.DATAFRAME_FILL_POSSIBLE_VALUES: + raise ( + ValueError( + f'Discipline {self.sos_name} design_var_descriptor[{self.DATAFRAME_FILL}] is not in {self.DATAFRAME_FILL_POSSIBLE_VALUES}')) + + else: + # if no dataframe fill default is one column per key + dataframe_fill = self.DATAFRAME_FILL_POSSIBLE_VALUES[0] + # index and key must be in the dataframe_descriptor for both methods + dataframe_needs = [self.INDEX, self.INDEX_NAME, 'key'] + dataframe_mess = [ + f'Please set an index to the output dataframe of {key} (index is also used for post proc representations).', + f'Please set an index name to the output dataframe of {key} (index_name is also used for post proc representations).', + f'Please set a "key" name to the output dataframe of {key} (name of the column in which the output will be written).', + ] + for k in dataframe_needs: + if k not in dvar_descriptor_key.keys(): + test = False + raise ( + ValueError( + f'Discipline {self.sos_name} design_var_descriptor[{key}] is missing "{k}" element. {dataframe_mess[dataframe_needs.index(k)]}')) + + if dataframe_fill == self.DATAFRAME_FILL_POSSIBLE_VALUES[1]: + # column_names must be in the dataframe_descriptor for 'one column for key,one for value' method + if self.COLUMNS_NAMES not in dvar_descriptor_key.keys(): + test = False + raise ( + ValueError( + f'Discipline {self.sos_name} design_var_descriptor[{self.COLUMNS_NAMES}] is missing with option {self.DATAFRAME_FILL_POSSIBLE_VALUES[1]}')) + # only two column names are required + if len(dvar_descriptor_key[self.COLUMNS_NAMES]) != 2: + test = False + raise ( + ValueError( + f'Discipline {self.sos_name} design_var_descriptor[{self.COLUMNS_NAMES}] must be of length 2, the column name for keys and the one for the value')) + + else: + test = False + raise (ValueError( + f'Discipline {self.sos_name} design_var_descriptor[{key}] out_type is not supported. Supported out_types are {self.OUT_TYPES}')) + + return test + + def get_chart_filter_list(self): + + chart_filters = [] + chart_list = ['BSpline'] + chart_filters.append(ChartFilter( + 'Charts', chart_list, chart_list, 'charts')) + + initial_xvect_list = ['Standard', 'With initial xvect'] + chart_filters.append(ChartFilter( + 'Initial xvect', initial_xvect_list, ['Standard', ], 'initial_xvect')) + + return chart_filters + + def get_post_processing_list(self, filters=None): + + # For the outputs, making a graph for block fuel vs range and blocktime vs + # range + + instanciated_charts = [] + charts = [] + initial_xvect_list = ['Standard', ] + if filters is not None: + for chart_filter in filters: + if chart_filter.filter_key == 'charts': + charts = chart_filter.selected_values + if chart_filter.filter_key == 'initial_xvect': + initial_xvect_list = chart_filter.selected_values + if 'With initial xvect' in initial_xvect_list: + init_xvect = True + else: + init_xvect = False + + if 'BSpline' in charts: + list_dv = self.get_sosdisc_inputs(self.DESIGN_VAR_DESCRIPTOR) + for parameter in list_dv.keys(): + new_chart = self.get_chart_BSpline(parameter, init_xvect) + if new_chart is not None: + instanciated_charts.append(new_chart) + + return instanciated_charts + + def get_chart_BSpline(self, parameter, init_xvect=False): + """ + Function to create post-proc for the design variables with display of the control points used to + calculate the B-Splines. + The activation/deactivation of control points is accounted for by inserting the values from the design space + dataframe into the ctrl_pts if need be (activated_elem==False) and at the appropriate index. + Input: parameter (name), parameter values, design_space + Output: InstantiatedPlotlyNativeChart + """ + + design_space = self.get_sosdisc_inputs(self.DESIGN_SPACE) + pts = self.get_sosdisc_inputs(parameter) + ctrl_pts = list(pts) + starting_pts = list( + design_space[design_space['variable'] == parameter]['value'].values[0]) + for i, activation in enumerate(design_space.loc[design_space['variable'] + == parameter, 'activated_elem'].to_list()[0]): + if not activation and len( + design_space.loc[design_space['variable'] == parameter, 'value'].to_list()[0]) > i: + ctrl_pts.insert(i, design_space.loc[design_space['variable'] + == parameter, 'value'].to_list()[0][i]) + eval_pts = None + + design_var_descriptor = self.get_sosdisc_inputs(self.DESIGN_VAR_DESCRIPTOR) + out_name = design_var_descriptor[parameter][self.OUT_NAME] + out_type = design_var_descriptor[parameter][self.OUT_TYPE] + index = design_var_descriptor[parameter][self.INDEX] + index_name = design_var_descriptor[parameter][self.INDEX_NAME] + + if out_type == 'array': + eval_pts = self.get_sosdisc_outputs(out_name) + + elif out_type == 'dataframe': + df = self.get_sosdisc_outputs(out_name) + if self.DATAFRAME_FILL in design_var_descriptor[parameter]: + dataframe_fill = design_var_descriptor[parameter][self.DATAFRAME_FILL] + else: + dataframe_fill = self.DATAFRAME_FILL_POSSIBLE_VALUES[0] + col_name = design_var_descriptor[parameter]['key'] + if dataframe_fill == self.DATAFRAME_FILL_POSSIBLE_VALUES[0]: + eval_pts = df[col_name].values + elif dataframe_fill == self.DATAFRAME_FILL_POSSIBLE_VALUES[1]: + column_names = design_var_descriptor[parameter][self.COLUMNS_NAMES] + eval_pts = df[df[column_names[0]] == col_name][column_names[1]].values + + if eval_pts is None: + print('eval pts not found in sos_disc_outputs') + return None + else: + chart_name = f'B-Spline for {parameter}' + fig = go.Figure() + if 'complex' in str(type(ctrl_pts[0])): + ctrl_pts = [np.real(value) for value in ctrl_pts] + if 'complex' in str(type(eval_pts[0])): + eval_pts = [np.real(value) for value in eval_pts] + if 'complex' in str(type(starting_pts[0])): + starting_pts = [np.real(value) for value in starting_pts] + x_ctrl_pts = np.linspace( + index[0], index[-1], len(ctrl_pts)) + marker_dict = dict(size=150 / len(ctrl_pts), line=dict( + width=150 / (3 * len(ctrl_pts)), color='DarkSlateGrey')) + fig.add_trace(go.Scatter(x=list(x_ctrl_pts), + y=list(ctrl_pts), name='Poles', + line=dict(color=color_list[0]), + mode='markers', + marker=marker_dict)) + fig.add_trace(go.Scatter(x=list(index), y=list(eval_pts), name='B-Spline', + line=dict(color=color_list[0]), )) + if init_xvect: + marker_dict['opacity'] = 0.5 + fig.add_trace(go.Scatter(x=list(x_ctrl_pts), + y=list(starting_pts), name='Initial Poles', + mode='markers', + line=dict(color=color_list[0]), + marker=marker_dict)) + fig.update_layout(title={'text': chart_name, 'x': 0.5, 'y': 1.0, 'xanchor': 'center', 'yanchor': 'top'}, + xaxis_title=index_name, yaxis_title=f'value of {parameter}') + new_chart = InstantiatedPlotlyNativeChart( + fig, chart_name=chart_name, default_title=True) + + return new_chart diff --git a/sostrades_optimization_plugins/models/design_var/documentation/BSpline_example.PNG b/sostrades_optimization_plugins/models/design_var/documentation/BSpline_example.PNG new file mode 100644 index 0000000000000000000000000000000000000000..493c9379577aa19fcbc9c422034c4dcb72705306 GIT binary patch literal 47016 zcmeFaX;_nI_CF582yGSU)KQcr)UkCLR8(YNqC+d=C}M{xt1MPolvNM|0TL}LZB@{U zpdv&|1uaVu1Y`+esiLeRn+PEwge{OjAOy1h&mGiSh3EVB|Kiu{>WlUw&vW1RIrmvU z=X1{U$?w_&+y~{RIC*K{52dHvdw{Y1AHp z|M;uVF56uS3isoeiVrP;|Nc1m-_D^53ToBpU&WfByGIlhiY)hhwd;T3?qibJ(u0mS zbO(E6Po969cVurzp8p=gk)uSS*}mb_)KtZ5d%jwCNpv0wnHz!@>4JrQq9#on<+}I|KWxWopnvT>Q(obBnvmIk8+ru913l+ZMMWRN_?U? zeEPa;PpVKbDLCkW6Pi4ni4Er$j!?L4I@?p!+gw-9lm-6Vnyc73Z27qsO~b%5+obti zOSVz7=FW@S6{q~C-3e8F-sacDiMI!GVyZW@ZtpFxxio!inNt^Kq>%TohosT)s2~3jN_DVF+()i>Y6`sj65y3t~*@u zNo1M;-hg&{es!+HP2cJfhh$&XTf5afVdqqH=1CRyqxis1*2|QO_@bgUe&)Am+1)0F z1Kr~#L8USLpm9$cZt=Sbt_P(3|S?a|87Ywdv3NA2?+(;Aei(KZQ#4PLLmr_+#vPxxQ zCZF#2+pm~=D^=S>H|jrh*nF%1c)+#du3N{~DYXZkOE)KXq%zH={?nlxqn#4shLSqQ zFXpmN9GTm*Pu65{#oSJCQpL|?;L8>AwIc!5pDK9umDc_uxn6`QAjM&Ptdhm#4c!h> z&Ko~`R-k=`!+X)EYv#$YhUc1k;2907Ft(O#{(3Kr*gNi%EjF^cM@lD`)pZQ2 zWQKCoZk#{1AJ*Sy;A2ilJl4vGkxucW6G+0*=Nn&f8l zL`V@OVyJSApz74cXN-q^Zth)C+auTYMMR(3kZS#nrz4g^9|P58evNf!0(i@u1kxW^ zI?Jxjt;BlNYxPGm+osh1)A9fl#-BYAP|uFN8Lloj%{L9cdFuRb(NFy(x|w^o=fi=3q?p257kcFgzQv$Py1x;>b~A- z(QFafyoQmr(@^d{K2)Wlitu2*o_)OVqH?-u^rm0nGTD|>oxd-l@H2ErrtDX7ny&1q z$rqB_q{ty!AM@j_t|rK;e5i?yQbEyr{iU&KqH4UjIli`zo3m8reyX!#vBy*Ef=czY zFK`<0v(0w3ca3_~iafhj`v(q(A|2`GrO~5*6F=%> z*D-MhH+gky%QukC1T3CRIAu`XG{$-E%2`HMKDB3P(Wm_qs=qAuwzD-;k^9Rg@u z=bP(B8i`F6)?JtTwh!eurpx4q_mXF>tgU5gx2VV7i<}A~&y`M;y7+j69-hrt*tdNV zZRx}ZmdNf0fAmuLw7a8f%_w2chRi!ZA=*8-#nMphjP=U8M2{6}yIkpJJbbKDE0?EF zCWXvKuayl_$vVv+7Zw!ifR$Z0GU5i4*IjMf7rgabtTIyYLKNz2et--}&K{lJq}mY5GLv z)>_$w4Ss2ChQTCW#(D&=G$QlKvDm+?(h6Ftax0r|5oK+%Q?f-S&Zs%VH0G_h(Or(c zOHpdXHtp{Vr4kRu-_;J#8=BOj*Nb!7f0^3y33(-k3e=eE2C2s0G8c8OA%CO-qHH3U zd@fFHUqR@`ej^2MHk}*%=}X}dKE;uA{P*_YYbB**K^?oA`=WEXzHAMq3gMHw^Zo&G z#+a^j*oTFeG_37A5H=7LcNA2Lp(h)tAulJD^QX?9ucjz^K9|P?3*BD6&-pl2m0wCl z8hG6;DzU=q6E;)=PY{a4NsrZ_C5J_6e@?HPuCom5;1+IK8X7a(JI)in3(` zIkNtixb1RREf|+3g0tw+@%3lO>nr^G7P*irkNYxG@7H-wi-hrXQt;@OcG-9bT{hSx zZPO=xlN#w~J1bDEU%80JhW6eJZuTl&W=oH6PRXR3jnWh7rs*4H61%v6;$3rnM<=VB znMzxe7qf+@3trv#sg;Q{Jz}llhEZj&#P>>_;!UJ=d4{urr#cr4GStfAjRN$RHqo|f z|3XUE)rmM^+IK+~ee4O5dDwQAp^$%Z5ksPFp8hc#@_v{BagtXi<@uGEe$ z&g8MLkn9^Dj+TUQ?VIeP2PJ$-$nf`vOrs`qqsqRNf{9+t1C->G*yN4m*@XjD}Bg&>;a;$ou>&5Eh60J3s)rr`a z|6Fw~Wf7f|Kl({@yrDhee(Le&&kw3xJAv_Nmm_R7x@n~NZf}i4@%a_kqOl%Jo2$M- zx^5oi6lX&zt@0fft!5OP1wGBJH}!UM zCpV>if4Z}J5gU$DEzuS`D<%bG_`89!J=YtZ^$v_(zT9^+dc+e{uxTxB#j<)e%s-8K zc-v9UZR;j<-UV9yehTY;Tts70j$>p`5SJru`Xy7j&#dB$7@~D77U7|FlvCr2 zrfVUQX0A(9)-i z1$py-sv9_bwR)vm#s#TqiE^*!Vle^|%1lx-BQvekN&jKUr#VX6`>-}B-AU=X_@wR4lmv7nMZJMF%i? z^fGQ5lXTf!U4fNDZw2O)3k|i1tckCyLx!skMfhZ+$DhM^eEV6O)Db(zqmqFOyE^kW zmn-+iVx3D#Nb|QK7-o2e%@QRM1?$0ARVn=1sw=TxjoYeuuadzejCo*OFqb6vh*G?v zXA1p@i6QdjFEp_Jx+Bn~bD!lu!>6D0`s#RkU>QBPuDoo={Z^4;tM=H`87l`*8P*Qh zRf=>4b$#d0+3%KVyD3dXV?FxHtIG8?@t@!r4JBd7itWR4%~uA>Sm^9Ris0<<7ckDw zXit04_|>_+=e$6rB3J?7EM`)${{EmrQHNsrLWay|Phqx;d*WtSBdgwFoO!%}am(N2 z=b#+_k$e6xn?ypj_U5!RNmR{vr!+a*S+IvNgA5IGQUu}B4-3Beh&Z*)l1VRdZO@wZ zt}qkoTVA;XG02ECUN%LS3yC8>)R{ZNt@cwmY3ypQ?&q=(vZhF0gYhr>z-ZWc|YT7rAH7uN-w0LHxMI-Dm@a^fwQs-2CQ4YlWewT>kX1 zdY!ENHRklgVYNSPzj1rNx?2sQXn%IJ*~wBULUV1*404ozjPC}N$phiEQo(r~9c z=EG06+4c+WOF1jygpX?4BSE+Y#X?qBt+V1dia15d4-4409N$UHuw`8shm_T{*!a{SdxAj zCH$JY2~<2ZZgrvtbNa65liCID?3?ONX0sNTj{@fH6Xvu#{(QFldD z4{z5)wg2NU+sA1kwGlD**07cA&}sBzh`Y_d;hekas~3>`Hs_Yf?hEe!YaNyZ$8oL< zq;~b^RlNF<+F@pR%SePeu*E|jebHH$&SEH1Vr3#M`tjGGqdg%-un4UD)n%oY75nO= zy*1YD>rT`TLu=aic4mGDj7Y0JlW+~;zVra_a2PlMo9{Sk2J(+t99ZMzFLLAvff?DyH)o>Y>H}R|e zPCdo44fivQncO7Eumc ziX&Jd=`1OSo(5;K#nR`1Q8ZZN6HOT6>=$*G&>S1fPk^@2hJV`?uKLUpYb{)EOH(B4 zp*`H86$0Zv!<|JK7Tj>ALBtlZ?)F1yx~)1-*hYfK>tH=TTENp4dT}5%f}2z(xeZ72 z8P?zH4wl#4xM#CO-y*`PJ?J7;QOyeT&pEYPpyu{Xx(N+kul!EMU@2FBY*!x5*zll^ zs+(7n?=Ecc1A{P>IL7bJ8i!OS!qIzizxqzpPmumX3vi zT$K3nGbQ&F*?+51%TUf~Hmo>}dF^D3xt++0Hm{8_j#}xVr0s;Yc9q9pJ6;4U`uOX4 zfF-zhf#CM7h1On`Tt0gB&d`O}td8!NXsdBpTRokB(w@4>B*Mh-Nc4%Tg8NmK^Td%4 z>uxBRIPOWCB+<3YeJzee4{s#oRaL&iaJI*LX~O;R*Wpfs^;(HGl(6-B+RHF^TI2hK z_~MtzOZ*>)RGs^FhGL+BCgJYHOb<0TlTD#Rs&y^;nt4^HF_#`}DHC>Bx6f7QjjphE zz{EHV)ye@oxwNEuMPubu6#B%^4T<+eRqU!i#NKhOv`1nhYkr-wxyzgyrhCi4Go@OkWCCOcLP9eQ=gfAR^I zGV))D+j1cCpxbop!ivN-!&a+)YlDE*upRz7j808Al>)2_f42#|P8#7pB$sJ~e9gR{ zu?}N*iVixVd9fQ1ml(YRnBRGdOU(!`k+`L6DQ0 z>yAyC9)?Hm1Y(_cvBlJX>Dnd1CWgMzy+5MuV&$Zlo7j*#Hm_fnfmVd`mflYg;+7=6ZVxRBJ06Y2tdB{QLBAvI~lfB3o8>E zD2#BW9t`0l6Y}e!2zCr9T&La$Yu6XLNgf6e_WL*IW|7)1UG2mQ{bJe=XNp+hf{;zO znD}bByHY+@MyYHb$xk5xp3~RLK#o5lyQ`nQXt7_8Q{|Ql@SLPab&t7TmZImoI%mI* z(pi*kB4>yGgIzRJn->bU=p4q) zTcejBgAu(b8%$0ZNVXGta_c+$POrZ)p?U2KED6-NE)ALnKseT(knbMpYF`)_PmS4w zrF)fv$(dNmJpsDHJ|1QpwCF!jV{)+cVj?=5(W;AVri<$C5LW2E-&uj$%b1|!iXrHu^_B7()JB1W9pwL8Ng4ZQw0P@ zLoH^+Us<`eB$UtdtKoX3n3J6XON%zT7S(iANilV=a(ZA&0%&@Fsk5CW`GA#pN4Xkl z14g8vs#e&1AZ!OpifpReo1Yn$sF6Ox`umctM2m@WfuVi;Qk<%bK0RfDLPI@{rw!;NiqR2}^Ga8Pg^dQdtCib7|vW0_;m|6b^M zWJX-i_I+kXv|$gsfktgzjwO7Je|1>}&dBev2YuK;6VtDPEg#0``EcNovd}sGdszZj9 zY&$g%+U&q zDDfGw`P&#?-?>>AZ+$G;Z+}@*tz>7!y0#p`1q3k$2#oCGm&fJ!%wS3L^L%t{VMWHQ zOLP6Z*st!y@TSk>v<@m^y=(X?p@Uy{UBeD<6h7UQhCiRH{r!Z91npp0R`%PMvD(Nfv!Y1BC}C;KLq z$QtLBmU8shA3Wha*ZKX|_JfpY_u{ektE}5Y?wkFrB-_RQd*Adfyx#-y9ovXs%VQ^R zA7a-i>zfk}NGERZVk3HEy)vCVcxO9dc^*ErZjpu6(NCd^sA>1cVsA!s(x zm9WZkUAS})c%gVRk`pH~OYXZ`0EzVcuO+XF&9bq<>8S0e$CIT~cs*mW^m0(S<6^t? z4(!*Ug|yVB0}^lHYdJZ+3&4$d)zq)lJS)B^nMqZtJ~X0!-+%5~duH~V9ty;o-u6z1 z29v2mnXX;p7^kac5i(*JbB|ECyW_bk+L^_4cV^$I6Xb_ug;$B{|F(IvjCY%>a=EC) z9)}Nr_XCjGJ-5@|DCj(ug{Um+z%I(@JW8C&cI`NhzHXN63vsF9F;>}Yw*I>fKNafv zUXj%9sejwYnFWFoMK)2)#%XmM!b*PLL=*oOyrINcHn!d-kv!RQcAid790*F>^sy}P zl#A(tYa?edpX)g?S`&(=9>=s#QCID={EtaVq7vi^*ZwZBlKji$QwD=Bo_op~iY<2} zMNi9s?mKKF73YSrm}1tQKA)3>O4XvXc%_{eZ@xJ|)Zum6(Cbm2msjT#R%TrxRkq`G z)71>MsC~L_L}Ndl681=9i*SoXr5f}yx;rm9cvVS0Uf7mWy6oGv^E_a2zsFfhkVKs< z)$pn-lm*R(DzrY1c&F)PNoV1)2|%pCWYNNKI#DQ` zywY2J(c^5)vlK}zz1%Np*J#n)$Qo)akmg0Hq&2``Ki2>5vFEMI-Cqd3alyU*z@mxI z9!>hvCr%|hbWpVpGK>V{9%En2XAp<%nV6WOp0$3~4yW|o^4wh}iz(p=flv>Xu5Fb& z)f!Q2A6hW9TKUz^w0RZV{MGIOdT_9XD1?6VWaYF@w$H&G8#3Lu`C1|>)SlqE00Md< zMYr%|Qs2lvNwu!TLR@}%?S6B;57wEO<$S+R6l9c-NIrO!u<%DTT%MXUMF{7u* zUwE8sWL`~jmx;EEOJ|8Q7aNtoX%#ILI)n~9$U zE1047g6Hy=2*_cfvnknOuF}xiDTUVG-_@Hpor|ZQ<^m$!9qwZvXL3?Zw!lN7_F$-= zy%oT3O+}FOud7_M*G?661Pt3rL*gqz9GUnokw98U7k>>66Mt66KOW0_>O%{u;U8ukj@*+!Ek7&(Cg#j$iEi^a20;+QPAI7AZfHdHo)-&@Yg=+z!$H zUz#j*VYgm4itf}CShgns&y*?FZEd1O9dM6oxfQk!X-+P0@>}#wE-8Ys1)h>0I3d=&3qoO*)=y=yu1YYg=Hb@zts)Lr87VFz$gc^i|9 z9(DMDyEWr*RPi+FC`?bF(b(f(A1rA<(jI~9nEk*K_Y2_-NjF*{-yBp&y zBv}(a3U@CryyOm7KOyVqOcf|RP0AFdPoT8@*gQEeFf=L}O7|6C%&{idpHpgPNUrG0 zO2nCX^8&GmUUE;|Oyz8n37eWWg9VO_?ci|8;`8(k+^8}`u}JaQz`Lg9O$%qmm_RBd z6+=KM_f9&Jp{vG5EJ?hmkG~ojtbsXv)GD>F1sS4LqS>N;VW(w#;Mi3**SEIsdb-DFVi75}^4kp3p!fbMj`sS|JGfOn4w*gTN9`8*+9#$O%z8fq7R>D5s_bab z^j!-dQIfNfvd=u3qF%*Y@rHwa&6dj~O=R~RI=9cQ-2TnQc|YXgMTn}Ut)(tQDJId5 zY*cG6C^BSa8-`i@>jf-NHE}cDO#2ooxq828T3Nx#798`=8<+NeOb~Gqw?{5$g)SsVPy-6?0@wEnJ5J2c8r zH)j2spRT(m>dE(gts+-SbTQuW!bK}%1d(-)rNkHQ2b!|HQ~RxLRn8J6%=_g>U)b8m zym?%y7DQar4|~(>u9ato0W+f*Lew5-b93C9u?6aF(Y3x6X3m7n-EW$|XS}Bh-6KZN zl1ziO!cBP(R zv3}b~A>qiJg^1N`!GA=YZwa}1L8u*DRBfXb$!b)&5h6a?691|DgA-0$9)`$m9^0sf zV!jZ`xrCXYomCzc;Y^dT$>UpJcjH$`XKK47JfO~pOz)5X6z5EAywy##vv-daRN!}m zrhd>aEV6_aYB%Vo@7>v1QtkVK2m$XV(@< z$NEIph=zz&t91joSJSy+0|WUgti;lq_|I^RC0fzaQp_cs2-xwJj;g(lbz5_A*w7)i z@2r_-RouDzX}A5S=N4iO3L=*|b(TUrRq?BH={;lq!`|k@F*fn)fw9?XHJ`-e^eu?> z6T`==n|)jtzyTN;SsV2dH|y!OM@gvmt0kR3>BzVPU%XMsBdECT~)!s}wmtAwy zWK$hAsMcP8x(=9*rxfSjzy6f%miyfDL!6zOZrH_1*quOo%AjHlj~e$??{H(&S$u4f-Q=6>N;`$%eY~mQWQv=56^9i;J$( za+Hp2DJLEn)b9-IHAB3(km{ejN4IqFUHiET*KEbFK3=Rjy;QfN$AhUKZ_%Rf*z+P` z`Tj1xBs-04)i=EhxkL+|KRs&VX%2JAR;}4b%m2{%cXO+q7t7fSg`~YfqntM60im=9 ziF|2KpBV`KOS)Eq2OE|dYqtI6m|?!UvU+OfBNuoCo9a%m9yVY)gTQk`m<-; zBl>C5*2shUCC3g7j?dsaP+!Q}tow;NpdhR_rGKMa2 zzDwz@QHuZU%`^nx|EhWFW^t3&Q@D1tO#aO}Wq@=9+9kMXaqzLvBl}ZqBVH9tTho3> z$K1^({?kN0WpDFy;E0`isk$0v zA$hpZwBMY@-E&GWgdn7DKMBHe51wedrCsWOU~tUn1gh)Zgiu|~Nldqx{hxi0_?AbZ z|9QIl-Q@T6Quh*2T>O?B9n|^qjbXd!P9nG!kN2&wgJxkj1n%&r{kREs^jSRJnaWov z?8|qL=(|XAp7XRvK3JPP>X~%fk3F_m#XkOXoSSip!O4bmv!YYPMek;X|AbiAYSUhQ zS-@QW*ENY`VGHG|j1w<2nXa+X{&wZfP?9tMCA&}1I@|(2<4%!~l@og-q_ZF<pwWUx6@0uj_jw8z6`!5bW?LX=@KzjeX)8*nihwV9}hu4XlCV_g%U9v9e{quV} zt7G49H6FxF@yNERYLu4eQ>0l330&(wfzJ-jvT~p*A)|$I$wpVJ4d-pRCsmcHh&r9& zwsivnSt4;qYp1A!IwRzD0z>8f!L zJIN-s^XNp$3AqC3m0ES*d*n|7c_ zQ8npRQX^dBj{AW&FYE zCRhUBQ<7osG`9r}!P#@XXBo3ph?k1b@NDQV-;lNouJRmhNX{hEusI7a6-s$QQr`0k zAwvQ=K`a@s(s-`vaZHCws(&p?N3!-Rdr9|9_J&OlrSY4?)T1oJb)v0=?w&W^SIg+e zX>+HVb!<-C>kbZh`9x!lYxSXFjuhHXEVXM3NESX}b2WHr*lcGCLtJsVuc@-qu;g&q zj-5J;O0$wHs0KXT*zOwy%!`{PYfql=85>sXY_2$2Jv1r!*Y5X)`^RnjcuUF1Oz@{A z`4k+_+a$b;L5&kK>+q76>ME|Ug!gcHH6`Zh*7;gm8lf<*hhavHg~D1+Gbz4DVn$RM z8+OZ0BUHupsG1XP{HJLL3_d(r2G>2MzVvm+4eTzsb+u1y3_xw7Os%TEmp$SmoH_s@ zq>m`Fh}qDsov8OogE^KY!gU>;AXcS{@IJ9$(^n^k>gy(GIqYEdWr%=wEb>aL)LZfX z?JJ?=LAG^|aDV@Q-^?@GGhq|Uk;JtiI5l$FGw)YSDUZ-6C~%i~TF>Th6=ijG>j7O; zAK_h@L4mcXde&USon!uZuaf;dMVZ<(U5B_NkM;YxXFGp?-x-tYhI1Ssbs0zLgPSP4 zj;sdbBV;TM$FXf2l7GafOno;SoxLL7a=Fx^_3(JQdxVrvDjvF=elX15em`Uw5Vw?3GzSpJt_B zwT(FOExl~+bfw;vr|)mj>PwOeoA+%Oq^=jfnIhgU zZoCY+usRvX6Arh)!5QyhLH9EB6$r%KHt1GG+!}eRjj}m^cp-lm-QOtuj`gAn`mCVF z{e+6%l>lhKaF!gSXGP_1MBz4y5GNi@lDWHMI?m^4CIIYVPjRMyDc%2qxp94Li!=w zysWek&wtU2%;R;98ZIG){5GOmztGnW{DH3SUHhIC{+Q9WuJRk7n>sLv zMJA;NzC65>AAoGrM5GH$oRM0HO?!7jqD+c-HAezcOgi-7-vWw7s*Gki`1>K#p}bwi zN4@M*yN{2nutj}@T_32km3PB`U^^0&Tdb@6+L(b;`xjv*-@51J;m=phvD2h@7DX~J zF=6gPH{qO_7&;Y$SX1)&vyNeFD&Xz9UUZ~MW~Cb@hUpnE(0!e3lkhktKzGk;7F_-y ztck82{-zRj#oh6DEhx)!GLSZ*xQ}MF`b|K;t`g0AiFwA506O>LutGt;Zc{|L_zGUq zHIZ*#IwGy9q9~QjAT1xfLnClFP`59lZmF~U$F|CWM=28^KoMhGwUH;Bp#nwAsghgp*DElsCqpnPn7<2XUjG99^f?=Rg zIrB=3Rb$h%R3+VjZ%5)?329)cpJA#`*VecaSx<*Vtdg$eyy=^}fDqA9dQfAtPGVio zYB^?Bgr9cm+cWs#@=5!0!R%NeZ*hh501*Ay45k++X#YkkwewTb`cF~VCYLH;MU7?Q z2h=6}m+Ue<*_p#Vo&qCH_L~MB07k`i&H6x9tESVp7lUcha6Pruu;jdT<erKHQxjcHG##Y z8}((kkyXGqmA?f`78;FtvX9 zktR&}fKJct7nScpZ0(zCrkTr$BCvfGARTUK-Fb4ywTo6wJ%8@@{qvy6n5V|XuO`4; zrr9XR<8;cV7};qyOXG3d#1+ed;2N9Ol&`l~NgmDU+$xGHD|W<16nbo-c^`U4sVohhqq7m#;}FNm|h972$B zx4_BXYW53h)NZW(-`axcp#4Bo<*}8$dVY4}23m4jcNNbRrlZ^&+MCE$N?4mXP-NW0 zO)@7j6I!;cf`T`od9j-{>7{(Q)w~gnNguyO;eMn?ycZMBRG>=H4<5I+hnYGZrLIl% z?+zf}0Mm7({UZJC8QCE1JCSHy%_=bCV$In0Ftj+Ry$zHsiNGe=$<1<7H$x>o;^HSX7()m)Y%j#T2&2=alJ9b91?C)g61B|)?Iu~k~pIeM7KEg3<8 z#N?2W^(qrZi?H(M!)Lh>zi#w1eoQgHh9zi4mFEcX0%8qfQXXPIf$1r=Zt7tKpG>U# z(d&tVRPoH?bm;X4(0sZPCQby_o@?rF(*6i@u`vPcnaNy!o}V(z?f?qYFe4l4ZyQjl zESdfjzKH%Fto*ifOTiG3{E{6+ANJa&8M$1$BVfU3&=BzRz;z4lt6vO<;?H2M8xbd; znh}lJdwf*OBhE~Gf8`uYXnq4FAQJ|ne_`s!k0_)HEX&`xbbl(zgZ*)aYAuSHW9(iv z)hA%S^ydIZ-&}{tYmRRV&DBtRLx)U@H)8cSOb(U59ZtKDA@VGIG3`aQCI_2QM~}ZGXNcUbI|WwEX4v>dVwQ{{L99S zR60ACkB~p>7sK$A81+*1*_+B{mtv739LGyW@Yg_;Y#C8(wT{G zKz|L3j9BC9jJ8keN$=8c{^XH9PYzd(WjnMDGWI0lwyBE5$q z<+Mp(q;5bhz)KXxYhPfjH+q}~jWR$umB1(+s5*Z?d5fQW7HA~}F)$^Ea}1En1t|^k z3B%sx=^j9mA!?EL#@YV0 z;wwN-L&L|q0poz;qF3Dv=&s67i*I+(Q_z#IF951m3LJ6I&wZ^EVW9BDQ<8?o6|v=8 zZgy9zo}R9M?PLa2fNcl`amu;PdmH_@A(> zQ14ra_A#%x*MIF14Y#?veBG_Qu%F+Q{-1i-qZB!njhG)6suozrCTT zyk6^OnsG^J7^=*l>;xgMr$EqZ(Tqr56vZDFBr461l=pV!q8U`E{EXUJLd$UvaL}Kd zQ*!TmNtdB;Sw$npP0247^vE81KnMFq8y!4wXAxSD5O#mzKc&k~Eq*}h=X$!3k)Qq_ zVvk5>f!MGr5%2`L{o3h{p>r>Z+%|#`1(+~d2szVjRYM1jHUPRpA(jPCul`))52D)p zqvUO~X+@;HRCJ|Q>LHo>(9?tTbl;=C#? zobIyK3sN`d4|qGj2jCcl`-QX~hXp^G1TPAn5KRy9IVHpp&Gwm|bSjZ`*+&RaTe_~U zg=R?)VEww$nq=j>v5T*EU8G!xwv>7wP;=f2Mm`0S7zjA@WtIIgw;dKgTrHY0s*`nx z5bWbj4+$qL_cpCgK&&(5Hx=@)EN*UxX>J#By5e4lSwjY=`(uXmS0_R%1!5Tcoa)4y z&fe2wDI?Mmr~@b|=pHaikn(f0__LnZ8s5fn@3NR)&ts1+QnwnpitzuxozKwKje_u# z2Yer7@UHp4i4Ye01cG;9^$;xt`V*bYPQWH>QnznF&dHAc172w#?v0y%m)JXh$C@LV zOlXpj<6*+HEivt2Y7aASek1O)~t(;=3#4votHUQdPf$e5O~ zV%=4&bCvY>H_%XWaGmgKI?@FV6hUz_4DKN-51+&QrQeQyI;!8=S^2>x({9i6G2F73>=yBfg>xM z1Y-$O#{$)skO&UjT_;6N0L33kr*k4gyxLRN@+RV1;6n(62?#S$5xo9yr_XZ&2|EzOcYbjh&5x*m4HvouPertJ&e@=U6Z`F)z?wyt-HP7MWP+m&NWv zNPlsjBO>dC3*VIdYE)hvbyRgZ6ojQZZGBg=X{hcFh_KKyd;LwL4RmtZ#^mw~ zcqQkzaLWSU6v?bh44}z*7$-SkuTUQY%5&tl^-rvtKS!imG)Jmq4<`vF6vb0DG0^Yk zUe?dFA>-WCo!=%F3%!r|@*?e?xswecuMh$ePH?pUx8%{dj$wlX23U$3@w)5n;|x2z zRe0r|`Ie~(XYuO0m)}uTv&_8otcOuyoilv8t&gj7^jsdW5})w$h@x4$x5N~AcM|Pp zD#Ys>TZjYqTewGc z7=30XErumH?{`lm%)v`vsohYoHjm2nWS~w0pqzjG=kKA^G+If@967|h8<*NaQj;3h z4((XK0oNiPuay5b*M#&5j;V-{%SkZQD_@-(C*9k06#j)uTq!w)OP~ zbJD7JPY)>+Jdcb@YCimZQ^}EuhFho+4Vs;BmJTK*xOFLSMK`F{?tIlvB@1{&`k6)X z(xXUr9b8X|5Rkw(6*&Y0fKt}CBI5INKV>hD7)2mUszV;}ZP6Ui2ir@rl&@a|H9PlW z&P=-Y{c!p1LY2rz#;PvYb>0=#ThRBlhrLfVifY$G;F9=D6uCSPRc8G-B=llKY`^tm ztY1!A2lTY-A*#Ug{S+{i&LB?>v4~aA zXCCTqSeCBl^aQ{eDCdIKZwS<(+JFdZXq?-F=ayLaOZ(rtQP1`0rFS6Q)&!zDbVebl zyraj^_m25ni7)USC6iUCl-tl6d>5Xm zCK>9bp!1e92?+9ngm$}h*;o+P3y0-A#G@MfASoae^*qSi2&C1HYUwXnuggKxc@9SL zU;7T2b(Zt6d_K5RTZVNm`+ib4o|bpr{qP7xe^aNi*#AH(nQ`l?TKhVNH3^Cto@Uzv<*Njc@k^SRbgBUBy0 zup9RXQXRbazd^B_A#u@eN%eTW2P2H<+EPc zL%LE1wKHgQAa&+hN|%LUg&Q_2T?yfcoId_N!0x}?sYV#c-1O9}LF*o@Gg(ao3e+Fk z4Qw&sLR}|j1)s1C!wRWvyb*{HLk{GHKTep)1g$@HY>X>ZZ0Mcn=}Rg8`F1*P^iwbl2D; zV-aF;cr|v!w8O|?dAa#Y(<}CMORV2j7DWcR(GhN`<_h60uH4s&MA(Z8IOVBNvCNaY zx3{F-y;xG7UT=OJ90km8*&-glRW_3&c+fcCBezfi1Ng(eqE7L*RK#-JZxIcV7<6CD z0dChA_H_#^v*yXnhAL!Z$q@)g#x}|e2xz9d<&|Ank{1UDkzeQor6a+5(&fN{t+vS@x-FOwO@x4tbgm?yQ?kR_qP8}w-J;@S=+H%#?4|p0iC5K)ysCXB z`PwE}C)(R947R2sQbe4S1zGnGLg?q|XW?dA} zGWa{O?yd?0^%UV;+w|eH7fA3PCL7RXUSh1@^lH*7EFZJg6t&%=3eaGbrLU#*!LEqj zAEER9pXI61SmsH6T~j1m$oeh$M#zgQ2!yUJk7WTFTy77`G1I=S#d4<&z(m<{6uei2 z?FiRV^-N=5UyZrYIn;WhZUxbdoi05iY#Jc*v@;&Wx?5o$SaDZOrxzfwK-)*b6QVUi z!3qUW#2Kv55vqGFh0i;nC`3E5c6PEdwq7?8=2PrrjA*)1u47%MyLIh4sBQsFH<0{1 zhY)^)X$|-`$wN;wZN12nkt+XZo;~MyChcK&-6Na+7qfZ;5tn0 z*M0*q@e42yCE`ye8;wZT%L#f;%lH=S@(tvBxl%2p2k8K40_xKA(041J(+(4qnp(fY z{N2nT#G!~Fjj|$``_|X$mXlWMmd5sbH@|xS>zeAToS~kIhNHr;95mp>gFT-%^DV8M zu-=!OkLD?@1r}paBMuV8Ml}S6X1b3gWmod^F$Lin^arkhFV94Cv{^ki$$67?v%*fy zcx%En{=LJMk4biGFsCO4EF>tub69<^q~?noCrkf{jk;){HCOXmK&&LmJ#vl?HCY<%9tIP{23O>6lQ2$53Qfg zX-6GJX_-Q#WfEdAcPLQMs7!^CY6cOHwr%>0*<3(-grc*THt5ketR;*x@QP+y7Rz~xi6 zZZgjDtrh8VnWZlLg?gJ`XR(U%oFt@at*vR_^rTxhgtMaOmC%n_1o*&IG?oEZTGxqE zi!Z@ity%h*P_V@$LO!YqNCq8EZ>#Pf)MX*&^E8?b52a>3q;EW*h-MN1F9Xda7;+a$ z{}1h{U*}42;{S(^c%y?b(a$yB-0J`eIr*-yn#M4*lC7@UozL`8AQZ6|%5l~jYIQ7# zYxRQ2BDZWz<>?GW{h_alNv2b;DqC-b4g8v~%o4;Kvw$g4=8k274qQQ#RuC=zZa=(p z?Mihy;y13I!99j~rCWVll4!hVEw;{pz`4l-MyS^6Hyq$97R!qw3qkj%Uee|up^dPH z$QE4>^W#oAr8G!rB{NWxYps%34i_>YNACAcxfgTqr^5skx@a(`y;&zm-J&zWj*#^= zMe*)wc{ODrwR_^-I$v7t2F25Wpg*nD6~J{K@W_%LfIWk zXxoTDkVZ&lR@Z|eA4TuA@-o#zx#gvX9wK*KUBp6k#wX}di>KrSL7003iL@xSL#ckH zyoj@q;(eaX9i`dIox%*7;=yb)-N$_a{Xx@QES8J%tL+h(WQ03oegt1-qaT@xr zO=4+sUhlw2sf(RSfL!6h=us4hf5s3Encss7GSH!isOQ@WQdFdklW9vtXsSV?4!e0s zK@IEGr-x~%p&90!K-si685+-FQUOYQMY@q^wm#d8vFg(t8k&^9mjv1MmTN$3K)nUk z;EAD^0P5!d#*k1M(xk#f=t5$TKuxo(gl&2rA8ev}%5uU4N6J_*0DK?iryKK;tyD ze({p?fsz+`auw9Cpt}_YXiN?%lz3_jg3c0mk=1D5A;x&7rWQ<{5->0rT$&cE_~B7o_A?5Q>Dc*;9%0 z69u5fq;E*!Cx=dzVf@&T-(y3WsZCMe!TQxeg}KaSs8K5s@$wL8kU^=T zD1{uCyYvNWqKciZFC&AVX4}}#Lh!{M|u}O=BFE@_}DRD6}ds zk@Gz_Z57F#`+`RHVoz7wAeD$(Xn~{#-)e~IIs}Z*bBKv8_e#LM+_cpk^8uxsyCPY@ zazj16^PTkYwIEaA($7iy<)ng5TZGawL4IKL%V-fMJjg)PkOs&*K@ zfwf{KpYg}?R1@_JLOs*c-z}Jzi>CP?C;-v4m%au!7Fo7h93R;El^HieIIL2u-QhsQ zN02SjALK5U%;;k+U4?nBiO6dHc77@UDelovIi5(}}i#eY_*{T=J)3>f%k3{(0M z&v%axhe%)}DjUjWQ9wJMK08$*ki%;W2L!~RJeaa)Y4bQ>HJU*431B4RP2%qpO3?iXP2f#R+pyN2UsD4k2`>AHPyvZG zy!<4sl>d8+iku26W=rnXsM%0d9%E|jen@Lz+NHb+2iltD;l%?%!Wq+!ZXuBCdm^1#YrgMwT3kSV=EHkV|9XMhh5HZKW# zfMIZHla*C>Bjt}n8jFLMF4WN{Zk@jR!;|t0zIS13N20y)3p^4fCLAj+r6Z&-Go?!= z=8~FBB_;Nd9a{ZW=cZ^VbJRF1-PL6H^Bk-AW@=c%(W|}Jx9l?t_(q=Gs#vl5)d&oh zPViuLHWlpN1Z%Byq7_Vn?$$=`MMZOK0a;}1cMT&uVyyy&Z5s=MZqrs5?5=P>D^5GP z(=Ip+3S|WQDKI}_yv(KV(hK+O@_b2n^h7BP)_P{TOZh^`?)vEp{^5Uibkdb-O=y5i8z-NkcM!_Ti|2E|C$1+~SvoO#z6 zBYYb~Vs#LY$I(Ftx#g?53F_2!+ejfuc1%?p)sf=`9pbiAQPE77d9?2#2h~@Bc32Sm zV)LV08^UCE+bj#K4orS{^!uR$Z;n85FyK9eA(hoVejth55L-1KVBH$;krA_4ok=dS z%#c^ZMwWcj!JOr_^Z(gMX`!9+BPf?a&L!wDx7o#oL%HFFfUR6vw2+jp&s}=JNrNq( zu)~jNoCeUoA@8z6N%jj(Xgg;S#N%}BGWtVqrz#E|-WuRZ2Zzs<9B!9Yriq3cEg(N* zyocVoibcQncS?>lFpm3~g6e8hsm`1wer`f66Ig{MER8$l;>5w{^E5)06VAY!Zuvp~ zE}J?YqIpRsQBEb=*>y51?b~V!0#B?_gz*N zc*jL}uezu5POU{;LoF6$sfLq6Y%2K`_LW>8sxTay4+&Dd5pAiP_0GZC>_m=*cLwKLmdqS zL=<$44B=?<)f+hDwcC&nVW$Sxk(P^d@_fJFj{RV_ahf-yZk3WT?VK_TR5br*z$vRL zN!NKkwM1}&#C`sn+!3F&lXXMnqdFJ{?PrAfRaifv-dTR9XHZc1mDHd#Ni!@hBHAOI zfb(Frf0=>dZDCIbY=aN^n3(anx1l`lhR|06uDt(UT8vgL4da@ZX73s?4A9q|sZHVS zQ56S(Pn#U9HR70x{?&Z16DehE?49?U8*YssD1v9ui-8uhkop}<$WRV$pMRxXxicmi z^|O$SlvzmR*)tN!Hk2fn4NIY!ztcf{Wr}W4JExQyBl&5nOrc1d*p!x_=t~9#dZ*!9N#XA&7;hqo*rr*hB(>RFQKIn}Ww^)mG*P*p;K47tJT+Gfx3z69pF z{7fqOHh`Ml&8v$fN}QLS2feA--730Gt<)YaO^NA9uSka_mwM6)?1_RmzG#eJSkMs1 zqM~`}mDIW_gx3!xlqQ6^a$y4L_0t-&E&caW_jX{wg@D}2&2t4d++VruBM`{+&G?!L z-O`11oE2_ub!hUdQ8e;xX2+>~F13L8_ORObrT-gJY-VzUCEqWr@dJ1xYPCk>zEc zt5QOi2o-d`^DQxDByid4(!drpZRD5(+k}CrG+NAZ*T^Pu?8ps$hRBQ4|D@uVjKt?x zmh!X13P@pd8SUf3v{D>uXLuTVC#$u!!020+xFa1GSj;>mu>_y7oXF=PsfBdt?D4%Z z(L?R9v_(2hgzG;{*zMcvK5=z#S4eZ6|3SmWIgRWH`z>zsJ$NQ%W_9k9mVud($Eqxm z3qX%4U<*hh%(dk3ZIQLZezZP)I#3t??gML1ehQ`s4*(2DJqB(71RPx@`s9w zNc^o010S?BKdViyy)%_u8xrKm0p29!Xb&k9c$bt*Pq|7XR8!O}*v#l?k*6-^CH|Km zX!o?+1L_ogP5}vekL5w}=r_k|y&FAvu z+1`W~pAeTl3J5DdA!>9somchyP99rXy#!7eS(?+7;jSimGvOI_<%ce5B>F-wF+bzW zT;k}Byh_fHi)gc_oDTRNlLIn!_0X0+99w*i72{vP!C>GewUHt?p|;Hy|7sQ1t{O-a zeXcBB&#Im=^2%2?<`D{@hUC}$sjPo`>rKcY z#&*wNhex2x-~%$;?7|M{b86DdDi;oDv+FLcdf3dPCH5(P9{@3Vc^;S(uGuin*hyaP z?HX;6D{mnK5%Jl5g|AJv>Lf1r@hA?74HBJq#BCR6mke<$Ob5CCCQ=EA=fYV}h^~?V z3-}EAm@sKs6ut|LK5<*ID+fd)rB=^ge5|Cb=qGVcqk@=Gpd9Ut8kQ!dPi`d-=n5%V zkIqwxz`V?ib7w{$1$7y+7B^pCf~i+qzY+7ONs8m3%c|2=Zlbp&xu^N7Sj?xyqfE`b z=Y|>pTWr%$+0RUk2W-UHD5kM&>+{w|6ducBAuc=o)(kls{3rJP-w19q19Q$W+(}v1 zT<2e9(OAslRJcTD8WSzcnjE^%JTdqtYZ^0kYWSrh8GwZS4FypMz7LWWS9P47M+gAV z012POn8;|E=F#G-lU>Pxc@26tQYx)*HI(~p6B6^` z=AJvK^v%~P_8k=+gzNE-e$hk#NGY=l(I0WWw2+bda=Z9b!7+wgN)PB6@RgDLOx3Qt zwpdVTLQ=+yA~>%04YM%j&G`qzp_ciBT$v?HRX|_Jnq>Z=zxB6*0FPR*zo&Pq%jP3$wL~mcK(7ht!sIpcmkb7yghMG_1jS8 zsNRNX&c4|J#7s{uCJGlB)DGOFD*LCxF3o;Uxx!dY7QvEl5AXqCL7(F&jOVR9ncN4JZuAv8ELi+hOv{M#fa1_a5&D01N>TOc2H% zL*AR(a6>d5YK2sUl-U8hcug@C>LQ{!<3c85Q_JW_7o_}!WgRRH6e`R(nOYYPDOpEX$fyuvT4=7s|j)+iVMV|C_!j0|j@ z1-&()JJ(eqE{yMPR_q5|rk1FlW9vQ|H3c}$S(k}dOh7%#ar~1^a-9#vA~`IjEM&Yk zz(nD9t&NJyDW>i{;Zj2NBVsyj&;t`P34b9eO{?f8U9*da9wdV+(vy-!7hP!^T{IaE z_Hmt8&M$km8FRc6xFBo>o_HF2AFL{#ENbK9*(5egPFU7c`BzoI0~a~MShtGsJ7sov z$#scjNOIESxMhc@@bjzHyi!PEknpVikL0=(UdNDzJo24igHqoCAZTi!LY<6~uyR={ z&RFA}wy!g$E*XQEW>MF;7d@-n&QP5T5*0D{I9Wzx^(xsgw>WuVbsR+F*bs89cgn){ zA{3uaL4^?O)o)i6f_NEzsZcDN@Mw!crw|TbeUpW5thhtwZmox0{)dc&6P&0x4u)Tr z6K4I4x=fs&2g66VJn!o4IiagU&)n`^>%m=DLjwMu;2OOI05&VX~o0z$Xx}8IXa8GKRBMaW^8u~H{ zggU6ZcvytvRwe~3{>-1H$K+d7mmJgodQF!~F2Q+aZ5yTir<@0CMLHuvV~r*p_Ry> zFA?Ycv8feygXErFp{I}_y9#<$VoM*}s=;ls%ui+v!-*~~B?A8GR2^E`xl5IsY{dC8 zrhN7sn{<|4EaYpmkx6HKICcj_-DyzYC#7k1GT}Ly1yKi zx+rghXcZ^FS}P_TNO@SQ^K0;^TPqIUcvEG#`sA*6CC)Sbb34T^D0fn<_*#F3#y?!z6<GUT5stgsS)5fxm0D96eIIpjFb3c zF>+s?-Lx-{t?cdu0N@*+arXrEzv)(>RZVlvO4T zv%X{lr_jFjmJO@D!-mySZ&Q&R4Ru#ttxRA8Gd;=lkj(NuqSfm?Bz++s(Ib7a4@bf% zQGUGG)l$2Ynt-0|CTfDFRp4#@Hy}DC5-qeGqmw_=bWg&;Een+UK75EJRT*y z6o@sLii0GUE2n>Pe!1x>pScqWDlDgm0Xxt5OdBNVb~(Kq*y-hOW$~G|ND#H0K6`$7fyv*> z<}>#rLCkXc!9`LMcMMa7uyj04vvgf$#V2jz{a6S$oq`Evrl3uPtewwBbFe@~Hn>r~ z`hS4wj0~U&0GP@WSL}=cpq#p0k_CY2pkKqLR%>Idk=NHXm;m5%?6}^c9MP>T)a?e) zHnFeGpIEo(1MF+9DzaH3;5;P2q`rCB{L;~#2jK$H`C zTW$3$C|2gI3;4w71EMd|&!yl}ffQbPVJ)hgjjDWrftwH?{xr$U8zH>PYfv*%^k^~u zC!iE7i)FaKejfi(q;BB>pnY=^!a5-?1s4JlB8ijGRfJ3m%Log1#JWF4zpW5%&Lp)a9s?*1iXRv{u;u(E zrVyv}#woT54-4;<=p%oZ!WzG-kl1MQO{TsRVm5bvZRt_Hw;Xv1AgqgBg0pBAxa{{O z;M;ddhT}hKc8+^hC$_a1{jpH^F{87YJk_^J(c|;hdPZ=Io(=_=#v9t*Wiz>;Px!0% zO5&wZplh*1_0X86mF`EbR8RLmu_Qk653^)9*UuM<0vHG9uFRoA%M$eKjj6yJJtzg% zm$7H#ah4fafJXziu!3k}rey$hGvkWm+p6!mi9w(aaQa`+@eu3e??r)O#=JQJU^QH! zH^>iX=?u}$+$4ARTaPZ)kv1_H#*~&aU)aFK#c z3jN9JszSDAz*AEdBpmspFk5Cc(jt4%+{Uj$Adr`@muOy7H2@#JFT_$e(Wl~}OO?!U z#Pl}rrl@cTM5_mCEpJP)2jUe|&2>8c^`D{_rwAqts!^b!`PYl$qvou*>6Cq1AsDE0 zQ|c@a0@-ch;W|3B{PPS^1?z1cE7{p0b*{J&b$e)CfBmNn3{Y*3PEHyg;$>o^9ZMat zNh==N#&o%k-k9WNOk=A-q)&h}HtKD?tE5DohbkM|D2wT)ca$4PTNZCKR4zkS2je|u zM;PFlX^MO-zf0j3W_0f9(#Ma5>j+ihNx)a|RDzAkHcvs@% zR;oe=mfa+;mtNmZ6w6G3wwU7y77fSS_F2hT)4mQF0MowatR}?D_4r{oao?U1W8rIx z*SS@wKMNrEf8WaUsII{C+e}D^KL~}!_Dyai9~S_1MICl!Bn@U95AcQ3wXl@3oj^YU zuyyiWWqiga-dXLdr}1xV21G+=Doh!(p_G8Om8Ke3iFD5+VPgNnT(_Z|-U(T5y2WogVwTmR${xL(}&vVX6&Onfn?*S*% zmf1M&Hzi>O`iYe-*?lebv*q?+Y8iRDl9ItKo=Z9mew!o7p5f>CjOtoM>P}$H-la#q zPLi>F#zqJRAR4>{8RNfC?wT@ILenOPlz1|l&upkwK{O2U zluZ6MBlug7^&M@`zx6-=NoVxmr~LOFp(Zro@=@B@<`ZhY^}Z{PQ3^=n)2tq*Bz%AP)I>42=c5#r~rfqe8`00KlT zaqqNFMAUA@YNkY^Tj_=mzxz^D@3WXyKJKPi*BdLiQbs)pAv>rpEJ(u+US%y(AXgz%&R`t-p&BWE9w!owNK#6N)wcny$~BX<6k#q)mvH#hgdq zK1;BVt{g`f>JEe(dSI1Px3#vx{GAlf)fO4S=l$Lqhs0$zb=b`%Vp3Uo8BOE7e=}@h z(PIk#ty4lxp1%5G+lFhQ*!zvIG^;IWy)uBVAn-o~gl-$|NO`5H3nU|UN8=ywW(0q~ z-6*xz)aJWgX7%4reA)^V0kWOff$}Sfyc@W0Z%i~iqM$oN8b62})>dEbY%4}oV;;gn z{(jXxY}|uBa>V*0UzcI;feK!CnEv*2`V-*F)X1DH?GaLatR&*$#vcwDI>-_CY)wuq zf6OunHfv_d;2eLT{DrJ|6y`sZR^5C+u--t zHa9j!tY6#u`sx4hi5Kn<@e88Z;RT&?LytI}o(?Zk zBWqHbIdE$eV9=eXjc)Ydg?S_Jm5EPfVF$>Bf)Q zv<<)ojkI9n^!r*qvI{QD87$6}T-f`4sHf-4nV}9ekw?90`4C3~PI$m>T$4}6xtJ*E z*)YT4B+_~Sg-0a(K0sDzmn9zym#=o=i6X+|m}M2-x<8Jlz#7j}{XW1ZT7+yU zc`GXE&`ihR{i=!ab@1J-sm(FcTTY0lY1YQ&OxpIZy{dqS@BOP=-oIsh@4bIbO!)W8 z{6DYE*!o<#{0EGq+VF`{i|@VnHP~eMv$P0)Qv$o(Eqe5sO)W!!Q*b(-p#W%n~PgG+~zNJG%?fg|$NL)e3^Z0pS>cfd2n z8oAUEvv%oCTif95`{~M=_#a_ak##4OqTk8NcORM6gTJfeaf!SAHxR}I@jN;xW-sSGIyCTO6Yqww$44`)yNY0 zoXzG{?P3FsWEft%;DWWg$*|>0cDJc@(>_bWD9x+5E;aiLM(WckgZAbnH3t2s3d`up zz8iLlRcyVN*HVVOV*;Tvcx_LwIMIjJk^7HY4p{5Uwnht8L2fB{fSV`)J|*jg+%K(K8H{T-j@>HetTEUH{0s%a-PQ) z0G|SaIcmy_t7BH&+;Rffk$>pw(m3AgI+21;h-#-x%_M-=*NjxE?&z!|BYiG@!wq5-l7BU29%VyW+o5vLV)S&q}lMF+C}&Gc4wYM zjqfH|EhI8|^%BOA<%<69;E(PlAA)iOhBiTQu1*<#-#lyx_CTP>aEaQ4TaOYEe^n7f zNlz@>9p#4dKO+R%)Mon=8wdf0Q_adFqE1)_d+o*=EzRl$^Hjr1{*D}u0Y9B6{=T^x zXp-f6b;_BTjj{3;;PL15^{9vk=acfy02{TQ%JEK*JmO=<%BYv4%)P;64&J?Nm5 zlYTr!L+=+4ag8lOOyYe;{JBxhl$F|z&tv;7su`WD*zFy`EJG>3CD?L~Y~I+hM!hav z2h4m_|MHTTSxpE~{zkg1U#Bt8Hy6AqEVeCz-PM@fvD=BMzdI7GWW97>UYVOiSh;>o zsHsAH5g&u8t+tlYG4bh&guyG@b<{5T8+HTS&4U;BBP^dxLLvSHhA2b)L1L@B3r5cP z8!7zMHrOO3QKzPeu6-l^+-au2$C?Naqmm4DYb$u6sWHNCnx^RVD-oq*TfalgUX{fi z8T0VaA$}*0*M3?r{Wub|UHg&?t@WGghk92go+#aiPZNEw)JHNoHTe*^!FElrH?REv eWS+hf!@92 eps: res = (1. - eps / 2) * val ** 2 + eps * val + - if self.smooth_log and val.real > self.eps2: res = res0 + 2 * np.log(val) + +The four parts of this function are designed to ensure a smooth continuous function, with a quadratic increase above 'eps'. +If 'smoothlog' is set to True, the function is capped with a log increase above eps2, to avoid some numeric issues with constraints +values going haywire. +!['Continuous positive smoothing function'](residuals_wo_smoothlog.png) + +### Aggregate functions + +The scalarized functions are then aggregated, first into three functions aggregating each type of functions, and then into a single objective used to drive the optimization: the lagrangian objective. + +It is calculated as the sum of the three aggregated functions multiplied by 100. + + +[^1]: [Martins, J. R. R. A., and Nicholas MK Poon. "On structural optimization using constraint aggregation." VI World Congress on Structural and Multidisciplinary Optimization WCSMO6, Rio de Janeiro, Brasil. 2005.](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.140.3612&rep=rep1&type=pdf) + + diff --git a/sostrades_optimization_plugins/models/func_manager/documentation/residuals_wo_smoothlog.png b/sostrades_optimization_plugins/models/func_manager/documentation/residuals_wo_smoothlog.png new file mode 100644 index 0000000000000000000000000000000000000000..339cb1652b8aa25e28ba04f52a64dd5c71b31d2d GIT binary patch literal 25589 zcmd?RhdY-4`#*l!d+(iD$%D?}uOWJe^KnIXFfk*&-S+2eO! zcklQ2_#MB`_ji2%fbZ)#I_l+i-}iMrpXc*DALrxoIIqa7I_hM^%)|(SkZD|2(MJ&M zAOyjhBE*NEh!2dmMZD zHZm=!4$Urj`uHGhCX9uP|3GwX@94a=W>6%#*@w*l`96QS@oKTnYGbR37rtxK#GIiB z75q0!nmdR`3IEXBvq5;cxVXyClLo24e-dF>Pa+~BIzEXY)adUq;w!?dTr4ffWq8%j zmJx}BmmNuQPQk13G1mWAzuBnV?U&Bh!T?S{K)}^AwAZWdn-^Fr69!AW{X8Wl#b{bW zKU8GguFm0acVq+0*715&!#v1rZf@?SMk1frYUS|yxLcq6;9XLVi>x=^J2t(LcB5ow zete_q{`9w!8%A%QqD!5wM9BF{Na;Q;G2XW~=202)zNX*kXlbnucQ%IegF*DTpK8U*n}=aJ-)xj8$58}CW) zek;Cou)V5R``}(QyFByi_sW)X`z{3e)tlbZ7RB)PW)HL9W{>3c20v+PYU=gF%|0zN zdANd3vQjJo0`)ldM*|Kj&d$zRfAR3}@=YuGkw!bwtd}p5Nx#h)``Xfmed3-pNp%|= zE?p8D^t(miyH{2m^9}fUcz6_Z_33=NM@N~Et0ku5Bcr2q#F1po7Z9u8m+UZ+c-I;c zAh5Q!7GaLLeC3KNTxryg*Z0QWi!hQ(`ECo`8Gb{CNcrzyKu(=H^>X|UFNF5qta+nR zLV9}4t5-b8&qhh72|4$rK_Yf`_I#5HJ`3-8x|NA~7Pnu$s@JbG-XH%EJTh|q!u@f^ zAGMz0opwrH$%4}>E75zKzgp}>trz;UoIO0+k_B(@Ovu8AC(z(4o{Nl%QqI#!e_Xsa z`B5QR*pj^7B921gkP>d0?2TD9wyv)3yZb+wujT2eIyjs`rj~|^ybe~Y-r9H3OG!&- zK{%wydJ4(O$$j?qgowEO>A#0CJeU){<9{4oXT}tDE74Tvze|Q6K73Hjx_a519JW9n z;lwR3FQ5ALiy-%l#6dG7!qEirIlZT_MOvvi$Dpty}E%GkgH*eF;!yfjk@Rj40)&~ z)h|#<g;F`&)?&b?D9U1eSH+fkFb}&l^pc_YHe%FGO4)Pf2T74 zS{^xUr^QbXG*xO`k-!o;eVX9P^9xPcs!{9?2b;h8a(s7wFANsYkh95zdQSQ?IMr?7 zOE^uKeM-W-MN7gH%X9e~*^e3z->KDS0wxb%zC6{PF3W)Qzr2LA^1YITBk=Hic6RpX zLT%V-A4Mg?SQ|YxyRSIcO*b|+BBG-O-U;#Z6T`oGFXq81N=;3DM32TqgZ!

t7fG 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 z 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 literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/models/func_manager/documentation/scheme_funcmanager.png b/sostrades_optimization_plugins/models/func_manager/documentation/scheme_funcmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..abc8f3ef0161706a7a8448bc65bf6d486e9cb3e3 GIT binary patch literal 54262 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 S6vc}sJ9A1!K26rt_dfu$t{V*i literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/models/func_manager/documentation/smooth_maximum.png b/sostrades_optimization_plugins/models/func_manager/documentation/smooth_maximum.png new file mode 100644 index 0000000000000000000000000000000000000000..015eb6bca607db23046b979a7977ec5eef12ce31 GIT binary patch literal 49005 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 b^Jl|@npx|X&7mVC{G+O*shBTsdFwv_<;RkS literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/models/func_manager/func_manager.py b/sostrades_optimization_plugins/models/func_manager/func_manager.py new file mode 100644 index 0000000..518f9c1 --- /dev/null +++ b/sostrades_optimization_plugins/models/func_manager/func_manager.py @@ -0,0 +1,391 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2023/08/10-2024/05/17 Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import numpy as np + +from sostrades_core.tools.base_functions.exp_min import compute_func_with_exp_min +from sostrades_optimization_plugins.tools.cst_manager.func_manager_common import smooth_maximum + + +class FunctionManager: + """ + Class to manage constraints + """ + OBJECTIVE = 'objective' + INEQ_CONSTRAINT = 'ineq_constraint' + EQ_CONSTRAINT = 'eq_constraint' + CONSTRAINTS = [INEQ_CONSTRAINT, EQ_CONSTRAINT] + FTYPE = 'ftype' + VALUE = 'value' + WEIGHT = 'weight' # Can be used for normalisation + AGGR = 'aggr' + AGGR_TYPE_SMAX = 'smax' + AGGR_TYPE_SUM = 'sum' + AGGR_TYPE_DELTA = 'delta' + AGGR_TYPE_LIN_TO_QUAD = 'lin_to_quad' + POS_AGGR_TYPE = [AGGR_TYPE_SMAX, AGGR_TYPE_SUM, AGGR_TYPE_DELTA, AGGR_TYPE_LIN_TO_QUAD] + + def __init__(self): + """ + Constructor for the function manager class + -Objective should be normalized (around 1. near optimum) + -Constraints should be normalized (Around 1. for strong violation) + -Inequatity constraints: satisfied < 0., violation > 0., strong violation > 1 + -Equality constraints: satisfied: 0. + """ + self.aggr_mod_ineq = None + self.POS_FTYPE = [self.OBJECTIVE, + self.INEQ_CONSTRAINT, self.EQ_CONSTRAINT] + self.reinit() + #-- We could use it to log? + + def reinit(self): + """ + Initialize functions dict + """ + self.functions = {} + self.mod_functions = {} + self.aggregated_functions = {} + self.mod_obj = 0. + self.smooth_log = False + self.eps2 = 1e20 + + def configure_smooth_log(self, smooth_log, eps2): + self.smooth_log = smooth_log + self.eps2 = eps2 + + def __to_array_type(self, value): + t_val = type(value) + if isinstance(value, np.ndarray): + mod_value = value + elif t_val == type(np.array([0.])): + mod_value = value + elif t_val == type([]): + mod_value = np.array(value) + elif t_val == type(0.): + mod_value = np.array(value) + else: + raise ValueError('Unsupported type ' + str(t_val)) + return mod_value + + def add_function(self, tag, value=None, ftype=INEQ_CONSTRAINT, weight=1., aggr_type='sum'): + """ + By default aggr_type to smax for constraints, thus use keyword for objectives + """ + if value is not None: + value = self.__to_array_type(value) + if aggr_type not in self.POS_AGGR_TYPE: + raise ValueError(str(aggr_type) + ' not in ' + + str(self.POS_AGGR_TYPE)) + dict_func = {} + dict_func[self.VALUE] = value + dict_func[self.FTYPE] = ftype + dict_func[self.WEIGHT] = weight + dict_func[self.AGGR] = aggr_type + self.functions[tag] = dict_func + + def update_function_fweight(self, tag, weight): + self.functions[tag][self.FTYPE] = weight + + def update_function_ftype(self, tag, ftype): + self.functions[tag][self.FTYPE] = ftype + + def update_function_value(self, tag, value): + self.functions[tag][self.VALUE] = self.__to_array_type(value) + + def set_aggregation_mods(self, aggr_ineq, aggr_eq): + self.aggr_mod_ineq = aggr_ineq + self.aggr_mod_eq = aggr_eq + + def scalarize_all_functions(self, eps=1e-3, alpha=3): + for tag in self.functions.keys(): + weight = self.functions[tag][self.WEIGHT] + dict_mod_func = {} + dict_mod_func[self.FTYPE] = self.functions[tag][self.FTYPE] + dict_mod_func[self.AGGR] = self.functions[tag][self.AGGR] + #-- All values are an np array even single values + #-- Weights are applied here to allow sign modification + values = weight * self.functions[tag][self.VALUE] + aggr_type = dict_mod_func[self.AGGR] + if self.functions[tag][self.FTYPE] == self.OBJECTIVE: + #-- smooth maximum of values return the value if it was a float + #-- return smooth maximum if objective was an array + if aggr_type == 'smax': + res = smooth_maximum(values, alpha) + elif aggr_type == 'sum': + res = values.sum() + else: + raise Exception(f"Unhandled aggr_type {aggr_type}") + elif self.functions[tag][self.FTYPE] == self.INEQ_CONSTRAINT: + #-- scale between (0., +inf) and take smooth maximum + cst = self.cst_func_ineq(values, eps, tag) + res = smooth_maximum(cst, alpha) + elif self.functions[tag][self.FTYPE] == self.EQ_CONSTRAINT: + if aggr_type == 'delta': + cst = self.cst_func_eq_delta(values, eps, tag) + elif aggr_type == 'lin_to_quad': + cst = self.cst_func_eq_lintoquad(values, eps, tag) + else: + cst = self.cst_func_eq(values) + res = smooth_maximum(cst, alpha) + else: + raise Exception(f"Unknown function type {self.functions[tag][self.FTYPE]}") + + dict_mod_func[self.VALUE] = res + self.mod_functions[tag] = dict_mod_func + + def build_aggregated_functions(self, eps=1e-3, alpha=3): + """ + Suppose objectives are scaled between 0. and 1. + Suppose constraints are scaled also + need to multiply by 100. to help optimizers numerical instability + """ + self.scalarize_all_functions(eps, alpha) + all_mod_obj = [] + all_mod_ineq_cst = [] + all_mod_eq_cst = [] + for tag in self.mod_functions.keys(): + if self.mod_functions[tag][self.FTYPE] == self.OBJECTIVE: + all_mod_obj.append(self.mod_functions[tag]) + elif self.mod_functions[tag][self.FTYPE] == self.INEQ_CONSTRAINT: + all_mod_ineq_cst.append(self.mod_functions[tag]) + elif self.mod_functions[tag][self.FTYPE] == self.EQ_CONSTRAINT: + all_mod_eq_cst.append(self.mod_functions[tag]) + + #-- Objective aggregation: sum all the objectives + self.aggregated_functions[self.OBJECTIVE] = 0. + for obj_dict in all_mod_obj: + self.aggregated_functions[self.OBJECTIVE] += obj_dict[self.VALUE] + + #-- Inequality constraint aggregation: takes the smooth maximum + ineq_cst_val = [] + for ineq_dict in all_mod_ineq_cst: + ineq_cst_val.append(ineq_dict[self.VALUE]) + ineq_cst_val = np.array(ineq_cst_val) + if len(ineq_cst_val) > 0: + if self.aggr_mod_ineq == 'smooth_max': + self.aggregated_functions[self.INEQ_CONSTRAINT] = self.cst_func_smooth_maximum( + ineq_cst_val, alpha) + else: + self.aggregated_functions[self.INEQ_CONSTRAINT] = ineq_cst_val.sum() + + else: + self.aggregated_functions[self.INEQ_CONSTRAINT] = 0. + + #-- Equality constraint aggregation: takes the smooth maximum + eq_cst_val = [] + for eq_dict in all_mod_eq_cst: + eq_cst_val.append(eq_dict[self.VALUE]) + eq_cst_val = np.array(eq_cst_val) + if len(eq_cst_val) > 0: + if self.aggr_mod_eq == 'smooth_max': + self.aggregated_functions[self.EQ_CONSTRAINT] = self.cst_func_smooth_maximum( + eq_cst_val, alpha) + else: + self.aggregated_functions[self.EQ_CONSTRAINT] = eq_cst_val.sum() + else: + self.aggregated_functions[self.EQ_CONSTRAINT] = 0. + + #--- Lagrangian objective calculation: sum the aggregated objective and constraints * 100. + self.mod_obj = 0. + self.mod_obj += self.aggregated_functions[self.OBJECTIVE] + self.mod_obj += self.aggregated_functions[self.INEQ_CONSTRAINT] + self.mod_obj += self.aggregated_functions[self.EQ_CONSTRAINT] + self.mod_obj = 100. * self.mod_obj + return self.mod_obj + + def cst_func_eq(self, values, tag='cst'): + """ + Function + """ + abs_values = np.sqrt(np.sign(values) * values) + return self.cst_func_ineq(abs_values, 0., tag=tag) + + def cst_func_eq_delta(self, values, eps=1e-3, tag='cst'): + """ + Function + """ + abs_values = np.sqrt(compute_func_with_exp_min(np.array(values) ** 2, 1e-15)) + return self.cst_func_ineq(abs_values, eps, tag=tag) + + def cst_func_ineq(self, values, eps=1e-3, tag='cst'): + """ + Awesome function + """ + cst_result = np.zeros_like(values) + for iii, val in enumerate(values): + if self.smooth_log and val.real > self.eps2: + # res0 is the value of the function at val.real=self.eps2 to + # ensure continuity + res0 = eps * (np.exp(eps) - 1.) + res00 = res0 + self.eps2 ** 2 - eps ** 2 + res = res00 + 2 * np.log(val) + print( + f'{tag} = {val.real} > eps2 = {self.eps2}, the log function is applied') + elif val.real > eps: + res0 = eps * (np.exp(eps) - 1.) + res = res0 + val ** 2 - eps ** 2 + elif val.real < -250.0: + res = 0.0 + else: + res = eps * (np.exp(val) - 1.) + if np.isnan(res): + print( + 'NaN detected in cst_func_smooth_positive i={}, x={}, r={}, name={}'.format(iii, val, res, tag)) + cst_result[iii] = res + + if np.any(np.isnan(cst_result)): + raise Exception('NaN in cst_func_smooth_positive {}'.format(tag)) + + return cst_result + + def cst_func_eq_lintoquad(self, values, eps=1e-3, tag='cst'): + """ + Same as cst_func_eq but with a linear increase for negative value + """ + cst_result = np.zeros_like(values) + for iii, val in enumerate(values): + if val.real > eps: + #if val > eps: quadratic + res0 = eps * (np.exp(eps) - 1.) + res = res0 + val ** 2 - eps ** 2 + elif -eps < val.real < 0: + # if val < 0: linear + res = eps * (np.exp(-val) - 1.) + elif val.real < -eps: + res0 = eps * (np.exp(eps) - 1.) + res= res0 + (-val) - eps + else: + # if 0 < val < eps: linear + res = eps * (np.exp(val) - 1.) + if np.isnan(res): + print( + 'NaN detected in cst_func_smooth_positive i={}, x={}, r={}, name={}'.format(iii, val, res, tag)) + cst_result[iii] = res + + if np.any(np.isnan(cst_result)): + raise Exception('NaN in cst_func_smooth_positive {}'.format(tag)) + + return cst_result + + def cst_func_smooth_maximum(self, values, alpha=3, drop_zeros=False): + """ + Function + """ + if drop_zeros: + val = values[np.where(values)[0]] + else: + val = values + + return smooth_maximum(val, alpha=alpha) + + def get_mod_func_val(self, tag): + ''' + get modified value + ''' + return self.mod_functions[tag][self.VALUE] + + def get_ineq_constraints_names(self, with_mod_value=False): + ''' + returns all ineq constraint data + ''' + def filter_ineq(name): + return self.functions[name][self.FTYPE] == self.INEQ_CONSTRAINT + + ineq_names = list(filter(filter_ineq, list(self.functions.keys()))) + + if not with_mod_value: + return ineq_names + else: + return dict([(n, self.mod_functions[n][self.VALUE]) for n in ineq_names]) + + def get_eq_constraints_names(self, with_mod_value=False): + ''' + returns all eq constraint data + ''' + functions = self.functions + + def filter_eq(name): + return functions[name][self.FTYPE] == self.EQ_CONSTRAINT + + eq_names = list(filter(filter_eq, list(functions.keys()))) + + if not with_mod_value: + return eq_names + else: + return dict([(n, self.mod_functions[n][self.VALUE]) for n in eq_names]) + + def get_constraints_names(self, with_mod_value=False): + ''' + returns list of all the constraints names, dict name:value + ''' + eq_data = self.get_eq_constraints_names() + ineq_data = self.get_ineq_constraints_names() + + if not with_mod_value: + return list(eq_data.keys()) + list(ineq_data.keys()) + else: + return {**eq_data, **ineq_data} + + def get_objectives_names(self, with_mod_value=False): + ''' + returns all objectives data + ''' + functions = self.functions + + def filter_obj(name): + return self.functions[name][self.FTYPE] == self.OBJECTIVE + + obj_names = list(filter(filter_obj, list(functions.keys()))) + + if not with_mod_value: + return obj_names + else: + return dict([(n, self.mod_functions[n][self.VALUE]) for n in obj_names]) + + def compute_dobjective_dweight(self, variable_name): + + return self.functions[variable_name][self.VALUE] + + @staticmethod + def scale_function(val, val_range): + """ + Scale a function in a range [a, b] that is mapped to [0, 1]. It can be ab. + :param val: value to scale + :param val_range: range for the function as [ideal, anti-ideal] (so ideal > anti-ideal for maximisation) + :return: scaled function with 0 corresponding to ideal value and 1 to anti-ideal + """ + # TODO: consider using a positive interval and adding a maximisation flag in the sake of clarity + return np.array([(val - val_range[0]) / (val_range[1] - val_range[0])]).reshape((-1,)) # NB: funcmanager demands arrays of shape (N, ) + + @staticmethod + def scale_function_derivative(val_range) -> float: + """ + Derivative of the scale function + :param val_range: range for the function as [ideal, anti-ideal] (so ideal > anti-ideal for maximisation) + :return: derivative of the scaled function with 0 corresponding to ideal value and 1 to anti-ideal + """ + return 1. / (val_range[1] - val_range[0]) + + @staticmethod + def unscale_function(val_sc, val_range): + """ + Unscale a function in a range. + :param val_sc: scaled function with 0 corresponding to ideal value and 1 to anti-ideal + :param val_range: range for the function as [ideal, anti-ideal] (so ideal > anti-ideal for maximisation) + :return: function in original unit + """ + return val_range[0] + val_sc * (val_range[1] - val_range[0]) diff --git a/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py b/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py new file mode 100644 index 0000000..7330fb0 --- /dev/null +++ b/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py @@ -0,0 +1,1490 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2023/04/04-2024/05/17 Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import csv +import logging +import time +import warnings +from copy import deepcopy +from math import isnan + +import numpy as np +import pandas as pd +from numpy import asarray, float64, ndarray +from plotly import graph_objects as go + +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_core.execution_engine.optim_manager_disc import OptimManagerDisc +from sostrades_core.tools.base_functions.exp_min import ( + compute_dfunc_with_exp_min, + compute_func_with_exp_min, +) +from sostrades_optimization_plugins.tools.cst_manager.func_manager_common import ( + get_dsmooth_dvariable, + smooth_maximum, +) +from sostrades_core.tools.post_processing.charts.chart_filter import ChartFilter +from sostrades_core.tools.post_processing.plotly_native_charts.instantiated_plotly_native_chart import ( + InstantiatedPlotlyNativeChart, +) + +warnings.simplefilter(action='ignore', category=FutureWarning) + + +class FunctionManagerDisc(OptimManagerDisc): + """ + Constraints aggregation discipline + """ + + # ontology information + _ontology_data = { + 'label': 'Core Function Manager', + 'type': 'Research', + 'source': 'SoSTrades Project', + 'validated': '', + 'validated_by': 'SoSTrades Project', + 'last_modification_date': '', + 'category': '', + 'definition': '', + 'icon': 'fas fa-chart-line fa-fw', + 'version': '', + } + MOD_SUFFIX = '_mod' + INEQ_CONSTRAINT = FunctionManager.INEQ_CONSTRAINT + EQ_CONSTRAINT = FunctionManager.EQ_CONSTRAINT + OBJECTIVE = FunctionManager.OBJECTIVE + OBJECTIVE_LAGR = OBJECTIVE + '_lagrangian' + + PARAMETER_LIST = [OBJECTIVE_LAGR, OBJECTIVE, + INEQ_CONSTRAINT, EQ_CONSTRAINT] + FUNC_DF = 'function_df' + VARIABLE = 'variable' + FTYPE = 'ftype' + WEIGHT = 'weight' + INDEX = 'index' + NAMESPACE_VARIABLE = 'namespace' + COMPONENT = 'component' + AGGR_TYPE = 'aggr' + PARENT = 'parent' + CHILDREN = 'children' + OPTIM_OUTPUT_DF = 'optim_output_df' + EXPORT_CSV = 'export_csv' + + NS_OPTIM = 'ns_optim' + DESC_IN = {FUNC_DF: {'type': 'dataframe', + 'dataframe_descriptor': {VARIABLE: ('string', None, True), # input function + FTYPE: ('string', None, True), + WEIGHT: ('float', None, True), + AGGR_TYPE: ('string', None, True), + PARENT: ('string', None, True), + # index of the dataframe + # INDEX: ('int', None, True), + NAMESPACE_VARIABLE: ('string', None, True), + }, # col name of the dataframe + 'dataframe_edition_locked': False, + 'structuring': True + }, + EXPORT_CSV: {'type': 'bool', 'default': False}, + 'smooth_log': {'type': 'bool', 'default': False, 'user_level': 3}, + 'eps2': {'type': 'float', 'default': 1e10, 'user_level': 3}, + 'aggr_mod_ineq': {'type': 'string', 'default': 'sum', 'user_level': 3}, + 'aggr_mod_eq': {'type': 'string', 'default': 'sum', 'user_level': 3}, + } + DESC_OUT = {OPTIM_OUTPUT_DF: {'type': 'dataframe'}} + + def __init__(self, sos_name, logger: logging.Logger): + ''' + constructor + ''' + super().__init__(sos_name=sos_name, logger=logger) + self.function_dict = None + self.func_manager = FunctionManager() + self.__formulation = None + self.old_iter = -1 + + def set_optim_formulation(self, formulation): + ''' + + Set the formulation object (sent by proxyoptim and built by mdoscenario of gemseo + ''' + self.__formulation = formulation + + def get_current_iter(self): + ''' + Get current iter from the optimisation problem in the formulation object built by GEMSEO + If no formulation then we compute in the old way, iter is the number of method calls + ''' + if self.__formulation is not None: + return self.__formulation.opt_problem.current_iter + else: + self.iter += 1 + return self.iter + + def setup_sos_disciplines(self): + ''' + + Specific configure for the func manager discipline + + ''' + dynamic_inputs, dynamic_outputs = {}, {} + + # initialization of func_manager + self.func_manager.reinit() + + inputs_dict = self.get_sosdisc_inputs() + + if 'eps2' in inputs_dict.keys(): + self.func_manager.configure_smooth_log(inputs_dict['smooth_log'], inputs_dict['eps2']) + + # retrieve all the function descriptions + if self.FUNC_DF in inputs_dict.keys(): + func_df = inputs_dict[self.FUNC_DF] + if func_df is not None: + + list_var = list(func_df[self.VARIABLE]) + + # create func_dict from func_df + func_dict = {} + + # loop on variables (function names), + # to get the indices (INDEX) and columns (COMPONENT) + for var in list_var: + ftype = func_df.loc[func_df[self.VARIABLE] + == var, self.FTYPE].values[0] + weight = func_df.loc[func_df[self.VARIABLE] + == var, self.WEIGHT].values[0] + func_dict[var] = {self.FTYPE: ftype, self.WEIGHT: weight} + + if self.INDEX in func_df: + index = func_df.loc[func_df[self.VARIABLE] + == var, self.INDEX].values[0] + if index is not None and not isinstance(index, str): + if not isnan(index): + func_dict[var].update({self.INDEX: index}) + + if self.COMPONENT in func_df: + component = func_df.loc[func_df[self.VARIABLE] + == var, self.COMPONENT].values[0] + if component is not None: + func_dict[var].update({self.COMPONENT: component}) + + self.function_dict = func_dict + # -- update all i/o function per function + for f, metadata in self.function_dict.items(): + # TODO: improve by retrieving desc_i/o info from disciplines + # instead of assuming that they all are dataframes + + # get namespace of the variable + # if namespace column is in the dataframe + if self.NAMESPACE_VARIABLE in func_df.columns: + namespace = func_df.loc[func_df[self.VARIABLE] + == f, self.NAMESPACE_VARIABLE].values[0] + if not isinstance(namespace, str) or namespace == '': + namespace = 'ns_functions' + # default namespace is ns_functions + else: + namespace = 'ns_functions' + + namespaces = self.dm.get_all_namespaces_from_var_name(f) + + if namespaces != []: + variable_full_name = namespaces[0] + var_type = self.dm.get_data(variable_full_name)['type'] + dynamic_inputs[f] = { + self.TYPE: var_type, self.STRUCTURING: True, self.VISIBILITY: self.SHARED_VISIBILITY, + self.NAMESPACE: namespace} + else: + dynamic_inputs[f] = { + self.TYPE: 'dataframe', self.DATAFRAME_DESCRIPTOR: {}, + self.DYNAMIC_DATAFRAME_COLUMNS: True, self.STRUCTURING: True, + self.VISIBILITY: self.SHARED_VISIBILITY, + self.NAMESPACE: namespace} + + # -- output update : constr aggregation and scalarized objective + out_name = self.__build_mod_names(f) + dynamic_outputs[out_name] = {self.TYPE: 'array', self.VISIBILITY: self.SHARED_VISIBILITY, + self.NAMESPACE: self.NS_OPTIM} + # -- add function to the FuncManager + ftype = metadata[FunctionManager.FTYPE] + w = metadata.get(FunctionManager.WEIGHT, None) + + aggr_type = 'sum' + if self.AGGR_TYPE in func_df.columns: + aggr_type = func_df.loc[func_df[self.VARIABLE] + == f, self.AGGR_TYPE].values[0] + if pd.isnull(aggr_type): + aggr_type = 'sum' + if w is None: + w = 1. + self.func_manager.add_function( + f, value=None, ftype=ftype, weight=w, aggr_type=aggr_type) + + # -- output update : aggregation of ineq constraints + dynamic_outputs[self.INEQ_CONSTRAINT] = {'type': 'array', self.VISIBILITY: self.SHARED_VISIBILITY, + 'namespace': self.NS_OPTIM} + + # -- output update : aggregation of eq constraints + dynamic_outputs[self.EQ_CONSTRAINT] = {'type': 'array', self.VISIBILITY: self.SHARED_VISIBILITY, + 'namespace': self.NS_OPTIM} + + # -- output update : scalarization of objective + dynamic_outputs[self.OBJECTIVE] = {'type': 'array', self.VISIBILITY: self.SHARED_VISIBILITY, + 'namespace': self.NS_OPTIM} + + # -- output update : lagrangian penalization + dynamic_outputs[self.OBJECTIVE_LAGR] = {'type': 'array', self.VISIBILITY: self.SHARED_VISIBILITY, + 'namespace': self.NS_OPTIM} + self.iter, self.last_len_database = -1, 0 + self.old_optim_output_df = pd.DataFrame() + self.add_inputs(dynamic_inputs) + self.add_outputs(dynamic_outputs) + self.inst_desc_in = dynamic_inputs + self.inst_desc_out = dynamic_outputs + # self.DESC_IN.update(proxy.inst_desc_in) + # self.DESC_OUT.update(proxy.inst_desc_out) + + def run(self): + ''' + computes the scalarization + ''' + + current_iter = self.get_current_iter() + # --initialize csv + if current_iter == 0 and self.get_sosdisc_inputs(self.EXPORT_CSV): + self.initialize_csv_files() + + aggr_mod_ineq, aggr_mod_eq = self.get_sosdisc_inputs(['aggr_mod_ineq', 'aggr_mod_eq']) + self.func_manager.set_aggregation_mods(aggr_mod_ineq, aggr_mod_eq) + # -- update function values + for f in self.function_dict.keys(): + fvalue_df = self.get_sosdisc_inputs(f) + self.check_isnan_inf(f, fvalue_df) + self.check_value_range(fvalue_df, fname=f, ftype=self.function_dict[f]['ftype'].upper()) + # conversion dataframe > array: + f_arr = self.convert_df_to_array(f, fvalue_df) + # update func manager with value as array + self.func_manager.update_function_value(f, f_arr) + + # -- build aggregation functions + self.func_manager.build_aggregated_functions(eps=1e-3) # alpha=3 + + # # -- store output values + + dict_out = self.build_dict_output_values() + + if self.get_sosdisc_inputs(self.EXPORT_CSV): + self.write_in_csv_files(current_iter, dict_out) + + # To store all results of the optim in a dataframe + self.build_new_output_optim_df(current_iter, dict_out) + + dict_out[self.OPTIM_OUTPUT_DF] = self.old_optim_output_df + + self.store_sos_outputs_values(dict_out) + + def build_dict_output_values(self): + ''' + Build the dictionary of outputs for the discipline + + ''' + dict_out = {} + for f in self.function_dict.keys(): + val = self.func_manager.mod_functions[f][self.VALUE] + out_name = self.__build_mod_names(f) + + dict_out[out_name] = np.array( + [val]) + dict_out[self.INEQ_CONSTRAINT] = np.array( + [self.func_manager.aggregated_functions[self.INEQ_CONSTRAINT]]) + dict_out[self.EQ_CONSTRAINT] = np.array( + [self.func_manager.aggregated_functions[self.EQ_CONSTRAINT]]) + dict_out[self.OBJECTIVE] = np.array( + [self.func_manager.aggregated_functions[self.OBJECTIVE]]) + dict_out[self.OBJECTIVE_LAGR] = np.array([self.func_manager.mod_obj]) + + return dict_out + + def build_new_output_optim_df(self, current_iter, dict_out): + ''' + + Build new_output_optim_df if the current iteration is new + Only first run of the optim iteration will be triggered + + ''' + if current_iter == self.old_iter + 1: + full_end_df = pd.DataFrame({key: [value] + for key, value in dict_out.items()}) + full_end_df.insert(loc=0, column='iteration', + value=[current_iter]) + self.old_iter = current_iter + new_optim_output_df = pd.concat([ + self.old_optim_output_df, full_end_df]) + self.old_optim_output_df = new_optim_output_df + + def initialize_csv_files(self): + ''' + + With export csv option some CSV files are initialized to follow the optimisation wrt func manager outputs + + ''' + s_name = [full_name for full_name in self.local_data if 'export_csv' in full_name][0].split('.')[0] + t = time.localtime() + + self.csvfile = open( + f'mod_{s_name}_funcmanager_test.csv', 'w') + self.csvfile2 = open( + f'aggr_{s_name}_funcmanager_test.csv', 'w') + self.writer = csv.writer( + self.csvfile, lineterminator='\n', delimiter=',') + self.writer2 = csv.writer( + self.csvfile2, lineterminator='\n', delimiter=',') + + def write_in_csv_files(self, current_iter, dict_out): + ''' + + With export csv option CSV files are written and flushed to follow the optimisation wrt func manager outputs + + ''' + msg2_list = [self.INEQ_CONSTRAINT, self.EQ_CONSTRAINT, self.OBJECTIVE, self.OBJECTIVE_LAGR] + + msg1 = [str(current_iter)] + msg1.extend([str(val) for key, val in dict_out.items() if key not in msg2_list]) + header = ["iteration "] + header.extend([key for key in dict_out if key not in msg2_list]) + header2 = ["iteration "] + header2.extend([key for key in dict_out if key in msg2_list]) + msg2 = [str(current_iter)] + msg2.extend([str(val) for key, val in dict_out.items() if key in msg2_list]) + + if current_iter == 1: + self.writer.writerow(header) + self.writer2.writerow(header2) + self.writer.writerow(msg1) + self.writer2.writerow(msg2) + self.csvfile2.flush() + self.csvfile.flush() + + def compute_sos_jacobian(self): + + # dobjective/dfunction_df + f_manager = self.func_manager + + # -- update function values + for f in self.function_dict.keys(): + fvalue_df = self.get_sosdisc_inputs(f) + self.check_isnan_inf(f, fvalue_df) + # conversion dataframe > array: + f_arr = self.convert_df_to_array(f, fvalue_df) + # update func manager with value as array + f_manager.update_function_value(f, f_arr) + + # -- build aggregation functions + f_manager.build_aggregated_functions(eps=1e-3) # alpha=3 + + var_list = [] + for variable_name in var_list: + f_manager.compute_dobjective_dweight(variable_name) + + inputs_dict = self.get_sosdisc_inputs() + grad_value_l = {} + + grad_val_compos = {} + value_gh_l = [] + value_ghk_l = [] + for variable_name in self.func_manager.functions.keys(): + value_df = inputs_dict[variable_name] + weight = self.func_manager.functions[variable_name][self.WEIGHT] + grad_value_l[variable_name] = {} + grad_val_compos[variable_name] = {} + if self.func_manager.functions[variable_name][self.FTYPE] == self.OBJECTIVE: + + if isinstance(value_df, np.ndarray): + n = len( + self.func_manager.functions[variable_name][self.VALUE]) + + if self.func_manager.functions[variable_name][self.AGGR_TYPE] == 'sum': + + grad_value = weight * np.ones(n) + elif self.func_manager.functions[variable_name][self.AGGR_TYPE] == 'smax': + grad_value = float(weight) * \ + np.array(get_dsmooth_dvariable( + self.func_manager.functions[variable_name][self.VALUE])) + else: + raise Exception(f"Unknown aggr type {self.func_manager.functions[variable_name][self.AGGR_TYPE]}") + + self.set_partial_derivative( + 'objective_lagrangian', variable_name, 100.0 * np.atleast_2d(grad_value)) + self.set_partial_derivative( + 'objective', variable_name, np.atleast_2d(grad_value)) + else: + for col_name in value_df.columns: + if col_name != 'years': + n = len( + self.func_manager.functions[variable_name][self.VALUE]) + + if self.func_manager.functions[variable_name][self.AGGR_TYPE] == 'sum': + + grad_value = weight * np.ones(n) + elif self.func_manager.functions[variable_name][self.AGGR_TYPE] == 'smax': + + grad_value = float(weight) * \ + np.array(get_dsmooth_dvariable( + self.func_manager.functions[variable_name][self.VALUE])) + + self.set_partial_derivative_for_other_types( + ('objective',), (variable_name, col_name), grad_value) + + self.set_partial_derivative_for_other_types( + ('objective_lagrangian',), (variable_name, col_name), 100.0 * grad_value) + + elif self.func_manager.functions[variable_name][self.FTYPE] == self.INEQ_CONSTRAINT: + if isinstance(value_df, pd.DataFrame): + for col_name in value_df.columns: + if col_name != 'years': + # h: cst_func_ineq, g: smooth_max, f: smooth_max + # g(h(x)) for each variable , f([g(h(x1), g(h(x2))]) + # weight + weight = self.func_manager.functions[variable_name][self.WEIGHT] + value = value_df[col_name] * weight + # h'(x) + grad_value = self.get_dfunc_ineq_dvariable( + value) + # h(x) + func_ineq = f_manager.cst_func_ineq( + value) + # g(h(x)) + value_gh_l.append( + smooth_maximum(func_ineq)) + # g'(h(x)) + grad_val_compos[variable_name][col_name] = get_dsmooth_dvariable( + func_ineq) + # g'(h(x)) * h'(x) + grad_val_compos_l = np.array( + grad_value) * np.array(grad_val_compos[variable_name][col_name]) + + grad_value_l[variable_name][col_name] = grad_val_compos_l + + elif isinstance(value_df, np.ndarray): + weight = self.func_manager.functions[variable_name][self.WEIGHT] + value = value_df * weight + # h'(x) + grad_value = self.get_dfunc_ineq_dvariable( + value) + # h(x) + func_ineq = f_manager.cst_func_ineq( + value) + # g(h(x)) + value_gh_l.append(smooth_maximum(func_ineq)) + # g'(h(x)) + grad_val_compos[variable_name] = get_dsmooth_dvariable( + func_ineq) + # g'(h(x)) * h'(x) + grad_val_compos_l = np.array( + grad_value) * np.array(grad_val_compos[variable_name]) + + grad_value_l[variable_name] = grad_val_compos_l + + else: + raise Exception( + 'Gradients for constraints which are not dataframes or arrays are not yet implemented') + elif self.func_manager.functions[variable_name][self.FTYPE] == self.EQ_CONSTRAINT: + if isinstance(value_df, pd.DataFrame): + for col_name in value_df.columns: + if col_name != 'years': + # k: sqrt(x**2) h: cst_func_eq, g : smooth_max, f: smooth_max + # g(h(k(x))) for each variable , f([g(h(k(x1)), g(h(k(x2)))]) + # weight + weight = self.func_manager.functions[variable_name][self.WEIGHT] + value = np.array(value_df[col_name].values * weight) + if self.func_manager.functions[variable_name][ + self.AGGR_TYPE] == self.func_manager.AGGR_TYPE_DELTA: + # k(x) + k_cst = np.sqrt(compute_func_with_exp_min(value ** 2, 1e-15)) + # k'(x) + dk_dcst = 2 * np.ones(len(value)) * value * \ + compute_dfunc_with_exp_min(value ** 2, 1e-15).reshape(value.shape) / ( + 2 * k_cst) + # h(k(x)) + h_k = f_manager.cst_func_ineq( + k_cst) + # h'(k(x)) + dh_dcst = self.get_dfunc_ineq_dvariable( + k_cst) + + elif self.func_manager.functions[variable_name][ + self.AGGR_TYPE] == self.func_manager.AGGR_TYPE_LIN_TO_QUAD: + k_cst = f_manager.cst_func_eq_lintoquad( + value) + dk_dcst = self.get_dfunc_eq_lin_to_quad_dvariable(value) + + h_k = k_cst + dh_dcst = 1. + else: + k_cst = np.sqrt(np.sign(value) * value) + # k'(x) + dk_dcst = np.sign(value) * np.ones(len(value)) / ( + 2 * k_cst) + # h(k(x)) + h_k = f_manager.cst_func_ineq( + k_cst) + # h'(k(x)) + dh_dcst = self.get_dfunc_ineq_dvariable( + k_cst) + + # g(h(k(x))) + value_ghk_l.append( + smooth_maximum(h_k)) + + # g'(h(k(x))) + grad_val_compos[variable_name][col_name] = get_dsmooth_dvariable( + h_k) + # g'(h(k(x))) * h'(k(x)) * k'(x) + grad_val_compos_l = np.array(grad_val_compos[variable_name][col_name]) * np.array( + dh_dcst) * np.array(dk_dcst) + + grad_value_l[variable_name][col_name] = grad_val_compos_l + + elif isinstance(value_df, np.ndarray): + # k: sqrt(x**2) h: cst_func_eq, g : smooth_max, f: smooth_max + # g(h(k(x))) for each variable , f([g(h(k(x1)), g(h(k(x2)))]) + # weight + weight = self.func_manager.functions[variable_name][self.WEIGHT] + value = value_df * weight + + if self.func_manager.functions[variable_name][ + self.AGGR_TYPE] == self.func_manager.AGGR_TYPE_DELTA: + # k(x) + k_cst = np.sqrt(compute_func_with_exp_min(value ** 2, 1e-15)) + # k'(x) + dk_dcst = 2 * np.ones(len(value)) * value * \ + compute_dfunc_with_exp_min(value ** 2, 1e-15).reshape(value.shape) / ( + 2 * k_cst) + # h(k(x)) + h_k = f_manager.cst_func_ineq( + k_cst) + # h'(k(x)) + dh_dcst = self.get_dfunc_ineq_dvariable( + k_cst) + + elif self.func_manager.functions[variable_name][ + self.AGGR_TYPE] == self.func_manager.AGGR_TYPE_LIN_TO_QUAD: + k_cst = f_manager.cst_func_eq_lintoquad( + value) + dk_dcst = self.get_dfunc_eq_lin_to_quad_dvariable(value) + + h_k = k_cst + dh_dcst = 1. + else: + k_cst = np.sqrt(np.sign(value) * value) + # k'(x) + dk_dcst = np.sign(value) * np.ones(len(value)) / ( + 2 * k_cst) + # h(k(x)) + h_k = f_manager.cst_func_ineq( + k_cst) + # h'(k(x)) + dh_dcst = self.get_dfunc_ineq_dvariable( + k_cst) + + # g(h(k(x))) + value_ghk_l.append( + smooth_maximum(h_k)) + + # g'(h(k(x))) + grad_val_compos[variable_name] = get_dsmooth_dvariable( + h_k) + # g'(h(k(x))) * h'(k(x)) * k'(x) + grad_val_compos_l = np.array(grad_val_compos[variable_name]) * np.array( + dh_dcst) * np.array(dk_dcst) + + grad_value_l[variable_name] = grad_val_compos_l + else: + raise Exception( + 'Gradients for constraints which are not dataframes or arrays are not yet implemented') + + dict_grad_ineq = {} + dict_grad_eq = {} + grad_val_ineq = {} + grad_val_eq = {} + # g'(h(x)) * h'(x) + if len(value_gh_l) != 0: + grad_val_ineq = get_dsmooth_dvariable(value_gh_l) + if len(value_ghk_l) != 0: + grad_val_eq = get_dsmooth_dvariable(value_ghk_l) + i = 0 + j = 0 + for variable_name in self.func_manager.functions.keys(): + if self.func_manager.functions[variable_name][self.FTYPE] == self.INEQ_CONSTRAINT: + + value_df = inputs_dict[variable_name] + dict_grad_ineq[variable_name] = grad_val_ineq[i] + weight = self.func_manager.functions[variable_name][self.WEIGHT] + if isinstance(value_df, np.ndarray): + + if self.func_manager.aggr_mod_ineq == 'smooth_max': + grad_lagr_val = np.array(grad_value_l[variable_name]) * grad_val_ineq[i] + grad_ineq_val = np.array(grad_value_l[variable_name]) * grad_val_ineq[i] + else: + grad_lagr_val = np.array(grad_value_l[variable_name]) + grad_ineq_val = np.array(grad_value_l[variable_name]) + + self.set_partial_derivative( + 'objective_lagrangian', variable_name, + np.atleast_2d(weight * 100.0 * grad_lagr_val)) + self.set_partial_derivative( + 'ineq_constraint', variable_name, + np.atleast_2d(weight * grad_ineq_val)) + + i = i + 1 + + elif isinstance(value_df, pd.DataFrame): + + for col_name in value_df.columns: + if col_name != 'years': + if self.func_manager.aggr_mod_ineq == 'smooth_max': + grad_lagr_val = np.array(grad_value_l[variable_name][col_name]) * grad_val_ineq[i] + grad_ineq_val = np.array(grad_value_l[variable_name][col_name]) * grad_val_ineq[i] + else: + grad_lagr_val = np.array(grad_value_l[variable_name][col_name]) + grad_ineq_val = np.array(grad_value_l[variable_name][col_name]) + + self.set_partial_derivative_for_other_types( + ('objective_lagrangian',), (variable_name, col_name), + weight * 100.0 * grad_lagr_val) + self.set_partial_derivative_for_other_types( + ('ineq_constraint',), (variable_name, col_name), + weight * grad_ineq_val) + i = i + 1 + + elif self.func_manager.functions[variable_name][self.FTYPE] == self.EQ_CONSTRAINT: + value_df = inputs_dict[variable_name] + dict_grad_eq[variable_name] = grad_val_eq[j] + weight = self.func_manager.functions[variable_name][self.WEIGHT] + if isinstance(value_df, np.ndarray): + if self.func_manager.aggr_mod_eq == 'smooth_max': + grad_lagr_val = np.array(grad_value_l[variable_name]) * grad_val_eq[j] + grad_eq_val = np.array(grad_value_l[variable_name]) * grad_val_eq[j] + else: + + grad_lagr_val = np.array(grad_value_l[variable_name]) + grad_eq_val = np.array(grad_value_l[variable_name]) + + self.set_partial_derivative( + 'objective_lagrangian', variable_name, + np.atleast_2d(weight * 100.0 * grad_lagr_val)) + self.set_partial_derivative( + 'eq_constraint', variable_name, + np.atleast_2d(weight * grad_eq_val)) + j = j + 1 + + elif isinstance(value_df, pd.DataFrame): + + for col_name in value_df.columns: + if col_name != 'years': + if self.func_manager.aggr_mod_eq == 'smooth_max': + grad_lagr_val = np.array(grad_value_l[variable_name][col_name]) * grad_val_eq[j] + grad_eq_val = np.array(grad_value_l[variable_name][col_name]) * grad_val_eq[j] + else: + grad_lagr_val = np.array(grad_value_l[variable_name][col_name]) + grad_eq_val = np.array(grad_value_l[variable_name][col_name]) + + self.set_partial_derivative_for_other_types( + ('objective_lagrangian',), (variable_name, col_name), + weight * 100.0 * grad_lagr_val) + self.set_partial_derivative_for_other_types( + ('eq_constraint',), (variable_name, col_name), + weight * grad_eq_val) + j = j + 1 + + def get_dfunc_ineq_dvariable(self, value_df, eps=1e-3): + """ + Get dineq_dvariable + """ + grad_value = [] + # for col in value_df.columns: + # if col != 'years': + valcol = value_df + + cst_result = np.zeros_like(valcol) + # ---get value of espilon2 + smooth_log = self.get_sosdisc_inputs('smooth_log') + eps2 = self.get_sosdisc_inputs('eps2') + for iii, val in enumerate(valcol): + if smooth_log and val > eps2: + # res00 + 2 * np.log(val) + res = 2.0 / val + elif val > eps: + # res = res0 + val ** 2 - eps ** 2 + res = 2.0 * val + elif val < -250: + # res = 0.0 + res = 0.0 + else: + # res = eps * (np.exp(val) - 1.) + res = eps * np.exp(val) + cst_result[iii] = res + grad_value.extend(cst_result) + + return grad_value + + def get_dfunc_eq_lin_to_quad_dvariable(self, value_df, eps=1e-3): + """ + Get dineq_dvariable for lin_to_quad case + """ + grad_value = [] + # for col in value_df.columns: + # if col != 'years': + valcol = value_df + + dcst_result = np.zeros_like(valcol) + # ---get value of espilon2 + smooth_log = self.get_sosdisc_inputs('smooth_log') + eps2 = self.get_sosdisc_inputs('eps2') + for iii, val in enumerate(valcol): + if val > eps: + # res = res0 + val ** 2 - eps ** 2 + dres = 2.0 * val + elif -eps < val < 0.0: + # res = eps * (np.exp(-val) - 1.) + dres = -eps * np.exp(-val) + elif val < -eps: + # res= res0 + (-val) - eps + dres = -1. + else: + # res = eps * (np.exp(val) - 1.) + dres = eps * np.exp(val) + dcst_result[iii] = dres + grad_value.extend(dcst_result) + return grad_value + + def check_isnan_inf(self, key_, value_): + """ + Check if there is any NaN or inf values in dataframe + """ + + if isinstance(value_, pd.DataFrame): + try: + if value_.isin([np.inf, -np.inf]).any().any(): + logging.warning(f'{key_} has inf values') + if value_.isin([np.nan]).any().any(): + logging.warning(f'{key_} has NaN values') + except AttributeError as error: + logging.warning( + f'func_managerdiscipline::check_isnan_inf : {str(error)}') + + def convert_df_to_array(self, func_name, val_df): + ''' + Non-generic method that returns an array of the only (excluding years) column in val_df + ''' + + if isinstance(val_df, ndarray): + arr = val_df + else: + if 'years' in val_df.columns: + val_df = val_df.drop('years', axis=1) + if self.INDEX in self.function_dict[func_name]: + val_df = val_df.loc[self.function_dict[func_name][self.INDEX]] + if self.COMPONENT in self.function_dict[func_name]: + val_df = val_df[self.function_dict[func_name][self.COMPONENT]] + + if isinstance(val_df, (pd.Series, pd.DataFrame)): + arr = val_df.to_numpy().flatten() + else: + arr = asarray([val_df]) + + if arr.dtype == object: + arr = arr.astype(float64) + return arr + + def get_chart_filter_list(self): + + chart_filters = [] + chart_list = ['lagrangian objective', 'aggregated objectives', + 'objectives', 'ineq_constraints', 'eq_constraints', 'objective (colored)'] + optim_output_df = self.get_sosdisc_outputs(self.OPTIM_OUTPUT_DF) + if optim_output_df[self.INEQ_CONSTRAINT].empty: + chart_list.remove('ineq_constraints') + if optim_output_df[self.EQ_CONSTRAINT].empty: + chart_list.remove('eq_constraints') + if optim_output_df[self.INEQ_CONSTRAINT].empty and optim_output_df[self.EQ_CONSTRAINT].empty: + chart_list.remove('objective (colored)') + chart_filters.append(ChartFilter( + 'Charts', chart_list, chart_list, 'charts')) + return chart_filters + + def get_post_processing_list(self, filters=None): + + # For the outputs, making a graph for block fuel vs range and blocktime vs + # range + + instanciated_charts = [] + charts = [] + + func_df = self.get_sosdisc_inputs('function_df') + + if filters is not None: + for chart_filter in filters: + if chart_filter.filter_key == 'charts': + charts = chart_filter.selected_values + if 'objective (colored)' in charts: + if not self.get_sosdisc_outputs(self.OPTIM_OUTPUT_DF)[self.OBJECTIVE].empty and not \ + self.get_sosdisc_outputs(self.OPTIM_OUTPUT_DF)[self.INEQ_CONSTRAINT].empty: + optim_output_df = deepcopy(self.get_sosdisc_outputs(self.OPTIM_OUTPUT_DF)) + new_chart = self.get_chart_obj_constraints_iterations(func_df, optim_output_df, [self.OBJECTIVE], + 'objective (colored)') + instanciated_charts.append(new_chart) + + chart_list = ['lagrangian objective', 'aggregated objectives', + 'objectives', 'ineq_constraints', 'eq_constraints', ] + + for chart in chart_list: + new_chart = None + optim_output_df = self.get_sosdisc_outputs(self.OPTIM_OUTPUT_DF) + parameters_df, obj_list, ineq_list, eq_list = self.get_parameters_df( + func_df) + if chart in charts: + if chart == 'lagrangian objective': + new_chart = self.get_chart_lagrangian_objective_iterations( + optim_output=optim_output_df, + main_parameters=parameters_df.loc[[ + self.OBJECTIVE_LAGR]], + sub_parameters=parameters_df.loc[[ + self.OBJECTIVE, self.INEQ_CONSTRAINT, self.EQ_CONSTRAINT]], + name=chart) + elif chart == 'aggregated objectives': + new_chart = self.get_chart_aggregated_iterations( + optim_output=optim_output_df, + main_parameters=parameters_df.loc[[ + self.OBJECTIVE, self.INEQ_CONSTRAINT, self.EQ_CONSTRAINT]], + objectives=parameters_df.loc[obj_list], + ineq_constraints=parameters_df.loc[ineq_list], + eq_constraints=parameters_df.loc[eq_list], name=chart) + elif chart == 'objectives': + new_chart = self.get_chart_parameters_mod_iterations(optim_output_df, + parameters_df.loc[obj_list], + name=chart) + elif chart == 'ineq_constraints': + is_ineq_constraints = 'ineq_constraint' in func_df.loc[ + func_df['weight'] != 0., 'ftype'].values.tolist() + if is_ineq_constraints: + new_chart = self.get_chart_parameters_mod_iterations(optim_output_df, + parameters_df.loc[ineq_list], + name=chart) + elif chart == 'eq_constraints': + is_eq_constraints = 'eq_constraint' in func_df.loc[ + func_df['weight'] != 0., 'ftype'].values.tolist() + if is_eq_constraints: + new_chart = self.get_chart_parameters_mod_iterations(optim_output_df, + parameters_df.loc[eq_list], + name=chart) + if new_chart is not None: + instanciated_charts.append(new_chart) + return instanciated_charts + + def get_parameters_df(self, func_df): + """ + Function to explore func_df and arrange the parameters info to plot into a dataframe + Get ['variable', 'parent', 'weight', 'aggr'] for aggregated objectives and constraints as well as for _mod + Inputs : func_df + Outputs : parameters_df, mod_lists + """ + parameters_dict = {} + # Handle _mod + obj_list = [] + ineq_list = [] + eq_list = [] + mod_columns_dict = {self.PARENT: None, + self.WEIGHT: 1.0, self.AGGR_TYPE: 'sum'} + for i, row in func_df.iterrows(): + mod_parameter_dict = {} + mod_parameter_dict[self.VARIABLE] = row[self.VARIABLE] + '_mod' + if self.PARENT in row.index: + if row[self.PARENT] in [np.nan]: + mod_parameter_dict[self.PARENT] = None + else: + mod_parameter_dict[self.PARENT] = row[self.PARENT] + else: + mod_parameter_dict[self.PARENT] = None + for column in mod_columns_dict.keys(): + if column in row.index: + mod_parameter_dict[column] = row[column] + else: + mod_parameter_dict[column] = mod_columns_dict[column] + if 'objective' in row['ftype']: + obj_list.append(mod_parameter_dict[self.VARIABLE]) + elif 'ineq_constraint' in row['ftype']: + ineq_list.append(mod_parameter_dict[self.VARIABLE]) + elif 'eq_constraint' in row['ftype']: + eq_list.append(mod_parameter_dict[self.VARIABLE]) + parameters_dict[mod_parameter_dict[self.VARIABLE] + ] = mod_parameter_dict + + # Aggregated objectives and constraints + dict_aggr_obj = {self.VARIABLE: self.OBJECTIVE, + self.PARENT: self.OBJECTIVE_LAGR, + self.WEIGHT: 1.0, self.AGGR_TYPE: 'sum'} + parameters_dict[dict_aggr_obj[self.VARIABLE]] = dict_aggr_obj + dict_aggr_ineq = {self.VARIABLE: self.INEQ_CONSTRAINT, + self.PARENT: self.OBJECTIVE_LAGR, + self.WEIGHT: 1.0, self.AGGR_TYPE: 'smax'} + parameters_dict[dict_aggr_ineq[self.VARIABLE]] = dict_aggr_ineq + dict_aggr_eq = {self.VARIABLE: self.EQ_CONSTRAINT, + self.PARENT: self.OBJECTIVE_LAGR, + self.WEIGHT: 1.0, self.AGGR_TYPE: 'smax'} + parameters_dict[dict_aggr_eq[self.VARIABLE]] = dict_aggr_eq + + # Lagrangian objective + dict_lagrangian = {self.VARIABLE: self.OBJECTIVE_LAGR, self.PARENT: None, + self.WEIGHT: 1.0, self.AGGR_TYPE: 'sum'} + parameters_dict[dict_lagrangian[self.VARIABLE]] = dict_lagrangian + parameters_df = pd.DataFrame(parameters_dict).transpose() + + return parameters_df, obj_list, ineq_list, eq_list + + def get_chart_lagrangian_objective_iterations(self, optim_output, main_parameters, sub_parameters=[], + name='lagrangian objective'): + """ + Function to create the post proc of aggregated objectives and constraints + A dropdown menu is used to select between: + -"Simple" : Simple scatter+line of lagrangian objective + -Detailed Contribution" : scatter+line of aggregated (sum*100) lagrangian objective + summed area of individual contributions (*100) + Inputs: main_parameters (lagrangian objective) name, list of sub-parameters (aggregated objectives, inequality constraints and + equality constraints) names and name of the plot + Ouput: instantiated plotly chart + """ + chart_name = f'{name} wrt iterations' + fig = go.Figure() + for parameter in main_parameters[self.VARIABLE]: + y = [value[0] for value in optim_output[parameter].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[parameter].values] + fig.add_trace(go.Scatter(x=list(optim_output['iteration'].values), + y=list(y), name=parameter, visible=True)) + for parameter in sub_parameters[self.VARIABLE]: + y = [value[0] * 100 for value in optim_output[parameter].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[parameter].values] + if sum(y) == 0: + continue + fig.add_trace(go.Scatter(x=list(optim_output['iteration'].values), + y=list(y), stackgroup='group', mode='none', name=parameter + ' (x100)', + visible=False)) + fig.update_layout(title={'text': chart_name, 'x': 0.5, 'y': 1.0, 'xanchor': 'center', 'yanchor': 'top'}, + xaxis_title='n iterations', yaxis_title=f'value of {name}') + + fig.update_layout( + updatemenus=[ + dict( + buttons=list([ + dict( + args=[{'visible': [False if scatter['stackgroup'] + == 'group' else True for scatter in fig.data]}, ], + label="Simple", + method="restyle" + ), + dict( + args=[{'visible': [True for _ in fig.data]}, ], + label="Detailed Contribution", + method="restyle" + ) + ]), + direction='down', + type='dropdown', + pad={"r": 0, "t": 0}, + showactive=True, + active=0, + x=1.0, + y=1.01, + yanchor='bottom', + xanchor='right' + ), + dict( + buttons=list([ + dict( + args=[{"yaxis.type": "linear"}], + label="Linear", + method="relayout" + ), + dict( + args=[{"yaxis.type": "log"}], + label="Log", + method="relayout" + ), + ]), + type="buttons", + direction="right", + pad={"r": 10, "t": 10}, + showactive=True, + active=0, + x=0.0, + xanchor="left", + y=1.01, + yanchor="bottom" + ), + ] + ) + + new_chart = InstantiatedPlotlyNativeChart( + fig, chart_name=chart_name, default_title=True) + return new_chart + + def get_chart_aggregated_iterations(self, optim_output, main_parameters, objectives={}, ineq_constraints={}, + eq_constraints={}, name='aggregated'): + """ + Function to create the post proc of aggregated objectives and constraints + A dropdown menu is used to select between: + -"All Aggregated" : Simple scatter+line of all the aggregated values + -"Objective - Detailed" : scatter+line of aggregated (sum) objective + summed area of individual contributions + -"Ineq Constraint - Detailed" : scatter+line of aggregated (smax) inequality constraint + area of individual contributions + -"Eq Constraint - Detailed": scatter+line of aggregated (smax) equality constraint + area of individual contributions + Inputs: main_parameters (aggregated) names, list of objectives, inequality constraints and equality constraints names, + and name of the plot + Ouput: instantiated plotly chart + """ + chart_name = f'{name} wrt iterations' + fig = go.Figure() + for parameter in main_parameters[self.VARIABLE]: + if 'objective' in parameter: + customdata = ['aggr', 'obj'] + elif 'ineq_constraint' in parameter: + customdata = ['aggr', 'ineq'] + elif 'eq_constraint' in parameter: + customdata = ['aggr', 'eq'] + else: + raise Exception(f"Can't get customdata for parameter {parameter}") + y = [value[0] for value in optim_output[parameter].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[parameter].values] + fig.add_trace(go.Scatter(x=list(optim_output['iteration'].values), + y=list(y), name=parameter, customdata=customdata, visible=True)) + + if len(objectives) > 0: + for parameter in objectives[self.VARIABLE]: + customdata = ['mod', 'obj'] + y = [value[0] for value in optim_output[parameter].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[parameter].values] + if sum(y) == 0: + continue + fig.add_trace(go.Scatter(x=list(optim_output['iteration'].values), + y=list(y), stackgroup='group_obj', mode='none', + name=parameter, customdata=customdata, visible=False)) + + if len(ineq_constraints) > 0: + for i, parameter in enumerate(ineq_constraints[self.VARIABLE]): + customdata = ['mod', 'ineq'] + y = [value[0] for value in optim_output[parameter].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[parameter].values] + if sum(y) == 0: + continue + fig.add_trace(go.Scatter(x=list(optim_output['iteration'].values), + y=list(y), stackgroup='group_ineq' + str(i), mode='none', + name=parameter, customdata=customdata, visible=False)) + + if len(eq_constraints) > 0: + for i, parameter in enumerate(eq_constraints[self.VARIABLE]): + customdata = ['mod', 'eq'] + y = [value[0] for value in optim_output[parameter].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[parameter].values] + if sum(y) == 0: + continue + fig.add_trace(go.Scatter(x=list(optim_output['iteration'].values), + y=list(y), stackgroup='group_eq' + str(i), mode='none', + name=parameter, customdata=customdata, visible=False)) + fig.update_layout(title={'text': chart_name, 'x': 0.5, 'y': 1.0, 'xanchor': 'center', 'yanchor': 'top'}, + xaxis_title='n iterations', yaxis_title=f'value of {name}') + + fig.update_layout( + updatemenus=[ + dict( + buttons=list([ + dict( + args=[{'visible': [True if scatter['customdata'][0] + == 'aggr' else False for scatter in fig.data]}, ], + label="All Aggregated", + method="restyle" + ), + dict( + args=[{'visible': [True if scatter['customdata'][1] + == 'obj' else False for scatter in fig.data]}, ], + label="Objective - Detailed", + method="restyle" + ), + dict( + args=[{'visible': [True if scatter['customdata'][1] + == 'ineq' else False for scatter in fig.data]}, ], + label="Ineq Constraint - Detailed", + method="restyle" + ), + dict( + args=[{'visible': [True if scatter['customdata'][1] + == 'eq' else False for scatter in fig.data]}, ], + label="Eq Constraint - Detailed", + method="restyle" + ), + ]), + direction='down', + type='dropdown', + pad={"r": 0, "t": 0}, + showactive=True, + active=0, + x=1.0, + y=1.01, + yanchor='bottom', + xanchor='right' + ), + dict( + buttons=list([ + dict( + args=[{"yaxis.type": "linear"}], + label="Linear", + method="relayout" + ), + dict( + args=[{"yaxis.type": "log"}], + label="Log", + method="relayout" + ), + ]), + type="buttons", + direction="right", + pad={"r": 10, "t": 10}, + showactive=True, + active=0, + x=0.0, + xanchor="left", + y=1.01, + yanchor="bottom" + ), + ] + ) + new_chart = InstantiatedPlotlyNativeChart( + fig, chart_name=chart_name, default_title=True) + return new_chart + + def get_chart_parameters_mod_iterations(self, optim_output, parameters_df, name): + """ + Function to create the post proc of objectives and constraints mod. + First all the values are put in a dataframe, then the parent-children links are 'calculated'. + Then it traces the grouped _mod and adds value, weight, aggregation type and children as hovertext. + Finally a dropdown menu is used to select the level to be displayed. + Inputs: parameters_dict[variable,parents,children,weights,aggr_type] and name of the plot + Output: instantiated plotly chart + """ + chart_name = f'{name} wrt iterations' + fig = go.Figure() + # Remove entries with weight = 0.0 + weight0_index = parameters_df[parameters_df[self.WEIGHT] + == '0.0'].index + parameters_df.drop(weight0_index, inplace=True) + # Add value column + parameters_df['value'] = [ + [0.0 for _ in range(optim_output.shape[0])] for _ in range(parameters_df.shape[0])] + + for parameter in parameters_df[self.VARIABLE]: + y = [value[0] for value in optim_output[parameter].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[parameter].values] + parameters_df.loc[parameters_df[self.VARIABLE] + == parameter, 'value'] = {parameter: y} + # Remove entries with values = 0.0 + parameters_df['isnull'] = parameters_df['value'].apply( + lambda x: all(val == 0.0 for val in x)) + value0_index = parameters_df[parameters_df['isnull']].index + parameters_df.drop(value0_index, inplace=True) + parameters_df.drop(columns=['isnull'], inplace=True) + # Create entries for all the groups in dataframe + # Find all parents by level (to calculate level-by-level) + n_level = int(max(np.append(0, np.asarray([0 if parent is None else len(parent.split('-')) + for parent in parameters_df[self.PARENT]])))) + level_list = dict.fromkeys(range(n_level), []) + parent_split = np.asarray([[] if parent is None else parent.split('-') + for parent in parameters_df[self.PARENT]], dtype=object) + for i in reversed(range(n_level)): + i_lvl = [] + for parent in parent_split: + if len(parent) - 1 >= i: + i_lvl += [parent[i], ] + level_list[i] = list(set(i_lvl)) + # parents_list = list(set(np.asarray([parent.split( + # '-') for parent in parameters_df[self.PARENT].to_list()]).flatten())) + + for lvl, parent_level in level_list.items(): + for parent in parent_level: + # if parent isn't in df + if parent not in parameters_df[self.VARIABLE]: + parent_list = [] + parameters_df['match'] = parameters_df[self.PARENT].apply( + lambda x: 'Match' if parent in str(x) else 'Mismatch') + for full_parent in parameters_df.loc[parameters_df['match'] == 'Match', self.PARENT]: + if full_parent is None: + continue + for i, split_full_parent in enumerate(full_parent.split('-')): + if parent == split_full_parent and i > 0: # if parent has a parent + parent_list.append( + full_parent.split('-')[i - 1]) + parent_parent = None + if len(list(set(parent_list))) > 0: + parent_parent = parent_list[0] + children_list = parameters_df.loc[parameters_df['match'] + == 'Match', self.VARIABLE] + parameters_df.drop(columns=['match'], inplace=True) + value = [sum(v) for v in zip( + *parameters_df.loc[children_list, 'value'])] + dict_parent = {self.VARIABLE: parent, + self.PARENT: parent_parent, + self.WEIGHT: 1.0, self.AGGR_TYPE: 'sum', + 'value': [value]} + parameters_df = pd.concat([parameters_df, + pd.DataFrame(dict_parent, index=[parent])], axis=0) + + for row in parameters_df.iterrows(): + vis = False + y = row[1]['value'] + if 'complex' in str(type(y[0])): + y = [np.real(value) + for value in row[1]['value']] + hovertemplate = '
X: %{x}' + '
Y: %{y:.2e}' + \ + '
weight: %{customdata[0]}' + \ + '
aggr_type: %{customdata[1]}' + is_mod = True if '_mod' in row[1][self.VARIABLE] else False + vis = True if row[1][self.PARENT] is None else False + customdata = [[str(row[1][self.WEIGHT]) for _ in range(len(y))], + [row[1][self.AGGR_TYPE] for _ in range(len(y))], + [row[1][self.PARENT] for _ in range(len(y))], + [is_mod for _ in range(len(y))]] + fig.add_trace(go.Scatter(x=list(optim_output['iteration'].values), + y=list(y), name=row[1][self.VARIABLE], customdata=list(np.asarray(customdata).T), + hovertemplate=hovertemplate, + visible=vis)) + + fig.update_layout(title={'text': chart_name, 'x': 0.5, 'y': 1.0, 'xanchor': 'center', 'yanchor': 'top'}, + xaxis_title='n iterations', yaxis_title=f'value of {name}') + + fig.update_layout( + updatemenus=[ + dict( + buttons=list( + [dict( + args=[ + {'visible': [True if scatter['customdata'][0][2] + == group else False for scatter in fig.data]}, + ], + label='grouped', + method="update" + ) for group in list(set(parameters_df[self.PARENT])) if group is None] + + [dict( + args=[ + {'visible': [scatter['customdata'][0][3] + for scatter in fig.data]}, + ], + label='All', + method="update" + )] + + [dict( + args=[ + {'visible': [True if scatter['customdata'][0][2] + == group else False for scatter in fig.data]}, + ], + label='group ' + str(group), + method="update" + ) for group in list(set(parameters_df[self.PARENT])) if group is not None] + ), + direction='down', + type='dropdown', + pad={"r": 0, "t": 0}, + showactive=True, + active=0, + x=1.0, + y=1.01, + yanchor='bottom', + xanchor='right' + ), + dict( + buttons=list([ + dict( + args=[{"yaxis.type": "linear"}], + label="Linear", + method="relayout" + ), + dict( + args=[{"yaxis.type": "log"}], + label="Log", + method="relayout" + ), + ]), + type="buttons", + direction="right", + pad={"r": 10, "t": 10}, + showactive=True, + active=0, + x=0.0, + xanchor="left", + y=1.01, + yanchor="bottom" + ), + ] + ) + + new_chart = InstantiatedPlotlyNativeChart( + fig, chart_name=chart_name, default_title=True) + return new_chart + + def get_chart_obj_constraints_iterations(self, func_df, optim_output, objectives, name): + """ + Function to create a summary post proc of the optim problem + In black : the aggregated objective + In colorscale from green to red : the sum of all the constraints (green == negative values) + Additionnal information such as the name and value of the dominant constraint are shown in the hovertext + Inputs: objective name, name of the plot and boolean for log scale + Ouput: instantiated plotly chart + """ + + chart_name = 'objective wrt iterations with constraints (colored)' + fig = go.Figure() + x = optim_output['iteration'].values + for obj in objectives: + y = [value[0] for value in optim_output[obj].values] + if 'complex' in str(type(y[0])): + y = [np.real(value[0]) + for value in optim_output[obj].values] + fig.add_trace(go.Scatter(x=list(x), y=list( + y), name=obj, line=dict(color='black'))) + func_dict = {row[self.VARIABLE] + '_mod': row['ftype'] + for i, row in func_df.iterrows()} + + for col in optim_output.columns: + if col not in ['iteration', ]: + optim_output[col] = optim_output[col].apply( + lambda x: x[0].real) + + ineq_constraints = optim_output[[ + key for key, value in func_dict.items() if value in [self.INEQ_CONSTRAINT]]].astype(float).reset_index( + drop=True) + eq_constraints = optim_output[[ + key for key, value in func_dict.items() if value in [self.EQ_CONSTRAINT]]].astype(float).reset_index( + drop=True) + + ineq_constraints_sum = ineq_constraints.sum(axis=1).fillna(0) + ineq_constraints_max = ineq_constraints.max(axis=1).fillna(0) + try: + ineq_constraints_max_col = ineq_constraints.idxmax(axis=1) + except: + ineq_constraints_max_col = None + + eq_constraints_sum = eq_constraints.sum(axis=1).fillna(0) + eq_constraints_max = eq_constraints.max(axis=1).fillna(0) + try: + eq_constraints_max_col = eq_constraints.idxmax(axis=1) + except: + eq_constraints_max_col = None + tot_constraints = pd.concat([ineq_constraints, eq_constraints], axis=1) + tot_constraints_sum = ineq_constraints_sum + eq_constraints_sum + tot_constraints_max = [max(row) for index, row in pd.concat( + [ineq_constraints_max, eq_constraints_max], axis=1).iterrows()] + try: + tot_constraints_max_col = [row.idxmax() + for index, row in tot_constraints.iterrows()] + tot_constraints_max = [row[tot_constraints_max_col[index]] + for index, row in tot_constraints.iterrows()] + except: + tot_constraints_max_col = [ + 0 for index, row in tot_constraints.iterrows()] + tot_constraints_max = [0 for index, + row in tot_constraints.iterrows()] + fm_ineq = [value + for value in optim_output[self.INEQ_CONSTRAINT].values] + fm_eq = [value + for value in optim_output[self.EQ_CONSTRAINT].values] + tot_constraints_text = [zipped for zipped in zip( + fm_ineq, fm_eq, tot_constraints_max_col, tot_constraints_max, tot_constraints_sum)] + hover_text = [ + 'Iteration : {}
INEQ constraint : {}
EQ constraint : {}
Max constraint name : {}
Max constraint value : {}
Summed constraint: {}'.format( + i + 1, t[0], t[1], t[2], t[3], t[4]) for i, t in enumerate(tot_constraints_text)] + + def f_log10(value): + if value < 0: + log10_value = -np.log10(abs(value)) - 1.0 + else: + log10_value = np.log10(value) + 1.0 + return log10_value + + values = [value if abs(value) <= 1.0 else f_log10(value) + for value in tot_constraints_sum] + + xmap = list(np.linspace(x[0] - 0.5, x[-1] + 0.5, len(x) + 1)) + dy = max(y) - min(y) + y0 = min(y) + dy / 2 + fig.add_trace(go.Heatmap(x=xmap, y0=y0, dy=dy, z=[values, ], hoverinfo='text', hovertext=[hover_text, ], + colorscale=['green', 'white', 'red'], opacity=0.5, + zmid=0.0, + colorbar={"title": 'total constraints (sum) [symlog]', + 'x': 1.1, 'yanchor': "middle", 'titleside': 'right'})) + + fig.update_layout(title={'text': chart_name, 'x': 0.5, 'y': 1.0, 'xanchor': 'center', 'yanchor': 'top'}, + xaxis_title='n iterations', yaxis_title=f'value of {name}') + fig.update_layout( + updatemenus=[ + dict( + buttons=list([ + dict( + args=[{"yaxis.type": "linear"}], + label="Linear", + method="relayout" + ), + dict( + args=[{"yaxis.type": "log"}], + label="Log", + method="relayout" + ), + ]), + type="buttons", + direction="right", + pad={"r": 10, "t": 10}, + showactive=True, + active=0, + x=0.0, + xanchor="left", + y=1.01, + yanchor="bottom" + ), + ] + ) + new_chart = InstantiatedPlotlyNativeChart( + fig, chart_name=chart_name, default_title=True) + return new_chart + + def __build_mod_names(self, f): + ''' returns the functions out names + ''' + return f + self.MOD_SUFFIX + + def check_value_range(self, fvalue_df, fname: str, ftype: str): + """Log some warnings if value is not between 0 and 1""" + if isinstance(fvalue_df, np.ndarray): + if fvalue_df.max() > 1: + self.logger.warning(f"{ftype} {fname} maximum is above 1 ({fvalue_df.max()}). All its values should be between 0 and 1") + if fvalue_df.min() < 0: + self.logger.warning(f"{ftype} {fname} minimum is lower than 1 ({fvalue_df.min()}). All its values should be between 0 and 1") diff --git a/sostrades_optimization_plugins/sos_processes/__init__.py b/sostrades_optimization_plugins/sos_processes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/sos_processes/test/__init__.py b/sostrades_optimization_plugins/sos_processes/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py new file mode 100644 index 0000000..0815bbd --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/process.py new file mode 100644 index 0000000..ee2b130 --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/process.py @@ -0,0 +1,50 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from sostrades_core.sos_processes.base_process_builder import BaseProcessBuilder + +""" +mode: python; py-indent-offset: 4; tab-width: 4; coding: utf-8 +Generate an optimization scenario +""" + + +class ProcessBuilder(BaseProcessBuilder): + + # ontology information + _ontology_data = { + 'label': 'Core Test Sellar Coupling Process for design var', + 'description': '', + 'category': '', + 'version': '', + } + + def get_builders(self): + ''' + default initialisation test + ''' + # add disciplines Sellar + disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar_for_design_var.' + mods_dict = {'Sellar_Problem': disc_dir + 'SellarProblem', + 'Sellar_2': disc_dir + 'Sellar2', + 'Sellar_1': disc_dir + 'Sellar1'} + + builder__list = self.create_builder_list( + mods_dict, ns_dict={'ns_OptimSellar': self.ee.study_name + '.SellarCoupling'}) + coupling_builder = self.ee.factory.create_builder_coupling( + "SellarCoupling") + coupling_builder.set_builder_info('cls_builder', builder__list) + + return coupling_builder diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py new file mode 100644 index 0000000..1e2356c --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py @@ -0,0 +1,50 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import pandas as pd +from numpy import array + +from sostrades_core.study_manager.study_manager import StudyManager + + +class Study(StudyManager): + + def __init__(self, execution_engine=None): + super().__init__(__file__, execution_engine=execution_engine) + + def setup_usecase(self): + ns = f'{self.study_name}' + coupling_name = "SellarCoupling" + + disc_dict = {} + # Sellar inputs + disc_dict[f'{ns}.{coupling_name}.x'] = pd.DataFrame(data={'index': [0,1,2,3], 'value' : [1., 1., 1., 1.]}) + disc_dict[f'{ns}.{coupling_name}.y_1'] = 1. + disc_dict[f'{ns}.{coupling_name}.y_2'] = 1. + disc_dict[f'{ns}.{coupling_name}.z'] = array([5., 2.]) + disc_dict[f'{ns}.{coupling_name}.Sellar_Problem.local_dv'] = 10. + disc_dict[f'{ns}.{coupling_name}.max_mda_iter'] = 100 + disc_dict[f'{ns}.{coupling_name}.tolerance'] = 1e-12 + disc_dict[f'{ns}.{coupling_name}.sub_mda_class'] = 'MDAGaussSeidel' + + return [disc_dict] + + +if '__main__' == __name__: + uc_cls = Study() + uc_cls.load_data() + uc_cls.execution_engine.display_treeview_nodes(display_variables=True) + uc_cls.run() +# uc_cls.execution_engine.root_process.coupling_structure.graph.write_full_graph("here.pdf") diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/process.py new file mode 100644 index 0000000..d133b6d --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/process.py @@ -0,0 +1,68 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from sostrades_core.sos_processes.base_process_builder import BaseProcessBuilder + +""" +mode: python; py-indent-offset: 4; tab-width: 4; coding: utf-8 +Generate an optimization scenario +""" + + +class ProcessBuilder(BaseProcessBuilder): + + # ontology information + _ontology_data = { + 'label': 'Core Test Sellar Opt with Design Var', + 'description': '', + 'category': '', + 'version': '', + } + + def get_builders(self): + ''' + default initialisation test + ''' + # add disciplines Sellar + disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar_for_design_var.' + + mod_func = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + mod_dv = 'sostrades_optimization_plugins.models.design_var.design_var_disc.DesignVarDiscipline' + + mods_dict = {'DesignVar': mod_dv, + 'Sellar_Problem': disc_dir + 'SellarProblem', + 'Sellar_2': disc_dir + 'Sellar2', + 'Sellar_1': disc_dir + 'Sellar1', + 'FunctionManager': mod_func, + + } + + ns_dict = {'ns_optim': self.ee.study_name + '.SellarOptimScenario', + 'ns_OptimSellar': self.ee.study_name + '.SellarOptimScenario', + 'ns_functions': self.ee.study_name + '.SellarOptimScenario' + } + + builder_list = self.create_builder_list(mods_dict, ns_dict=ns_dict) + + # coupling + coupling_builder = self.ee.factory.create_builder_coupling( + "SellarCoupling") + coupling_builder.set_builder_info('cls_builder', builder_list) + + opt_builder = self.ee.factory.create_optim_builder( + 'SellarOptimScenario', [coupling_builder]) + + return opt_builder diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py new file mode 100644 index 0000000..4a91a12 --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py @@ -0,0 +1,115 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2023/04/13-2024/05/16 Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import pandas as pd +from numpy import arange, array + +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) +from sostrades_core.study_manager.study_manager import StudyManager + +AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE +AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM +AGGR_TYPE_SMAX = FunctionManager.AGGR_TYPE_SMAX + + +class Study(StudyManager): + + def __init__(self, run_usecase=True, execution_engine=None): + super().__init__(__file__, run_usecase=run_usecase, execution_engine=execution_engine) + self.optim_name = "SellarOptimScenario" + self.coupling_name = "SellarCoupling" + + def setup_usecase(self): + INEQ_CONSTRAINT = FunctionManager.INEQ_CONSTRAINT + OBJECTIVE = FunctionManager.OBJECTIVE + + ns = f'{self.study_name}' + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [array([1., 2., 3., 4.]), array([5., 2.])], + 'lower_bnd': [array([0., 0., 0., 0.]), array([-10., 0.])], + 'upper_bnd': [array([10., 10., 10., 10.]), array([10., 10.])], + 'enable_variable': [True, True], + 'activated_elem': [[True], [True, True]]} + dspace = pd.DataFrame(dspace_dict) + + design_var_descriptor = {'x_in': {'out_name': 'x', + 'out_type': 'dataframe', + 'key': 'value', + 'index': arange(0, 4, 1), + 'index_name': 'index', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + }, + 'z_in': {'out_name': 'z', + 'out_type': 'array', + 'index': [0, 1], + 'index_name': 'index', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + } + } + + disc_dict = {} + disc_dict[ + f'{ns}.{self.optim_name}.{self.coupling_name}.DesignVar.design_var_descriptor'] = design_var_descriptor + + # Optim inputs + disc_dict[f'{ns}.{self.optim_name}.max_iter'] = 100 + disc_dict[f'{ns}.{self.optim_name}.algo'] = "L-BFGS-B" + disc_dict[f'{ns}.{self.optim_name}.design_space'] = dspace + disc_dict[f'{ns}.{self.optim_name}.formulation'] = 'DisciplinaryOpt' + disc_dict[f'{ns}.{self.optim_name}.objective_name'] = 'objective_lagrangian' + disc_dict[f'{ns}.{self.optim_name}.ineq_constraints'] = [] + disc_dict[f'{ns}.{self.optim_name}.algo_options'] = { + # "maxls": 6, + # "maxcor": 3, + "ftol_rel": 1e-15, + + } + + # Sellar and design var inputs + disc_dict[f'{ns}.{self.optim_name}.x_in'] = array([1., 1., 1., 1.]) + disc_dict[f'{ns}.{self.optim_name}.y_1'] = 5. + disc_dict[f'{ns}.{self.optim_name}.y_2'] = 1. + disc_dict[f'{ns}.{self.optim_name}.z_in'] = array([5., 2.]) + disc_dict[f'{ns}.{self.optim_name}.{self.coupling_name}.Sellar_Problem.local_dv'] = 10. + + func_df = pd.DataFrame( + columns=['variable', 'ftype', 'weight', AGGR_TYPE]) + func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] + func_df['weight'] = [200, 0.000001, 0.1] + func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] + func_mng_name = 'FunctionManager' + + prefix = f'{self.study_name}.{self.optim_name}.{self.coupling_name}.{func_mng_name}.' + values_dict = {} + values_dict[prefix + + FunctionManagerDisc.FUNC_DF] = func_df + + disc_dict.update(values_dict) + + return [disc_dict] + + +if '__main__' == __name__: + uc_cls = Study() + uc_cls.load_data() + uc_cls.execution_engine.display_treeview_nodes(display_variables=True) + uc_cls.run() diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py new file mode 100644 index 0000000..0815bbd --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/process.py new file mode 100644 index 0000000..2bc401f --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/process.py @@ -0,0 +1,65 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from sostrades_core.sos_processes.base_process_builder import BaseProcessBuilder + +""" +mode: python; py-indent-offset: 4; tab-width: 4; coding: utf-8 +Generate an optimization scenario +""" + + +class ProcessBuilder(BaseProcessBuilder): + + # ontology information + _ontology_data = { + 'label': 'Core Test Sellar SubOpt with Design Var and Func Manager on a double level', + 'description': '', + 'category': '', + 'version': '', + } + + def get_builders(self): + ''' + default initialisation test + ''' + # add disciplines Sellar + disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar_for_design_var.' + + mod_func = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + mod_dv = 'sostrades_optimization_plugins.models.design_var.design_var_disc.DesignVarDiscipline' + + mods_dict = {'DesignVar': mod_dv, + 'Sellar_Problem': disc_dir + 'SellarProblem', + 'Sellar_2': disc_dir + 'Sellar2', + 'Sellar_1': disc_dir + 'Sellar1', + 'FunctionManager': mod_func, + + } + + ns_dict = {'ns_optim': self.ee.study_name , + 'ns_OptimSellar': self.ee.study_name , + 'ns_functions': self.ee.study_name + } + + builder_list = self.create_builder_list(mods_dict, ns_dict=ns_dict) + + # coupling + coupling_builder = self.ee.factory.create_builder_coupling( + "SellarCoupling") + coupling_builder.set_builder_info('cls_builder', builder_list) + + + return coupling_builder diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py new file mode 100644 index 0000000..0f0b77c --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py @@ -0,0 +1,108 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import pandas as pd +from numpy import arange, array + +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) +from sostrades_core.study_manager.study_manager import StudyManager + +AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE +AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM +AGGR_TYPE_SMAX = FunctionManager.AGGR_TYPE_SMAX + + +class Study(StudyManager): + + def __init__(self, run_usecase=True, execution_engine=None): + super().__init__(__file__, run_usecase=run_usecase, execution_engine=execution_engine) + self.optim_name = "SellarOptimScenario" + self.coupling_name = "SellarCoupling" + + def setup_usecase(self): + INEQ_CONSTRAINT = FunctionManager.INEQ_CONSTRAINT + OBJECTIVE = FunctionManager.OBJECTIVE + + ns = f'{self.study_name}' + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [array([1., 2., 3., 4.]), array([5., 2.])], + 'lower_bnd': [array([0., 0., 0., 0.]), array([-10., 0.])], + 'upper_bnd': [array([10., 10., 10., 10.]), array([10., 10.])], + 'enable_variable': [True, True], + 'activated_elem': [[True], [True, True]]} + dspace = pd.DataFrame(dspace_dict) + + design_var_descriptor = {'x_in': {'out_name': 'x', + 'out_type': 'dataframe', + 'key': 'value', + 'index': arange(0, 4, 1), + 'index_name': 'index', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + }, + 'z_in': {'out_name': 'z', + 'out_type': 'array', + 'index': [0, 1], + 'index_name': 'index', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + } + } + + disc_dict = {} + disc_dict[ + f'{ns}.{self.coupling_name}.DesignVar.design_var_descriptor'] = design_var_descriptor + + + # Sellar and design var inputs + disc_dict[f'{ns}.x_in'] = array([1., 1., 1., 1.]) + disc_dict[f'{ns}.y_1'] = 5. + disc_dict[f'{ns}.y_2'] = 1. + disc_dict[f'{ns}.z_in'] = array([5., 2.]) + disc_dict[f'{ns}.max_mda_iter'] = 50 + disc_dict[f'{ns}.design_space'] = dspace + disc_dict[f'{ns}.{self.coupling_name}.sub_mda_class'] = 'MDAGaussSeidel' + + disc_dict[f'{ns}.{self.coupling_name}.max_mda_iter'] = 50 + disc_dict[f'{ns}.tolerance'] = 1e-16 + disc_dict[f'{ns}.{self.coupling_name}.tolerance'] = 1e-16 + disc_dict[f'{ns}.{self.coupling_name}.Sellar_Problem.local_dv'] = 10. + + func_df = pd.DataFrame( + columns=['variable', 'ftype', 'weight', AGGR_TYPE]) + func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] + func_df['weight'] = [200, 0.000001, 0.1] + func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] + func_mng_name = 'FunctionManager' + + prefix = f'{self.study_name}.{self.coupling_name}.{func_mng_name}.' + values_dict = {} + values_dict[prefix + + FunctionManagerDisc.FUNC_DF] = func_df + + disc_dict.update(values_dict) + + return [disc_dict] + + +if '__main__' == __name__: + uc_cls = Study() + uc_cls.load_data() + uc_cls.execution_engine.display_treeview_nodes(display_variables=True) + uc_cls.run() diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py new file mode 100644 index 0000000..15afa72 --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py @@ -0,0 +1,60 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from sostrades_core.sos_processes.base_process_builder import BaseProcessBuilder + +""" +mode: python; py-indent-offset: 4; tab-width: 4; coding: utf-8 +Generate an optimization scenario +""" + +class ProcessBuilder(BaseProcessBuilder): + + # ontology information + _ontology_data = { + 'label': 'Core Test Sellar Opt with Func Manager', + 'description': '', + 'category': '', + 'version': '', + } + def get_builders(self): + ''' + default initialisation test + ''' + # add disciplines Sellar + disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar.' + + mod_func = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + + mods_dict = {'Sellar_Problem': disc_dir + 'SellarProblem', + 'Sellar_2': disc_dir + 'Sellar2', + 'Sellar_1': disc_dir + 'Sellar1', + 'FunctionManager': mod_func} + + ns_dict = {'ns_functions': self.ee.study_name + '.SellarOptimScenario', + 'ns_optim': self.ee.study_name + '.SellarOptimScenario', + 'ns_OptimSellar': self.ee.study_name + '.SellarOptimScenario'} + + builder_list = self.create_builder_list(mods_dict, ns_dict=ns_dict) + + coupling_builder = self.ee.factory.create_builder_coupling( + "SellarCoupling") + coupling_builder.set_builder_info('cls_builder', builder_list) + + opt_builder = self.ee.factory.create_optim_builder( + 'SellarOptimScenario', [coupling_builder]) + + return opt_builder diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py new file mode 100644 index 0000000..bd8f056 --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py @@ -0,0 +1,100 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import pandas as pd +from numpy import array + +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) +from sostrades_core.study_manager.study_manager import StudyManager + +AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE +AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM +AGGR_TYPE_SMAX = FunctionManager.AGGR_TYPE_SMAX + + +class Study(StudyManager): + + def __init__(self, run_usecase=True, execution_engine=None): + super().__init__(__file__, run_usecase=run_usecase, execution_engine=execution_engine) + self.optim_name = "SellarOptimScenario" + self.coupling_name = "SellarCoupling" + + def setup_usecase(self): + + INEQ_CONSTRAINT = FunctionManager.INEQ_CONSTRAINT + OBJECTIVE = FunctionManager.OBJECTIVE + ns = f'{self.study_name}' + dspace_dict = {'variable': ['x', 'z', 'y_1', 'y_2'], + 'value': [[1.], [5., 2.], [5.], [1.]], + 'lower_bnd': [[0.], [-10., 0.], [-100.], [-100.]], + 'upper_bnd': [[10.], [10., 10.], [100.], [100.]], + 'enable_variable': [True, True, True, True], + 'activated_elem': [[True], [True, True], [True], [True]]} + # 'type' : ['float',['float','float'],'float','float'] + dspace = pd.DataFrame(dspace_dict) + + disc_dict = {} + # Optim inputs + disc_dict[f'{ns}.{self.optim_name}.max_iter'] = 500 + disc_dict[f'{ns}.{self.optim_name}.algo'] = "SLSQP" + disc_dict[f'{ns}.{self.optim_name}.design_space'] = dspace + # TODO: what's wrong with IDF + disc_dict[f'{ns}.{self.optim_name}.formulation'] = 'DisciplinaryOpt' + # f'{ns}.{optim_name}.obj' + disc_dict[f'{ns}.{self.optim_name}.objective_name'] = 'objective_lagrangian' + disc_dict[f'{ns}.{self.optim_name}.ineq_constraints'] = [ + ] + # f'{ns}.{self.optim_name}.c_1', f'{ns}.{self.optim_name}.c_2'] + + disc_dict[f'{ns}.{self.optim_name}.algo_options'] = { + #"maxls": 6, + #"maxcor": 3, + "ftol_rel": 1e-15, + + } + + # Sellar inputs + disc_dict[f'{ns}.{self.optim_name}.x'] = array([1.]) + disc_dict[f'{ns}.{self.optim_name}.y_1'] = array([1.]) + disc_dict[f'{ns}.{self.optim_name}.y_2'] = array([1.]) + disc_dict[f'{ns}.{self.optim_name}.z'] = array([1., 1.]) + disc_dict[f'{ns}.{self.optim_name}.{self.coupling_name}.Sellar_Problem.local_dv'] = 10. + + func_df = pd.DataFrame( + columns=['variable', 'ftype', 'weight', AGGR_TYPE]) + func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] + func_df['weight'] = [200, 0.000001, 0.1] + func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] + func_mng_name = 'FunctionManager' + + prefix = self.study_name + f'.{self.optim_name}.{self.coupling_name}.' + func_mng_name + '.' + values_dict = {} + values_dict[prefix + + FunctionManagerDisc.FUNC_DF] = func_df + + disc_dict.update(values_dict) + return [disc_dict] + + +if '__main__' == __name__: + uc_cls = Study() + uc_cls.load_data() + uc_cls.execution_engine.display_treeview_nodes(display_variables=True) + uc_cls.run() diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py new file mode 100644 index 0000000..86f8a62 --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py @@ -0,0 +1,57 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from sostrades_core.sos_processes.base_process_builder import BaseProcessBuilder + +""" +mode: python; py-indent-offset: 4; tab-width: 4; coding: utf-8 +Generate an optimization scenario +""" + +class ProcessBuilder(BaseProcessBuilder): + + # ontology information + _ontology_data = { + 'label': 'Core Test Sellar Opt with Func Manager Faulty', + 'description': '', + 'category': '', + 'version': '', + } + def get_builders(self): + ''' + default initialisation test + ''' + # add disciplines Sellar + disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar.' + + mod_func = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + + mods_dict = {'Sellar_Problem': disc_dir + 'SellarProblem', + 'Sellar_1': disc_dir + 'Sellar1', + 'Sellar_3': disc_dir + 'Sellar3', + 'FunctionManager': mod_func} + + ns_dict = {'ns_functions': self.ee.study_name + '.' + 'SellarOptimScenario.SellarCoupling', + 'ns_optim': self.ee.study_name + '.' + 'SellarOptimScenario.SellarCoupling', + 'ns_OptimSellar': self.ee.study_name + '.SellarOptimScenario.SellarCoupling'} + builder_list = self.create_builder_list(mods_dict, ns_dict=ns_dict) + coupling_builder = self.ee.factory.create_builder_coupling("SellarCoupling") + coupling_builder.set_builder_info('cls_builder', builder_list) + #coupling_builder.set_builder_info('with_data_io', True) + opt_builder = self.ee.factory.create_optim_builder( + 'SellarOptimScenario', [coupling_builder]) + + return opt_builder diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py new file mode 100644 index 0000000..25627af --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py @@ -0,0 +1,98 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import pandas as pd +from numpy import array + +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) +from sostrades_core.study_manager.study_manager import StudyManager + +AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE +AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM +AGGR_TYPE_SMAX = FunctionManager.AGGR_TYPE_SMAX + + +class Study(StudyManager): + + def __init__(self, execution_engine=None): + super().__init__(__file__, execution_engine=execution_engine) + self.optim_name = "SellarOptimScenario" + self.subcoupling_name = "SellarCoupling" + self.coupling_name = "Sellar_Problem" + + def setup_usecase(self): + + INEQ_CONSTRAINT = FunctionManager.INEQ_CONSTRAINT + OBJECTIVE = FunctionManager.OBJECTIVE + ns = f'{self.study_name}' + dspace_dict = {'variable': ['x', 'z', 'y_1', 'y_2'], + 'value': [[1.], [5., 2.], [5.], [1.]], + 'lower_bnd': [[0.], [-10., 0.], [-100.], [-100.]], + 'upper_bnd': [[10.], [10., 10.], [100.], [100.]], + 'enable_variable': [True, True, True, True], + 'activated_elem': [[True], [True, True], [True], [True]]} + # 'type' : ['float',['float','float'],'float','float'] + dspace = pd.DataFrame(dspace_dict) + + disc_dict = {} + # Optim inputs + disc_dict[f'{ns}.{self.optim_name}.max_iter'] = 500 + disc_dict[f'{ns}.{self.optim_name}.algo'] = "SLSQP" + disc_dict[f'{ns}.{self.optim_name}.design_space'] = dspace + # TODO: what's wrong with IDF + disc_dict[f'{ns}.{self.optim_name}.formulation'] = 'DisciplinaryOpt' + # f'{ns}.{optim_name}.obj' + disc_dict[f'{ns}.{self.optim_name}.objective_name'] = 'objective_lagrangian' + disc_dict[f'{ns}.{self.optim_name}.ineq_constraints'] = [ + ] + # f'{ns}.{self.optim_name}.c_1', f'{ns}.{self.optim_name}.c_2'] + + disc_dict[f'{ns}.{self.optim_name}.algo_options'] = {"ftol_rel": 1e-10, + "ineq_tolerance": 2e-3, + "normalize_design_space": False} + + # Sellar inputs + disc_dict[f'{ns}.{self.optim_name}.{self.subcoupling_name}.x'] = array([1.]) + disc_dict[f'{ns}.{self.optim_name}.{self.subcoupling_name}.z'] = array([1., 1.]) + disc_dict[f'{ns}.{self.optim_name}.{self.subcoupling_name}.{self.coupling_name}.local_dv'] = 10. + disc_dict[f'{ns}.{self.optim_name}.{self.subcoupling_name}.sub_mda_class'] = 'PureNewtonRaphson' + disc_dict[f'{ns}.{self.optim_name}.max_iter'] = 2 + + func_df = pd.DataFrame( + columns=['variable', 'ftype', 'weight', AGGR_TYPE]) + func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] + func_df['weight'] = [200, 0.000001, 0.1] + func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] + func_mng_name = 'FunctionManager' + + prefix = self.study_name + f'.{self.optim_name}.' + f'{self.subcoupling_name}.' + func_mng_name + '.' + values_dict = {} + values_dict[prefix + + FunctionManagerDisc.FUNC_DF] = func_df + + disc_dict.update(values_dict) + return [disc_dict] + + +if '__main__' == __name__: + uc_cls = Study() + uc_cls.load_data() + uc_cls.execution_engine.display_treeview_nodes(display_variables=True) + uc_cls.run() diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py new file mode 100644 index 0000000..0815bbd --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/process.py new file mode 100644 index 0000000..c27b490 --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/process.py @@ -0,0 +1,71 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from sostrades_core.sos_processes.base_process_builder import BaseProcessBuilder + +""" +mode: python; py-indent-offset: 4; tab-width: 4; coding: utf-8 +Generate an optimization scenario +""" + + +class ProcessBuilder(BaseProcessBuilder): + + # ontology information + _ontology_data = { + 'label': 'Core Test Sellar SubOpt process with Design Var and Func Manager', + 'description': '', + 'category': '', + 'version': '', + } + + def get_builders(self): + ''' + default initialisation test + ''' + # add disciplines Sellar + disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar_for_design_var.' + + mod_func = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + mod_dv = 'sostrades_optimization_plugins.models.design_var.design_var_disc.DesignVarDiscipline' + + mods_dict = { + 'Sellar_Problem': disc_dir + 'SellarProblem', + 'Sellar_2': disc_dir + 'Sellar2', + 'Sellar_1': disc_dir + 'Sellar1', + + } + + ns_dict = {'ns_optim': self.ee.study_name + '.Sellar.SellarOptimScenario', + 'ns_OptimSellar': self.ee.study_name + '.Sellar.SellarOptimScenario', + 'ns_functions': self.ee.study_name + '.Sellar.SellarOptimScenario' + } + + builder_list = self.create_builder_list(mods_dict, ns_dict=ns_dict) + + # coupling + coupling_builder = self.ee.factory.create_builder_coupling( + "SellarCoupling") + coupling_builder.set_builder_info('cls_builder', builder_list) + + mods_dict_bis = {'FunctionManager': mod_func, + 'DesignVar': mod_dv} + + builder_fm_ds_list = self.create_builder_list(mods_dict_bis, ns_dict=ns_dict) + builder_fm_ds_list.append(coupling_builder) + coupling_builders = self.ee.factory.create_builder_coupling( + "Sellar") + coupling_builders.set_builder_info('cls_builder', builder_fm_ds_list) + return coupling_builders diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py new file mode 100644 index 0000000..8549b0b --- /dev/null +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py @@ -0,0 +1,102 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import pandas as pd +from numpy import arange, array + +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) +from sostrades_core.study_manager.study_manager import StudyManager + +AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE +AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM +AGGR_TYPE_SMAX = FunctionManager.AGGR_TYPE_SMAX + + +class Study(StudyManager): + + def __init__(self, run_usecase=True, execution_engine=None): + super().__init__(__file__, run_usecase=run_usecase, execution_engine=execution_engine) + self.optim_name = "SellarOptimScenario" + self.coupling_name = "Sellar.SellarCoupling" + + def setup_usecase(self): + INEQ_CONSTRAINT = FunctionManager.INEQ_CONSTRAINT + OBJECTIVE = FunctionManager.OBJECTIVE + + ns = f'{self.study_name}' + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [array([1., 2., 3., 4.]), array([5., 2.])], + 'lower_bnd': [array([0., 0., 0., 0.]), array([-10., 0.])], + 'upper_bnd': [array([10., 10., 10., 10.]), array([10., 10.])], + 'enable_variable': [True, True], + 'activated_elem': [[True], [True, True]]} + dspace = pd.DataFrame(dspace_dict) + + design_var_descriptor = {'x_in': {'out_name': 'x', + 'out_type': 'dataframe', + 'key': 'value', + 'index': arange(0, 4, 1), + 'index_name': 'index', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + }, + 'z_in': {'out_name': 'z', + 'out_type': 'array', + 'index': [0, 1], + 'index_name': 'index', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + } + } + + disc_dict = {} + disc_dict[ + f'{ns}.Sellar.DesignVar.design_var_descriptor'] = design_var_descriptor + disc_dict[f'{ns}.Sellar.{self.optim_name}.design_space'] = dspace + + + # Sellar and design var inputs + disc_dict[f'{ns}.Sellar.{self.optim_name}.x_in'] = array([1., 1., 1., 1.]) + disc_dict[f'{ns}.Sellar.{self.optim_name}.y_1'] = 5. + disc_dict[f'{ns}.Sellar.{self.optim_name}.y_2'] = 1. + disc_dict[f'{ns}.Sellar.{self.optim_name}.z_in'] = array([5., 2.]) + disc_dict[f'{ns}.{self.coupling_name}.Sellar_Problem.local_dv'] = 10. + + func_df = pd.DataFrame( + columns=['variable', 'ftype', 'weight', AGGR_TYPE]) + func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] + func_df['weight'] = [200, 0.000001, 0.1] + func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] + func_mng_name = 'FunctionManager' + + prefix = f'{self.study_name}.Sellar.{func_mng_name}.' + values_dict = {} + values_dict[prefix + + FunctionManagerDisc.FUNC_DF] = func_df + + disc_dict.update(values_dict) + + return [disc_dict] + + +if '__main__' == __name__: + uc_cls = Study() + uc_cls.load_data() + uc_cls.execution_engine.display_treeview_nodes(display_variables=True) + uc_cls.run() diff --git a/sostrades_optimization_plugins/sos_wrapping/__init__.py b/sostrades_optimization_plugins/sos_wrapping/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/tests/__init__.py b/sostrades_optimization_plugins/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario.cpython-39-pytest-7.4.3.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10470319820df75ac6bb6844f3f5a2d6dc8fbe30 GIT binary patch literal 2980 zcmbtWOK&5`5uO;7>*H`mSkJ7O&LqA4L6iDS~E?m z<#}!Q%t{ofOV*bh@(YY0(lN;Y$RWoZ2fpRxzmP+msvb(F=u?mp*i(;wR9AiVjrhWX zZ^84oKNf?(ELzsTP?>&QP~L-|{s#=9MXk^doyew+dDmjsv1}`@(}rDnnzU&=9`)c| zi{@gV`V(J^&V$X37UD&^Xl(W9PTZ#Lc!@33OU1Ny+oUi5mr zOjjoI8z3(mc`o`%{E$Ahtq-lx4_jYbVM}-?KYdlBkHUHJv#%WWck$?zMTsDnZ(c#P zq0_h47WlDaS^jpKjg%Z7=VWVRYm0QHIw~~jcl-XQX($IW;JHi_P0|D>ytTfuxxV%E z35ZYEH-5Fg`D=1(gSW^w&xRtFiS+$XWFQhPLQ*85P$WMVq?_>oUK7t_^0`nNwkKPi z4e|?|usU(AuKmUzr3H!kh$LxF3N2s*smVY_f{3#~WI2%u3DP)=Bu@fCPGx><4w-Dx z@xPpGkRIh6=5m;wjYv9}o<(@>`vjijJkQ=)Uq3xP?Qj!iCso7ssJeq*|734_x7Xi& z0+IUu^CS{l6D3|0Qo#j}MucY&N5GFDx`>~WR1rQ@0(^Ojah@uf%VhYN=yZ^uawYsw z>RidABEOZON$dn}Iz0hW#1pdG?UTL!DtW)#-|Ij2zt}tYX#e>E`J(&mS+{qvx7#QC z&&c+EZ)fjdZ@&lH2c+Bkl6<_^+j&d`qz9@X&N79uLx2)fC&G^37nt0evXB8dQP-IW z5CU+cWLWT_Aj9;fPzhXwWJ1MKWAQYE7W$ElWj?{m5B0t&#dpv=R&UKNy-i}}= z)1Cc3V1z4~t~q6$E5%1;TZ^1!N+x-l8SH^;g=;NT&ddzPqd3($gY5IDynD-KY_Rp6 z-7^ssxO_Jm%0z&v-p`8=kG=mHto}@;0ptO$@&OO>RE+|RaQcDns2YD61YuDdnje~; z)zx1GfNIuEa+phk2x2z=z@9o0d8i4$ZTRVL zVF>N9#jG606YCYq4oJ>P?b5{%uAB?>pk5!_SGH}TcGyO;jBj%#21-pDZb8!N1bjc=?!*FxN-(mkWkN$2LA*4Vv~ z=kZKD@nqp-G4xlgi^jNdc?V8w!n-|gUfCDk*n>0Oi@6v73@u1pt4AA{xy`NfH#HPQ z@la?Mq)K!^1pt`(2;ISy(MLEkEux4k2Do6SY)~Wti+KXLRBJYEi|dLrVM=~}ztcDK zZxyCPy_ladnG5wDCYriD8m4p!@NgmmXvPmA5Gj&eVQJQuN;l!LP#!3yBShI8RB)lo zLxAZ~AtT^6Hc)AN9Wo^d;E6}GJcUaY&}qr=+E~s7X)=(*LJ2j8G58vx$?zn)0IQ3P z0pSH)i?UH?_X=@jS_*0($)iF9HQC74oNzm@hvLBOK zRm~|b!wvpz}GKlY0Tq6)N)Pe6 z4NPTVpjOaU3nE?CfybU_Q|Q6=RtTZed$&U0Z;@j4BN*KFs%Z&m3v3E#lldC_`FG#; Hpn?7iT@ST6 literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario_with_func_manager.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario_with_func_manager.cpython-39-pytest-7.4.3.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dc8835031c00b7ec7b1f3813341da6f4aefe428 GIT binary patch literal 2558 zcmbtW&u`l{6qaP!mKEn$fdcIg1(9OI0wi{lcEivOL%k&JjCIaXCkrZMBN&Nxm`b8T zQgs~Ir}nzTfZd7#$#MVAj=~NDcIv;d!?s7+ahT?ogAMbC@W^8r0>d)`Gq6I_v+{Q#vMs|jqM}zaN0PEv21y}wqKa3^_w3M( zX1p2Gcx(j4p!CWJO0;}jeOd5kgEF0aZix<^eQ9{}bpCwd0&WYewz2GxBg-({O`h}x z>mDhzv9_^+8cZCf615v`_iG-o4)cj(JeG*ZTH#)OZN0v6e-)

uVp?*FQ#AF1Uv_ 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 Q*R09>kCk(@1vBb@0n$d)mH+?% literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/__pycache__/l1_test_01_gradient_design_var_disc.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l1_test_01_gradient_design_var_disc.cpython-39-pytest-7.4.3.pyc new file mode 100644 index 0000000000000000000000000000000000000000..842b2dca653be40130ac5871e65694af77f5e780 GIT binary patch literal 7552 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 Cv+>6O literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/__pycache__/l1_test_gradient_sellar_optim_subprocess.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l1_test_gradient_sellar_optim_subprocess.cpython-39-pytest-7.4.3.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86e8f8041d15ea7e184ec542b51488e037803eb2 GIT binary patch literal 18638 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 A@c;k- literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_dataframe_fill_one_column_for_key_2element_deactivated.pkl b/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_dataframe_fill_one_column_for_key_2element_deactivated.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5861d2a50262a4e8cd8c333559f7c81cf31caafa GIT binary patch literal 253 zcmV_SoA+`m*;9Z7JQI#Ij8F&mL{A3#t_!=a|s4s;P5$g=IylDK@>!u1Jl9Dxx_ zydfb1vbD$Br_^(qQv<|47uGe6ECq$I8rjOtRsv}eoe5){*F4}xKa05{oG3_4jA5ey D*CJ@L literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_default_dataframe_fill.pkl b/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_Test_default_dataframe_fill.pkl new file mode 100644 index 0000000000000000000000000000000000000000..34facf96a9a8221ff72a0fd5a9e1cb918bee2e6f GIT binary patch literal 242 zcmZ>Y%CIzaj8qGbEM?%C!+M7TqpILoUOY9rYHk zSKgiDo3*a%(5lt#7v64inOvLM@WVkxYDUIpHO5Z`A8dtA>v=r>U^hi^W~tYM1=BT; zut~0DSMk|-xg$g+qQRo8DOk{T%d}Ky#rfXX4?X6pINv=(`$~V`?R%s>ySsA* qeuUmQEfF})%q;88T(bq%2V`!~4S(&q%*2QFN4ErP;6&J2p8~)C-gfuw4uP;ZR02W* z(V%iNK@6|~n{91v1gWT_A*ZC%Kn*ki4FRAU05rtV4Lv{xKxkyzjXg%x5{O9yLnA;v zKmY&$01SZ027qV)000D}f@Kd=Y7B!$gFrn%0BC8T27mwn8UPw(dV@xmj0yTIj0k~f zq(q)Pu%J;jR{K0`h0Y6d*=>d7w36>Y9F8j*){2qjG-^5mcZcZYYB`EANl_XfgE;_S zh$Thf4oMNVY{3= zHSIw}1rTUDQYryGB1CAcp23bhNK88|{|66wlK`_GJVbq>c#NM1*n;KWjx;1%Q)loG wkW3HJ>1JPxQFxD6WyD7%u1Ju{OMJTiS#d^?;0!jPaD)CX_aC9r(U;qFB literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_02.pkl b/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_02.pkl new file mode 100644 index 0000000000000000000000000000000000000000..cc217672e3be8082b174ed9d4abdc742a28ddea4 GIT binary patch literal 297 zcmV+^0oMLPT4*^jL0KkKS;;@`kN^N<|K|U_P9RW0B1TAAG(f-i-*Q9d4gdfF06+jB zumHMfXr(_?^$!&FJvAOFqazaV+W_`pScm<#fq3B-)!SSdk5Mm3a9vOf?L<*iHz_CiT+ zJyEuLC4!NoMbM8K#ga4V2+U4fT&!2yX)gfCI#t86F{F60$o2SciNWP(eY2;1ptnnhzyIbNg}#zhTf3 toevcTPW>)_&gSC0U9NoAv_nmSFhmb!vwlii)`D>V7ji{7P>{3Lr(=t}dyoJC literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_04.pkl b/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_04.pkl new file mode 100644 index 0000000000000000000000000000000000000000..fdec0f6e85ddea24e72e63dfb570085c5b1d5cac GIT binary patch literal 299 zcmV+`0o49NT4*^jL0KkKSw;aeg#Z9!|K|U#P9RW0C`L$GG(f-i-*Q9d4gdfF06+jB zumHMfXr(_?^)&KNQ`1S}nlfpo7*o_dkPRM@wo-a*s8rB_rquLJG}9(VCVt5p`aNJ2AVVmfK8km@U~_=91Bt*IYx~GP$E>02&$-t6!w@(N$Y_d z(G_d$^@(%UE&GKu!VwhV(lg@-)MYbcLX{NMS7l@Ep}Ux$<<@Dko60*YB}vKq1HvN- zmvB28e4zPqA8wA68)+UY4@L|Y01pymWO#*yO33U9Vjb{hK?MdAfKiGPXgn3>=l0|g xe#4+5gfvwdGx)9G%UDsCCN^nG9=X9VMfSA2)g{YX!7zV|xgwk>NFxB5LV#u?eHZ`$ literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_05.pkl b/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_05.pkl new file mode 100644 index 0000000000000000000000000000000000000000..a8cf141bdd12ad7ff47fddaa807a3dc22ea8853f GIT binary patch literal 329 zcmV-P0k-}^T4*^jL0KkKS$$3P-~a$-|NsB3PM}ahAjW82G(!LX-*Q9t4iW&wfD8Zu zKtKQhgdng0+Gs&aexpgfMx)RgO*94*X){D^OiVxzDVU~=H9R#4pqdPtdPa=~Mu0Ri z05miJ001%p6Gb!t0Av6F0ia}P0MHEp01+qP^(2n>6aqrVV7Q8yE&zxKN-0lbXS`0H zCoJi(0=ILM;vT%KLdy4*iBHgFn!sx}f@qOI`e7#F3lf2L-g>JY?JC=igGmAShJo@E zu40t&Ir1-dr=o&@u>-es0XYW(qd(O2v(czxbv_|$kR22Zd{{pcBOz>L%^^5)M{sDJ zgh(f(Ao{b6W86KhIR6M7&p%l@m1Jv~m2AXX~fW%~E!fB>W z05mijnuN$SG-w(P4Kio|01TQL3?VFVG=Xzd^l%p-JroS-<47U|2&*Lu34KIL`9^Ra zHTN|aPX*cI*3F`6p9t*G86!Rbh>WCMcO@`MEfr8b))cl*@ohTln}sJ;1emb>JmAD) zzY0Orv!wRsAf5bZHR}?jIS668fLj<2xv=99^Ig&*khH?`D5gk?Cf5lE yAh8G;Ld9{F|D|QBu|)}FQs?<%9R6TP5YpZ3^IscEQj`ku{x0N-aG@Ylu8Qm^R)4hs literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_07.pkl b/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_07.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5e4106f559994a23ae077499d9c0a5d32e69f073 GIT binary patch literal 295 zcmV+?0oeXRT4*^jL0KkKS+murV*mhS|K|U#P9RW0AjU{pG(f-i-*Q9d4gdfF06+jB zumHMfXr%Q|K+{uCNwkb3A(&G`YA^??8^uPR%7sk`8f{NR(@ipDVrUI8hD?Cbgvo)Z zDAOP?ngOFtFn|CJGGx$bF>V+W_`pScm<#fq3B-)!SSdk5Mm3a9vOf?L<*iHz_CiT+ zJyEuLC4!NoMbM8K#ga4V2+U4fT&!2yX)gfCI#t86F{F60$o2SciNWP(eY2;1ptnnhzyIbNg}#zhTf3 toevcTPW>)_&gSC0U9NoAv_nmSFhmb!vwlii)`D>V7ji{7P>{3Lr(=t}dyoJC literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_08.pkl b/sostrades_optimization_plugins/tests/jacobian_pkls/jacobian_obj_vs_design_var_sellar_test_08.pkl new file mode 100644 index 0000000000000000000000000000000000000000..e588b356bc3ff2188652f4b18e7e4c5baafc8b42 GIT binary patch literal 299 zcmV+`0o49NT4*^jL0KkKSuOUgbN~QifByfiP9ShWAVx@8G(f-i-*Q9d4*&rG03ZMX z07wV`umHMfXq5Fdo|#gU!=g!~A4n3qTrKN0O{alpG=#XVetcaVracu5 zpyo#ra&2=2ckv|JP|&e=2)65BRcjqjF~%Y0V*rLi%mx=-qKbeaIiu7L2ysX5Sco`8 xVFRNTSnm}+Qb!eG*5WEe)7rjes)nr%-{P~$^JjRuVa zKmY&$10VnZ13+W|7&3&_FoqgN)G`}TXda**p`aNw15FJEjXfas8fefoOY?F7V1g}& z395h&W9nN5ctu76h_sZjPzX#oB}6pInId9}%qi@FH{y&DRET6;64pvDAH^#(kKGS0SGfUWx)^t6sVDEfu0|De_IqAd=R;Fa97w0 z7=yPWF`}rRsE9%#3-pj6h;1|`6Tm_N6@=*|aw#NSkjW9tE?*HR4oc;R2%89pgQQ9j z=O{>uMg4LlB4b22h|wu1McWJmtV=_(^%agwT99dV+!l(C5bDaD+*U2@8 U4~TJ?1C0XiNT&)C0pp?+z{D}DB>(^b literal 0 HcmV?d00001 diff --git a/sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py b/sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py new file mode 100644 index 0000000..fe359f9 --- /dev/null +++ b/sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py @@ -0,0 +1,71 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2023/01/24-2024/05/16 Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import unittest + +from sostrades_core.execution_engine.execution_engine import ExecutionEngine +from sostrades_optimization_plugins.sos_processes.test.test_sellar_opt_w_func_manager.usecase import ( + Study, +) + +""" +mode: python; py-indent-offset: 4; tab-width: 4; coding: utf-8 +unit test for optimization scenario +""" + + +class TestSoSOptimScenarioWithFuncManager(unittest.TestCase): + """ + SoSOptimScenario test class + """ + + def test_18_optim_scenario_optim_algo_projected_gradient_func_manager(self): + self.name = 'Test12' + self.ee = ExecutionEngine(self.name) + + builder = self.ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_func_manager' + ) + self.ee.factory.set_builders_to_coupling_builder(builder) + self.ee.configure() + + usecase = Study(execution_engine=self.ee) + usecase.study_name = self.name + + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + + full_values_dict.update({ + f"{self.name}.SellarOptimScenario.{'max_iter'}": 67, + f"{self.name}.SellarOptimScenario.{'algo'}": 'ProjectedGradient', + }) + self.ee.load_study_from_input_dict(full_values_dict) + + self.ee.execute() + + proxy_optim = self.ee.root_process.proxy_disciplines[0] + filters = proxy_optim.get_chart_filter_list() + graph_list = proxy_optim.get_post_processing_list(filters) + for graph in graph_list: + #graph.to_plotly().show() + pass + +if '__main__' == __name__: + cls = TestSoSOptimScenarioWithFuncManager() + cls.setUp() + diff --git a/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py b/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py new file mode 100644 index 0000000..899b774 --- /dev/null +++ b/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py @@ -0,0 +1,711 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2023/05/12-2024/05/16 Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import logging +import unittest + +import numpy as np +import pandas as pd +from numpy import arange + +from sostrades_core.execution_engine.execution_engine import ExecutionEngine +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) +from sostrades_optimization_plugins.sos_processes.test.test_sellar_opt_w_func_manager.usecase import ( + Study, +) + + +class TestFuncManager(unittest.TestCase): + """ + FunctionManager test class + """ + + def setUp(self): + ''' + Initialize third data needed for testing + ''' + self.name = 'FuncManagerTest' + self.func_manager = FunctionManager() + + def tearDown(self): + pass + + def test_01_instantiate_func_manager(self): + fail = True + try: + func_manager = FunctionManager() + fail = False + except: + fail = True + self.assertFalse(fail) + + def test_02_objective_functions(self): + obj1 = 1.5 + obj2 = 1. + + self.func_manager.add_function( + 'obj1', None, FunctionManager.OBJECTIVE, weight=0.8) + self.func_manager.update_function_value('obj1', obj1) + + self.func_manager.add_function( + 'obj2', None, FunctionManager.OBJECTIVE, weight=0.2) + self.func_manager.update_function_value('obj2', obj2) + + self.func_manager.build_aggregated_functions(eps=1e-3) + self.assertEqual(self.func_manager.mod_obj, + 100 * (0.8 * obj1 + 0.2 * obj2)) + + def test_03_ineq_functions(self): + OBJECTIVE = self.func_manager.OBJECTIVE + INEQ_CONSTRAINT = self.func_manager.INEQ_CONSTRAINT + EQ_CONSTRAINT = self.func_manager.EQ_CONSTRAINT + + obj1 = 1.5 + obj2 = 1. + cst1 = np.array([10., 200., -30.]) + cst2 = np.array([40000., 1., -10000.]) + cst3 = np.array([-10., 0.2, -5.]) + eqcst1 = np.array([-10., 10., -5.]) + eqcst2 = np.array([0.001, 0.001, 0.00001]) + + self.func_manager.add_function( + 'obj1', None, FunctionManager.OBJECTIVE, weight=0.8) + self.func_manager.update_function_value('obj1', obj1) + + self.func_manager.add_function( + 'obj2', None, FunctionManager.OBJECTIVE, weight=0.2) + self.func_manager.update_function_value('obj2', obj2) + + self.func_manager.add_function( + 'cst1', None, FunctionManager.INEQ_CONSTRAINT) + self.func_manager.update_function_value('cst1', cst1) + + self.func_manager.add_function( + 'cst2', None, FunctionManager.INEQ_CONSTRAINT) + self.func_manager.update_function_value('cst2', cst2) + + self.func_manager.add_function( + 'cst3', None, FunctionManager.INEQ_CONSTRAINT) + self.func_manager.update_function_value('cst3', cst3) + + self.func_manager.add_function( + 'eqcst1', None, FunctionManager.EQ_CONSTRAINT) + self.func_manager.update_function_value('eqcst1', eqcst1) + + self.func_manager.add_function( + 'eqcst2', None, FunctionManager.EQ_CONSTRAINT) + self.func_manager.update_function_value('eqcst2', eqcst2) + + self.func_manager.configure_smooth_log(True, 1e4) + self.func_manager.set_aggregation_mods('sum', 'sum') + self.func_manager.build_aggregated_functions(eps=1e-3) + + self.assertAlmostEqual( + self.func_manager.aggregated_functions[OBJECTIVE], (0.8 * obj1 + 0.2 * obj2), delta=1e-6) + self.assertGreater( + self.func_manager.aggregated_functions[INEQ_CONSTRAINT], 0.) + self.assertGreater( + self.func_manager.aggregated_functions[EQ_CONSTRAINT], 0.) + + res = 100. * (self.func_manager.aggregated_functions[OBJECTIVE] + + self.func_manager.aggregated_functions[INEQ_CONSTRAINT] + + self.func_manager.aggregated_functions[EQ_CONSTRAINT]) + + self.assertEqual(self.func_manager.mod_obj, res) + + def test_04_vest_obj(self): + OBJECTIVE = self.func_manager.OBJECTIVE + obj1 = np.array([1., 2., 3.]) + self.func_manager.add_function( + 'obj1', None, OBJECTIVE, weight=0.8) + self.func_manager.update_function_value('obj1', obj1) + self.func_manager.build_aggregated_functions(eps=1e-3) + self.assertEqual( + self.func_manager.aggregated_functions[OBJECTIVE], obj1.sum() * 0.8) + + def test_05_instantiate_func_manager_disc(self): + try: + FunctionManagerDisc(self.name, logging.getLogger(__name__)) + fail = False + except: + fail = True + self.assertFalse(fail) + + def test_06_configure_func_manager_disc(self): + OBJECTIVE = self.func_manager.OBJECTIVE + INEQ_CONSTRAINT = self.func_manager.INEQ_CONSTRAINT + EQ_CONSTRAINT = self.func_manager.EQ_CONSTRAINT + OBJECTIVE_LAGR = FunctionManagerDisc.OBJECTIVE_LAGR + + # -- init the case + func_mng_name = 'FunctionManager' + prefix = self.name + '.' + func_mng_name + '.' + + ee = ExecutionEngine(self.name) + ns_dict = {'ns_functions': self.name + '.' + func_mng_name, + 'ns_optim': self.name + '.' + func_mng_name, + 'ns_functions_2': self.name + '.' + 'FunctionManager2'} + ee.ns_manager.add_ns_def(ns_dict) + + mod_list = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + fm_builder = ee.factory.get_builder_from_module( + 'FunctionManager', mod_list) + ee.factory.set_builders_to_coupling_builder(fm_builder) + ee.configure() + + # -- i/o setup + base_df = pd.DataFrame({'years': arange(10, 13)}) + obj1 = base_df.copy() + obj1['obj1_values'] = 1.5 + obj2 = base_df.copy() + obj2['obj2_values'] = 1. + cst1 = base_df.copy() + cst1['cst1_values'] = np.array([10., 200., -30.]) + cst2 = base_df.copy() + cst2['cst2_values'] = np.array([40000., 1., -10000.]) + # cst2['cst2_valuesss'] = np.array([400000., 1., -10000.]) + + cst3 = base_df.copy() + cst3['cst3_values'] = np.array([-10., 0.2, -5.]) + eqcst1 = base_df.copy() + eqcst1['eqcst1_values'] = np.array([-10., 10., -5.]) + eqcst2 = base_df.copy() + eqcst2['eqcst2_values'] = np.array([0.001, 0.001, 0.00001]) + + # -- ~GUI inputs: selection of functions + + func_df = pd.DataFrame(columns=['variable', 'ftype', 'weight']) + func_df['variable'] = ['cst1', 'cst2', 'cst3', + 'eqcst1', 'eqcst2', 'obj1', 'obj2'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, + INEQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] + func_df['weight'] = [1., 1., 1., 1, 1, 0.8, 0.2] + func_df['namespace'] = ['ns_functions'] * 6 + ['ns_functions_2'] + values_dict = {} + values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df + + # -- data to simulate disciplinary chain outputs + values_dict[prefix + 'cst1'] = cst1 + values_dict[prefix + 'cst2'] = cst2 + values_dict[prefix + 'cst3'] = cst3 + values_dict[prefix + 'eqcst1'] = eqcst1 + values_dict[prefix + 'eqcst2'] = eqcst2 + values_dict[prefix + 'obj1'] = obj1 + values_dict[self.name + '.FunctionManager2.' + 'obj2'] = obj2 + + ee.load_study_from_input_dict(values_dict) + + ee.display_treeview_nodes(True) + + # -- execution + ee.execute() + # -- retrieve outputs + disc = ee.dm.get_disciplines_with_name( + f'{self.name}.{func_mng_name}')[0] + outputs = disc.get_sosdisc_outputs() + + # -- check outputs with reference data + o1 = obj1['obj1_values'].to_numpy().sum() + o2 = obj2['obj2_values'].to_numpy().sum() + self.assertAlmostEqual(outputs[OBJECTIVE][0], 0.8 * o1 + 0.2 * o2) + self.assertGreater(outputs[INEQ_CONSTRAINT][0], 0.) + self.assertGreater(outputs[EQ_CONSTRAINT][0], 0.) + + res = 100. * (outputs[OBJECTIVE][0] + + outputs[INEQ_CONSTRAINT][0] + + outputs[EQ_CONSTRAINT][0]) + + self.assertEqual(outputs[OBJECTIVE_LAGR][0], res) + del (ee) + del (self) + + def test_07_jacobian_func_manager_disc(self): + OBJECTIVE = self.func_manager.OBJECTIVE + INEQ_CONSTRAINT = self.func_manager.INEQ_CONSTRAINT + EQ_CONSTRAINT = self.func_manager.EQ_CONSTRAINT + OBJECTIVE_LAGR = FunctionManagerDisc.OBJECTIVE_LAGR + + # -- init the case + func_mng_name = 'FunctionManager' + prefix = self.name + '.' + func_mng_name + '.' + + ee = ExecutionEngine(self.name) + ns_dict = {'ns_functions': self.name + '.' + func_mng_name, + 'ns_optim': self.name + '.' + func_mng_name} + ee.ns_manager.add_ns_def(ns_dict) + + mod_list = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + fm_builder = ee.factory.get_builder_from_module( + 'FunctionManager', mod_list) + ee.factory.set_builders_to_coupling_builder(fm_builder) + ee.configure() + + # -- i/o setup + base_df = pd.DataFrame({'years': arange(10, 13)}) + obj1 = base_df.copy() + obj1['obj1_values'] = 1.5 + obj2 = base_df.copy() + obj2['obj2_values'] = 1. + cst1 = base_df.copy() + cst1['cst1_values'] = np.array([10., 200., -30.]) + cst2 = base_df.copy() + cst2['cst2_values'] = np.array([40000., 1., -10000.]) + + cst3 = base_df.copy() + cst3['cst3_values'] = np.array([-10., 0.2, -5.]) + eqcst1 = base_df.copy() + eqcst1['eqcst1_values'] = np.array([-10., 10., -5.]) + eqcst2 = base_df.copy() + eqcst2['eqcst2_values'] = np.array([0.001, 0.001, 0.00001]) + + # -- ~GUI inputs: selection of functions + + func_df = pd.DataFrame(columns=['variable', 'ftype', 'weight']) + func_df['variable'] = ['cst1', 'cst2', 'cst3', + 'eqcst1', 'eqcst2', 'obj1', 'obj2'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, + INEQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] + func_df['weight'] = [1., 1., 1., 1, 1, 0.8, 0.2] + + values_dict = {} + values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df + + # -- data to simulate disciplinary chain outputs + values_dict[prefix + 'cst1'] = cst1 + values_dict[prefix + 'cst2'] = cst2 + values_dict[prefix + 'cst3'] = cst3 + values_dict[prefix + 'eqcst1'] = eqcst1 + values_dict[prefix + 'eqcst2'] = eqcst2 + values_dict[prefix + 'obj1'] = obj1 + values_dict[prefix + 'obj2'] = obj2 + + ee.load_study_from_input_dict(values_dict) + + ee.display_treeview_nodes(True) + + # -- execution + ee.execute() + # -- retrieve outputs + disc = ee.dm.get_disciplines_with_name( + f'{self.name}.{func_mng_name}')[0] + outputs = disc.get_sosdisc_outputs() + + # -- check outputs with reference data + o1 = obj1['obj1_values'].to_numpy().sum() + o2 = obj2['obj2_values'].to_numpy().sum() + self.assertAlmostEqual(outputs[OBJECTIVE][0], 0.8 * o1 + 0.2 * o2) + self.assertGreater(outputs[INEQ_CONSTRAINT][0], 0.) + self.assertGreater(outputs[EQ_CONSTRAINT][0], 0.) + + res = 100. * (outputs[OBJECTIVE][0] + + outputs[INEQ_CONSTRAINT][0] + + outputs[EQ_CONSTRAINT][0]) + + self.assertEqual(outputs[OBJECTIVE_LAGR][0], res) + + disc_techno = ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + assert disc_techno.check_jacobian( + input_data=disc_techno.local_data, + threshold=1e-5, inputs=['FuncManagerTest.FunctionManager.cst1', 'FuncManagerTest.FunctionManager.cst2', + 'FuncManagerTest.FunctionManager.cst3', 'FuncManagerTest.FunctionManager.obj1', + 'FuncManagerTest.FunctionManager.obj2'], + outputs=['FuncManagerTest.FunctionManager.objective_lagrangian'], derr_approx='complex_step') + + def test_08_jacobian_func_manager_disc2(self): + OBJECTIVE = self.func_manager.OBJECTIVE + INEQ_CONSTRAINT = self.func_manager.INEQ_CONSTRAINT + EQ_CONSTRAINT = self.func_manager.EQ_CONSTRAINT + OBJECTIVE_LAGR = FunctionManagerDisc.OBJECTIVE_LAGR + + # -- init the case + func_mng_name = 'FunctionManager' + prefix = self.name + '.' + func_mng_name + '.' + + ee = ExecutionEngine(self.name) + ns_dict = {'ns_functions': self.name + '.' + func_mng_name, + 'ns_optim': self.name + '.' + func_mng_name} + ee.ns_manager.add_ns_def(ns_dict) + + mod_list = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + fm_builder = ee.factory.get_builder_from_module( + 'FunctionManager', mod_list) + ee.factory.set_builders_to_coupling_builder(fm_builder) + ee.configure() + + # -- i/o setup + base_df = pd.DataFrame({'years': arange(10, 11)}) + obj1 = base_df.copy() + obj1['obj1_values'] = 100 + obj2 = base_df.copy() + obj2['obj2_values'] = 20 + cst1 = base_df.copy() + cst1['cst1_values'] = -40 + cst2 = base_df.copy() + cst2['cst2_values'] = 0.000001 + cst3 = base_df.copy() + cst3['cst3_values'] = 4000 + cst4 = base_df.copy() + cst4['cst4_values'] = -0.01 + # -- ~GUI inputs: selection of functions + + func_df = pd.DataFrame(columns=['variable', 'ftype', 'weight']) + func_df['variable'] = ['cst1', 'cst2', 'cst3', 'cst4', 'obj1', 'obj2'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, + INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] + func_df['weight'] = [-0.5, -1., -1., -1., 0.8, 0.2] + + values_dict = {} + values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df + + # -- data to simulate disciplinary chain outputs + values_dict[prefix + 'cst1'] = cst1 + values_dict[prefix + 'cst2'] = cst2 + values_dict[prefix + 'cst3'] = cst3 + values_dict[prefix + 'cst4'] = cst4 + values_dict[prefix + 'obj1'] = obj1 + values_dict[prefix + 'obj2'] = obj2 + + ee.load_study_from_input_dict(values_dict) + + ee.display_treeview_nodes(True) + + # -- execution + ee.execute() + # -- retrieve outputs + disc = ee.dm.get_disciplines_with_name( + f'{self.name}.{func_mng_name}')[0] + outputs = disc.get_sosdisc_outputs() + + # -- check outputs with reference data + o1 = obj1['obj1_values'].to_numpy().sum() + o2 = obj2['obj2_values'].to_numpy().sum() + self.assertAlmostEqual(outputs[OBJECTIVE][0], 0.8 * o1 + 0.2 * o2) + self.assertGreater(outputs[INEQ_CONSTRAINT][0], 0.) + + res = 100. * (outputs[OBJECTIVE][0] + + outputs[INEQ_CONSTRAINT][0] + + outputs[EQ_CONSTRAINT][0]) + + self.assertEqual(outputs[OBJECTIVE_LAGR][0], res) + + disc_techno = ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + assert disc_techno.check_jacobian( + input_data=disc_techno.local_data, + threshold=1e-5, inputs=['FuncManagerTest.FunctionManager.cst1', 'FuncManagerTest.FunctionManager.cst2', + 'FuncManagerTest.FunctionManager.cst3', 'FuncManagerTest.FunctionManager.cst4', + 'FuncManagerTest.FunctionManager.obj1', 'FuncManagerTest.FunctionManager.obj2'], + outputs=['FuncManagerTest.FunctionManager.objective_lagrangian'], derr_approx='complex_step') + + def test_09_inf_nan_values(self): + OBJECTIVE = self.func_manager.OBJECTIVE + INEQ_CONSTRAINT = self.func_manager.INEQ_CONSTRAINT + EQ_CONSTRAINT = self.func_manager.EQ_CONSTRAINT + + # -- init the case + func_mng_name = 'FunctionManager' + prefix = self.name + '.' + func_mng_name + '.' + + ee = ExecutionEngine(self.name) + ns_dict = {'ns_functions': self.name + '.' + func_mng_name, + 'ns_optim': self.name + '.' + func_mng_name} + ee.ns_manager.add_ns_def(ns_dict) + + mod_list = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + fm_builder = ee.factory.get_builder_from_module( + 'FunctionManager', mod_list) + ee.factory.set_builders_to_coupling_builder(fm_builder) + ee.configure() + + # -- i/o setup + base_df = pd.DataFrame({'years': arange(10, 13)}) + obj1 = base_df.copy() + obj1['obj1_values'] = 1.5 + obj2 = base_df.copy() + obj2['obj2_values'] = 1. + cst1 = base_df.copy() + cst1['cst1_values'] = np.array([np.nan, 200., -30.]) + cst2 = base_df.copy() + cst2['cst2_values'] = np.array([40000., 1., -10000.]) + cst3 = base_df.copy() + cst3['cst3_values'] = np.array([-10., 0.2, -5.]) + eqcst1 = base_df.copy() + eqcst1['eqcst1_values'] = np.array([-10., 10., -5.]) + eqcst2 = base_df.copy() + eqcst2['eqcst2_values'] = np.array([0.001, np.inf, 0.00001]) + + # -- ~GUI inputs: selection of functions + + func_df = pd.DataFrame(columns=['variable', 'ftype', 'weight']) + func_df['variable'] = ['cst1', 'cst2', 'cst3', + 'eqcst1', 'eqcst2', 'obj1', 'obj2'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, + INEQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] + func_df['weight'] = [1, 1, 1, 1, 1, 0.8, 0.2] + values_dict = {} + values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df + + # -- data to simulate disciplinary chain outputs + values_dict[prefix + 'cst1'] = cst1 + values_dict[prefix + 'cst2'] = cst2 + values_dict[prefix + 'cst3'] = cst3 + values_dict[prefix + 'eqcst1'] = eqcst1 + values_dict[prefix + 'eqcst2'] = eqcst2 + values_dict[prefix + 'obj1'] = obj1 + values_dict[prefix + 'obj2'] = obj2 + + ee.load_study_from_input_dict(values_dict) + + ee.display_treeview_nodes(True) + + def test_10_jacobian_func_manager_disc_different_aggr(self): + OBJECTIVE = self.func_manager.OBJECTIVE + INEQ_CONSTRAINT = self.func_manager.INEQ_CONSTRAINT + EQ_CONSTRAINT = self.func_manager.EQ_CONSTRAINT + + # -- init the case + func_mng_name = 'FunctionManager' + prefix = self.name + '.' + func_mng_name + '.' + + ee = ExecutionEngine(self.name) + ns_dict = {'ns_functions': self.name + '.' + func_mng_name, + 'ns_optim': self.name + '.' + func_mng_name} + ee.ns_manager.add_ns_def(ns_dict) + + mod_list = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + fm_builder = ee.factory.get_builder_from_module( + 'FunctionManager', mod_list) + ee.factory.set_builders_to_coupling_builder(fm_builder) + ee.configure() + + # -- i/o setup + base_df = pd.DataFrame({'years': arange(10, 13)}) + obj1 = base_df.copy() + obj1['obj1_values'] = 1.5 + obj2 = base_df.copy() + obj2['obj2_values'] = 1. + cst1 = base_df.copy() + cst1['cst1_values'] = np.array([10., 200., -30.]) + cst2 = base_df.copy() + cst2['cst2_values'] = np.array([40000., 1., -10000.]) + + cst3 = base_df.copy() + cst3['cst3_values'] = np.array([-10., 0.2, -5.]) + eqcst1 = base_df.copy() + eqcst1['eqcst1_values'] = np.array([-10., 10., -5.]) + eqcst2 = base_df.copy() + eqcst2['eqcst2_values'] = np.array([0.001, 0.001, 0.00001]) + + # -- ~GUI inputs: selection of functions + + func_df = pd.DataFrame(columns=['variable', 'ftype', 'weight', 'aggr']) + func_df['variable'] = ['cst1', 'cst2', 'cst3', + 'eqcst1', 'eqcst2', 'obj1', 'obj2'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, + INEQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] + func_df['weight'] = [1., 1., 1., 1, 1, 0.8, 0.2] + func_df['aggr'] = ['sum', 'sum', 'sum', 'sum', 'sum', 'smax', 'sum'] + func_df['parent'] = ['ineqcst', 'ineqcst', 'ineqcst', 'eqcst', 'eqcst', 'obj', 'obj'] + values_dict = {} + values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df + + # -- data to simulate disciplinary chain outputs + values_dict[prefix + 'cst1'] = cst1 + values_dict[prefix + 'cst2'] = cst2 + values_dict[prefix + 'cst3'] = cst3 + values_dict[prefix + 'eqcst1'] = eqcst1 + values_dict[prefix + 'eqcst2'] = eqcst2 + values_dict[prefix + 'obj1'] = obj1 + values_dict[prefix + 'obj2'] = obj2 + + ee.load_study_from_input_dict(values_dict) + + ee.display_treeview_nodes(True) + + # -- execution + ee.execute() + # -- retrieve outputs + disc = ee.dm.get_disciplines_with_name( + f'{self.name}.{func_mng_name}')[0] + outputs = disc.get_sosdisc_outputs() + + # -- check outputs with reference data + o1 = obj1['obj1_values'].to_numpy().sum() + o2 = obj2['obj2_values'].to_numpy().sum() + + res = 100. * (outputs[OBJECTIVE][0] + + outputs[INEQ_CONSTRAINT][0] + + outputs[EQ_CONSTRAINT][0]) + + disc_techno = ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + assert disc_techno.check_jacobian( + input_data=disc_techno.local_data, + threshold=1e-5, inputs=['FuncManagerTest.FunctionManager.cst1', 'FuncManagerTest.FunctionManager.cst2', + 'FuncManagerTest.FunctionManager.cst3', 'FuncManagerTest.FunctionManager.obj1', + 'FuncManagerTest.FunctionManager.obj2'], + outputs=['FuncManagerTest.FunctionManager.objective_lagrangian'], derr_approx='complex_step') + + # get charts + filter = disc.get_chart_filter_list() + graph_list = disc.get_post_processing_list(filter) + + def test_11_jacobian_eq_delta_and_lin_to_quad(self): + OBJECTIVE = self.func_manager.OBJECTIVE + INEQ_CONSTRAINT = self.func_manager.INEQ_CONSTRAINT + EQ_CONSTRAINT = self.func_manager.EQ_CONSTRAINT + + # -- init the case + func_mng_name = 'FunctionManager' + prefix = self.name + '.' + func_mng_name + '.' + + ee = ExecutionEngine(self.name) + ns_dict = {'ns_functions': self.name + '.' + func_mng_name, + 'ns_optim': self.name + '.' + func_mng_name} + ee.ns_manager.add_ns_def(ns_dict) + + mod_list = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' + fm_builder = ee.factory.get_builder_from_module( + 'FunctionManager', mod_list) + ee.factory.set_builders_to_coupling_builder(fm_builder) + ee.configure() + + # -- i/o setup + base_df = pd.DataFrame({'years': arange(10, 13)}) + obj1 = base_df.copy() + obj1['obj1_values'] = 1.5 + obj2 = base_df.copy() + obj2['obj2_values'] = 1. + ineq_cst = base_df.copy() + ineq_cst['ineq_cst_values'] = np.array([10., -2000., -30.]) + ineq_cst_array = np.array([10., 2000., -30.]) + eqcst_delta = base_df.copy() + eqcst_delta['eqcst_delta_values'] = np.array([400., 1., -10.]) + eqcst_delta2 = base_df.copy() + eqcst_delta2['eqcst_delta2_values'] = np.array([0.0001, 1., -0.00003]) + eqcst_delta_array = np.array([-10., -200000., -5.]) + eqcst_lintoquad = base_df.copy() + eqcst_lintoquad['eqcst_lintoquad_values'] = np.array([-1., 2., 0.03]) + eqcst_lintoquad_array = np.array([-0.2, -50., 100.]) + + # -- ~GUI inputs: selection of functions + + func_df = pd.DataFrame(columns=['variable', 'ftype', 'weight', 'aggr']) + func_df['variable'] = ['ineq_cst', 'ineq_cst_array', 'eqcst_delta', 'eqcst_delta2', + 'eqcst_delta_array', 'eqcst_lintoquad', 'eqcst_lintoquad_array', + 'obj1', 'obj2'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, + EQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, + OBJECTIVE, OBJECTIVE] + func_df['weight'] = [0.5, -1., -0.2, 0.2, 1.2, -1.0, 0.01, 0.8, 0.2] + func_df['aggr'] = ['sum', 'sum', 'sum', 'sum', 'delta', 'lin_to_quad', 'lin_to_quad', 'smax', 'sum'] + values_dict = {} + values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df + + # -- data to simulate disciplinary chain outputs + values_dict[prefix + 'ineq_cst'] = ineq_cst + values_dict[prefix + 'ineq_cst_array'] = ineq_cst_array + values_dict[prefix + 'eqcst_delta'] = eqcst_delta + values_dict[prefix + 'eqcst_delta2'] = eqcst_delta2 + values_dict[prefix + 'eqcst_delta_array'] = eqcst_delta_array + values_dict[prefix + 'eqcst_lintoquad'] = eqcst_lintoquad + values_dict[prefix + 'eqcst_lintoquad_array'] = eqcst_lintoquad_array + values_dict[prefix + 'obj1'] = obj1 + values_dict[prefix + 'obj2'] = obj2 + values_dict[prefix + 'aggr_mod_eq'] = 'sum' + values_dict[prefix + 'aggr_mod_ineq'] = 'smooth_max' + + ee.load_study_from_input_dict(values_dict) + + ee.dm.set_data(prefix + 'ineq_cst_array', 'type', 'array') + ee.dm.set_data(prefix + 'eqcst_delta_array', 'type', 'array') + ee.dm.set_data(prefix + 'eqcst_lintoquad_array', 'type', 'array') + + ee.load_study_from_input_dict(values_dict) + + ee.display_treeview_nodes(True) + + # -- execution + ee.execute() + # -- retrieve outputs + disc = ee.dm.get_disciplines_with_name( + f'{self.name}.{func_mng_name}')[0] + outputs = disc.get_sosdisc_outputs() + + # -- check outputs with reference data + o1 = obj1['obj1_values'].to_numpy().sum() + o2 = obj2['obj2_values'].to_numpy().sum() + + res = 100. * (outputs[OBJECTIVE][0] + + outputs[INEQ_CONSTRAINT][0] + + outputs[EQ_CONSTRAINT][0]) + + disc_techno = ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + assert disc_techno.check_jacobian( + input_data=disc_techno.local_data, + threshold=1e-8, inputs=['FuncManagerTest.FunctionManager.ineq_cst', + 'FuncManagerTest.FunctionManager.ineq_cst_array', + 'FuncManagerTest.FunctionManager.eqcst_delta', + 'FuncManagerTest.FunctionManager.eqcst_delta2', + 'FuncManagerTest.FunctionManager.eqcst_delta_array', + 'FuncManagerTest.FunctionManager.eqcst_lintoquad', + 'FuncManagerTest.FunctionManager.eqcst_lintoquad_array', + 'FuncManagerTest.FunctionManager.obj1', + 'FuncManagerTest.FunctionManager.obj2'], + outputs=['FuncManagerTest.FunctionManager.objective_lagrangian', + 'FuncManagerTest.FunctionManager.eq_constraint', + 'FuncManagerTest.FunctionManager.ineq_constraint', + ], + step=1e-15, derr_approx='complex_step') + + def test_12_test_number_iteration_output_optim_df(self): + self.name = 'Test12' + self.ee = ExecutionEngine(self.name) + + builder = self.ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_func_manager' + ) + self.ee.factory.set_builders_to_coupling_builder(builder) + self.ee.configure() + + usecase = Study(execution_engine=self.ee) + usecase.study_name = self.name + + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + + self.ee.load_study_from_input_dict(full_values_dict) + + self.ee.execute() + + proxy_optim = self.ee.root_process.proxy_disciplines[0] + formulation = proxy_optim.mdo_discipline_wrapp.mdo_discipline.formulation + optim_iter = formulation.opt_problem.current_iter + optim_name = "SellarOptimScenario" + optim_output_df = self.ee.dm.get_value( + f'{self.name}.{optim_name}.SellarCoupling.FunctionManager.{FunctionManagerDisc.OPTIM_OUTPUT_DF}') + self.assertEqual(optim_iter + 1, len(optim_output_df)) + # get charts + func_disc = self.ee.dm.get_disciplines_with_name(f'{self.name}.{optim_name}.SellarCoupling.FunctionManager')[0] + filter = func_disc.get_chart_filter_list() + graph_list = func_disc.get_post_processing_list(filter) diff --git a/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py b/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py new file mode 100644 index 0000000..6f91fac --- /dev/null +++ b/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py @@ -0,0 +1,174 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/04/13-2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from os.path import dirname + +import numpy as np +import pandas as pd + +from sostrades_core.execution_engine.execution_engine import ExecutionEngine +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) +from sostrades_core.tests.core.abstract_jacobian_unit_test import ( + AbstractJacobianUnittest, +) + +AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE +AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM +AGGR_TYPE_SMAX = FunctionManager.AGGR_TYPE_SMAX +INEQ_CONSTRAINT = FunctionManager.INEQ_CONSTRAINT +OBJECTIVE = FunctionManager.OBJECTIVE + + +class TestDesignVar(AbstractJacobianUnittest): + """ + DesignVar test class + """ + + def analytic_grad_entry(self): + return [self.test_derivative + ] + + def setUp(self): + + self.study_name = 'Test' + self.ns = f'{self.study_name}' + self.sc_name = "SellarOptimScenario" + self.c_name = "SellarCoupling" + + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [[1., 1., 3., 2.], [5., 2., 2., 1., 1., 1.]], + 'lower_bnd': [[0., 0., 0., 0.], [-10., 0., -10., -10., -10., -10.]], + 'upper_bnd': [[10., 10., 10., 10.], [10., 10., 10., 10., 10., 10.]], + 'enable_variable': [True, True], + 'activated_elem': [[True], [True, True]]} + self.dspace = pd.DataFrame(dspace_dict) + + self.design_var_descriptor = {'x_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'value', + 'index': np.arange(0, 4, 1), + 'index_name': 'test', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + }, + 'z_in': {'out_name': 'z', + 'type': 'array', + 'out_type': 'array', + 'index': np.arange(0, 10, 1), + 'index_name': 'index', + 'namespace_in': 'ns_OptimSellar', + 'namespace_out': 'ns_OptimSellar' + } + } + self.repo = 'sostrades_optimization_plugins.sos_processes.test' + self.proc_name = 'test_sellar_opt_w_design_var' + + + self.ee = ExecutionEngine(self.study_name) + factory = self.ee.factory + + opt_builder = factory.get_builder_from_process(repo=self.repo, + mod_id=self.proc_name) + + self.ee.factory.set_builders_to_coupling_builder(opt_builder) + self.ee.configure() + + # -- set up disciplines in Scenario + values_dict = {} + + # design var + values_dict[f'{self.ns}.{self.sc_name}.{self.c_name}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + + # Optim inputs + values_dict[f'{self.ns}.{self.sc_name}.max_iter'] = 1 + values_dict[f'{self.ns}.{self.sc_name}.algo'] = "SLSQP" + values_dict[f'{self.ns}.{self.sc_name}.design_space'] = self.dspace + values_dict[f'{self.ns}.{self.sc_name}.formulation'] = 'DisciplinaryOpt' + values_dict[f'{self.ns}.{self.sc_name}.objective_name'] = 'obj' + values_dict[f'{self.ns}.{self.sc_name}.ineq_constraints'] = ['c_1', 'c_2'] + values_dict[f'{self.ns}.{self.sc_name}.algo_options'] = {"ftol_rel": 1e-10, + "ineq_tolerance": 2e-3, + "normalize_design_space": False} + + # Sellar inputs + local_dv = 10. + values_dict[f'{self.ns}.{self.sc_name}.x_in'] = np.array([1., 1., 3., 2.]) + values_dict[f'{self.ns}.{self.sc_name}.y_1'] = 5. + values_dict[f'{self.ns}.{self.sc_name}.y_2'] = 1. + values_dict[f'{self.ns}.{self.sc_name}.z_in'] = np.array([5., 2., 2., 1., 1., 1.]) + values_dict[f'{self.ns}.{self.sc_name}.{self.c_name}.Sellar_Problem.local_dv'] = local_dv + + # function manager + func_df = pd.DataFrame( + columns=['variable', 'ftype', 'weight', AGGR_TYPE]) + func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] + func_df['weight'] = [200, 0.000001, 0.1] + func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] + func_mng_name = 'FunctionManager' + + prefix = f'{self.study_name}.{self.sc_name}.{self.c_name}.{func_mng_name}.' + values_dict[prefix + + FunctionManagerDisc.FUNC_DF] = func_df + + # load and run + self.ee.load_study_from_input_dict(values_dict) + self.values_dict=values_dict + + def test_01_check_execute(self): + print("\n Test 1 : check configure and treeview") + self.ee.configure() + self.ee.execute() + + disc = self.ee.dm.get_disciplines_with_name(f'{self.ns}.{self.sc_name}.{self.c_name}.DesignVar')[0] + + # checks output type is well created for dataframes (most commonly used) + df = disc.get_sosdisc_outputs('x') + assert isinstance(df, pd.DataFrame) + assert all(df.columns == [self.design_var_descriptor['x_in']['index_name'], self.design_var_descriptor['x_in']['key']]) + + filters = disc.get_chart_filter_list() + graph_list = disc.get_post_processing_list(filters) + # for graph in graph_list: + # graph.to_plotly().show() + + def test_derivative(self): + self.ee.prepare_execution() + disc = self.ee.dm.get_disciplines_with_name(f'{self.ns}.{self.sc_name}.{self.c_name}.DesignVar')[0] + + input_names = [f'{self.ns}.{self.sc_name}.x_in', + f'{self.ns}.{self.sc_name}.z_in', + ] + + output_names = [f'{self.ns}.{self.sc_name}.x', + f'{self.ns}.{self.sc_name}.z', + ] + + + self.check_jacobian(local_data=self.values_dict, location=dirname(__file__), filename='jacobian_design_var_bspline.pkl', + discipline=disc.mdo_discipline_wrapp.mdo_discipline, step=1e-15, inputs=input_names, + outputs=output_names, derr_approx='complex_step') + + +if '__main__' == __name__: + cls = TestDesignVar() + cls.setUp() + cls.test_01_check_execute() + cls.test_derivative() diff --git a/sostrades_optimization_plugins/tests/l0_test_63_design_var.py b/sostrades_optimization_plugins/tests/l0_test_63_design_var.py new file mode 100644 index 0000000..e292a4e --- /dev/null +++ b/sostrades_optimization_plugins/tests/l0_test_63_design_var.py @@ -0,0 +1,214 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import numpy as np +import pandas as pd + +from sostrades_optimization_plugins.models.design_var.design_var_disc import ( + DesignVarDiscipline, +) +from sostrades_core.execution_engine.execution_engine import ExecutionEngine +from sostrades_core.tests.core.abstract_jacobian_unit_test import ( + AbstractJacobianUnittest, +) + + +class TestDesignVar(AbstractJacobianUnittest): + """ + DesignVar unitary test class + """ + def analytic_grad_entry(self): + return [self.test_derivative + ] + + def setUp(self): + self.study_name = 'Test' + self.ns = f'{self.study_name}' + + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [[1., 1., 3., 2.], [5., 2., 2., 1., 1., 1.]], + 'lower_bnd': [[0., 0., 0., 0.], [-10., 0., -10., -10., -10., -10.]], + 'upper_bnd': [[10., 10., 10., 10.], [10., 10., 10., 10., 10., 10.]], + 'enable_variable': [True, True], + 'activated_elem': [[True], [True, True]]} + self.dspace = pd.DataFrame(dspace_dict) + + self.design_var_descriptor = {'x_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'value', + 'index': np.arange(0, 4, 1), + 'index_name': 'test', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public' + }, + 'z_in': {'out_name': 'z', + 'type': 'array', + 'out_type': 'array', + 'index': np.arange(0, 6, 1), + 'index_name': 'index', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public' + } + } + + self.ee = ExecutionEngine(self.study_name) + factory = self.ee.factory + design_var_path = 'sostrades_optimization_plugins.models.design_var.design_var_disc.DesignVarDiscipline' + design_var_builder = factory.get_builder_from_module('DesignVar', design_var_path) + self.ee.ns_manager.add_ns_def({'ns_public': self.ns, + 'ns_optim': self.ns}) + self.ee.factory.set_builders_to_coupling_builder(design_var_builder) + self.ee.configure() + + # -- set up disciplines in Scenario + values_dict = {} + + # design var + values_dict[ + f'{self.ns}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + values_dict[ + f'{self.ns}.design_space'] = self.dspace + values_dict[f'{self.ns}.x_in'] = np.array([1., 1., 3., 2.]) + values_dict[f'{self.ns}.z_in'] = np.array([5., 2., 2., 1., 1., 1.]) + self.values_dict = values_dict + + def test_01_check_execute_default_dataframe_fill(self): + ''' + + Test the class with the default method one column per key + + ''' + # load and run + self.ee.load_study_from_input_dict(self.values_dict) + self.ee.configure() + self.ee.execute() + + disc = self.ee.dm.get_disciplines_with_name(f'{self.ns}.DesignVar')[0] + + # checks output type is well created for dataframes (most commonly used) + df = disc.get_sosdisc_outputs('x') + assert isinstance(df, pd.DataFrame) + assert all( + df.columns == [self.design_var_descriptor['x_in']['index_name'], self.design_var_descriptor['x_in']['key']]) + assert (df['value'].values == self.values_dict[f'{self.ns}.x_in']).all() + + def test_02_check_execute_dataframe_fill_one_column_for_key(self): + ''' + + Test the class with the method 'one column for key, one for value' + + ''' + self.design_var_descriptor = {'x_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'value', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', + DesignVarDiscipline.DATAFRAME_FILL: + DesignVarDiscipline.DATAFRAME_FILL_POSSIBLE_VALUES[1], + DesignVarDiscipline.COLUMNS_NAMES: ['name', 'sharevalue'] + }, + 'z_in': {'out_name': 'z', + 'type': 'array', + 'out_type': 'array', + 'index': np.arange(0, 10, 1), + 'index_name': 'index', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public' + } + } + self.values_dict[ + f'{self.ns}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + + self.ee.load_study_from_input_dict(self.values_dict) + self.ee.configure() + self.ee.execute() + + disc = self.ee.dm.get_disciplines_with_name(f'{self.ns}.DesignVar')[0] + filter = disc.get_chart_filter_list() + graph_list = disc.get_post_processing_list(filter) + # for graph in graph_list: + # graph.to_plotly().show() + # checks output type is well created for dataframes (most commonly used) + df = disc.get_sosdisc_outputs('x') + assert isinstance(df, pd.DataFrame) + index_name = self.design_var_descriptor['x_in'][DesignVarDiscipline.INDEX_NAME] + index = self.design_var_descriptor['x_in'][DesignVarDiscipline.INDEX] + column_names = [index_name] + column_names.extend(self.design_var_descriptor['x_in'][DesignVarDiscipline.COLUMNS_NAMES]) + assert all( + df.columns == column_names) + assert (df[index_name].values == index).all() + assert (df[self.design_var_descriptor['x_in'][DesignVarDiscipline.COLUMNS_NAMES][0]].values == + self.design_var_descriptor['x_in']['key']).all() + assert (df[self.design_var_descriptor['x_in'][DesignVarDiscipline.COLUMNS_NAMES][1]].values == self.values_dict[ + f'{self.ns}.x_in']).all() + + def test_03_check_deactivated_element_options(self): + ''' + + Test the class with deactivated element option + + ''' + dspace_z_value = [5., 2., 2., 1., 1., 1.] + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [[1., 1., 3., 4.], dspace_z_value], + 'lower_bnd': [[0., 0., 0., 0.], [-10., 0., -10., -10., -10., -10.]], + 'upper_bnd': [[10., 10., 10., 10.], [10., 10., 10., 10., 10., 10.]], + 'enable_variable': [True, True], + 'activated_elem': [[True, True, True, False], [True, True, False, False, False, False]]} + # the last element is deactivated for x_in with method initial_value + self.design_var_descriptor['x_in'][DesignVarDiscipline.FILL_ACTIVATED_ELEMENTS] = 'initial_value' + # the last four elements are deactivated for z_in using the method 'last element activated' + self.design_var_descriptor['z_in'][DesignVarDiscipline.FILL_ACTIVATED_ELEMENTS] = 'last element activated' + + self.dspace = pd.DataFrame(dspace_dict) + self.values_dict[ + f'{self.ns}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + self.values_dict[ + f'{self.ns}.design_space'] = self.dspace + self.values_dict[f'{self.ns}.x_in'] = np.array([2., 1., 3.]) + self.values_dict[f'{self.ns}.z_in'] = np.array([5., 2.]) + # load and run + + self.ee.load_study_from_input_dict(self.values_dict) + self.ee.configure() + self.ee.execute() + + disc = self.ee.dm.get_disciplines_with_name(f'{self.ns}.DesignVar')[0] + + # checks output type is well created for dataframes (most commonly used) + df = disc.get_sosdisc_outputs('x') + assert isinstance(df, pd.DataFrame) + assert all( + df.columns == [self.design_var_descriptor['x_in']['index_name'], self.design_var_descriptor['x_in']['key']]) + df_value_th = np.array([2., 1., 3., 4.]) + assert (df['value'].values == df_value_th).all() + + z = disc.get_sosdisc_outputs('z') + + assert (len(z) == len(dspace_z_value)) + z_in_value = list(self.values_dict[f'{self.ns}.z_in']) + z_value_th = z_in_value + [z_in_value[-1]] * (len(dspace_z_value) - len(z_in_value)) + assert (list(z) == z_value_th) + + +if '__main__' == __name__: + cls = TestDesignVar() + cls.setUp() + cls.test_01_check_execute() diff --git a/sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py b/sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py new file mode 100644 index 0000000..36042fc --- /dev/null +++ b/sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py @@ -0,0 +1,276 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from os.path import dirname + +import numpy as np +import pandas as pd + +from sostrades_optimization_plugins.models.design_var.design_var_disc import ( + DesignVarDiscipline +) + +from sostrades_core.execution_engine.execution_engine import ExecutionEngine +from sostrades_core.tests.core.abstract_jacobian_unit_test import ( + AbstractJacobianUnittest, +) + + +class GradiantAssetDiscTestCase(AbstractJacobianUnittest): + """ + AssetDisc gradients test class + """ + np.random.seed(42) + + def analytic_grad_entry(self): + return [ + self.test_01_analytic_gradient_default_dataframe_fill(), + self.test_02_analytic_gradient_dataframe_fill_one_column_for_key(), + ] + + def setUp(self): + """Initialize""" + self.study_name = 'Test' + self.ns = f'{self.study_name}' + + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [[1., 1., 3., 2.], [5., 2., 2., 1.]], + 'lower_bnd': [[0., 0., 0., 0.], [-10., 0., -10., -10.]], + 'upper_bnd': [[10., 10., 10., 10.], [10., 10., 10., 10.]], + 'enable_variable': [True, True], + 'activated_elem': [[True, True, True, True], [True, True, True, True]]} + self.dspace = pd.DataFrame(dspace_dict) + + self.design_var_descriptor = {'x_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'x_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', }, + 'z_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'z_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', } + } + + self.ee = ExecutionEngine(self.study_name) + factory = self.ee.factory + design_var_path = 'sostrades_optimization_plugins.models.design_var.design_var_disc.DesignVarDiscipline' + design_var_builder = factory.get_builder_from_module('DesignVar', design_var_path) + self.ee.ns_manager.add_ns_def({'ns_public': self.ns, + 'ns_optim': self.ns}) + self.ee.factory.set_builders_to_coupling_builder(design_var_builder) + self.ee.configure() + + # -- set up disciplines in Scenario + values_dict = {} + + # design var + values_dict[ + f'{self.ns}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + values_dict[ + f'{self.ns}.design_space'] = self.dspace + values_dict[f'{self.ns}.x_in'] = np.array([1., 1., 3., 2.]) + values_dict[f'{self.ns}.z_in'] = np.array([5., 2., 2., 1.]) + self.values_dict = values_dict + + def tearDown(self): + pass + + def test_01_analytic_gradient_default_dataframe_fill(self): + """Test gradient with default design var dataframe description, namely 'one column per key' """ + self.ee.load_study_from_input_dict(self.values_dict) + self.ee.configure() + self.ee.execute() + + disc = self.ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + self.check_jacobian(location=dirname(__file__), + filename=f'jacobian_{self.study_name}_default_dataframe_fill.pkl', + discipline=disc, + step=1e-16, + derr_approx='complex_step', + threshold=1e-5, + local_data=disc.local_data, + inputs=[f'{self.ns}.x_in', f'{self.ns}.z_in'], + outputs=[f'{self.ns}.x'] + ) + + def test_02_analytic_gradient_dataframe_fill_one_column_for_key(self): + """Test gradient with design var dataframe description 'one column for key, one column for value' """ + self.design_var_descriptor = {'x_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'x_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', + DesignVarDiscipline.DATAFRAME_FILL: + DesignVarDiscipline.DATAFRAME_FILL_POSSIBLE_VALUES[1], + DesignVarDiscipline.COLUMNS_NAMES: ['name', 'sharevalue']}, + 'z_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'z_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', + DesignVarDiscipline.DATAFRAME_FILL: + DesignVarDiscipline.DATAFRAME_FILL_POSSIBLE_VALUES[1], + DesignVarDiscipline.COLUMNS_NAMES: ['name', 'sharevalue']} + } + + self.values_dict[ + f'{self.ns}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + self.ee.load_study_from_input_dict(self.values_dict) + self.ee.configure() + self.ee.execute() + + disc = self.ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + self.check_jacobian(location=dirname(__file__), + filename=f'jacobian_{self.study_name}_dataframe_fill_one_column_for_key.pkl', + discipline=disc, + step=1e-16, + derr_approx='complex_step', + threshold=1e-5, + local_data=disc.local_data, + inputs=[f'{self.ns}.x_in', f'{self.ns}.z_in'], + outputs=[f'{self.ns}.x'] + ) + + def test_03_analytic_gradient_dataframe_fill_one_column_for_key_with1_deactivated_element(self): + """Test gradient with design var dataframe description 'one column for key, one column for value' """ + self.design_var_descriptor = {'x_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'x_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', + DesignVarDiscipline.DATAFRAME_FILL: + DesignVarDiscipline.DATAFRAME_FILL_POSSIBLE_VALUES[1], + DesignVarDiscipline.COLUMNS_NAMES: ['name', 'sharevalue']}, + 'z_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'z_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', + DesignVarDiscipline.DATAFRAME_FILL: + DesignVarDiscipline.DATAFRAME_FILL_POSSIBLE_VALUES[1], + DesignVarDiscipline.COLUMNS_NAMES: ['name', 'sharevalue']} + } + + self.values_dict[ + f'{self.ns}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [[1., 1., 3., 2.], [5., 2., 2., 1.]], + 'lower_bnd': [[0., 0., 0., 0.], [-10., 0., -10., -10.]], + 'upper_bnd': [[10., 10., 10., 10.], [10., 10., 10., 10.]], + 'enable_variable': [True, True], + 'activated_elem': [[False, True, True, True], [False, True, True, True]]} + self.dspace = pd.DataFrame(dspace_dict) + self.values_dict[ + f'{self.ns}.design_space'] = self.dspace + + self.values_dict[f'{self.ns}.x_in'] = np.array([1., 3., 2.]) + self.values_dict[f'{self.ns}.z_in'] = np.array([2., 2., 1.]) + self.ee.load_study_from_input_dict(self.values_dict) + self.ee.configure() + self.ee.execute() + + disc = self.ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + self.check_jacobian(location=dirname(__file__), + filename=f'jacobian_{self.study_name}_dataframe_fill_one_column_for_key_1element_deactivated.pkl', + discipline=disc, + step=1e-16, + derr_approx='complex_step', + threshold=1e-5, + local_data=disc.local_data, + inputs=[f'{self.ns}.x_in', f'{self.ns}.z_in'], + outputs=[f'{self.ns}.x'] + ) + + def test_04_analytic_gradient_dataframe_fill_one_column_for_key_with2_deactivated_element(self): + """Test gradient with design var dataframe description 'one column for key, one column for value' """ + self.design_var_descriptor = {'x_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'x_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', + DesignVarDiscipline.DATAFRAME_FILL: + DesignVarDiscipline.DATAFRAME_FILL_POSSIBLE_VALUES[1], + DesignVarDiscipline.COLUMNS_NAMES: ['name', 'sharevalue']}, + 'z_in': {'out_name': 'x', + 'type': 'array', + 'out_type': 'dataframe', + 'key': 'z_in', + 'index': np.arange(0, 4, 1), + 'index_name': 'years', + 'namespace_in': 'ns_public', + 'namespace_out': 'ns_public', + DesignVarDiscipline.DATAFRAME_FILL: + DesignVarDiscipline.DATAFRAME_FILL_POSSIBLE_VALUES[1], + DesignVarDiscipline.COLUMNS_NAMES: ['name', 'sharevalue']} + } + + self.values_dict[ + f'{self.ns}.DesignVar.design_var_descriptor'] = self.design_var_descriptor + + dspace_dict = {'variable': ['x_in', 'z_in'], + 'value': [[1., 1., 3., 2.], [5., 2., 2., 1.]], + 'lower_bnd': [[0., 0., 0., 0.], [-10., 0., -10., -10.]], + 'upper_bnd': [[10., 10., 10., 10.], [10., 10., 10., 10.]], + 'enable_variable': [True, True], + 'activated_elem': [[False, True, False, True], [False, True, False, True]]} + self.dspace = pd.DataFrame(dspace_dict) + self.values_dict[ + f'{self.ns}.design_space'] = self.dspace + + self.values_dict[f'{self.ns}.x_in'] = np.array([1., 2.]) + self.values_dict[f'{self.ns}.z_in'] = np.array([2., 1.]) + self.ee.load_study_from_input_dict(self.values_dict) + self.ee.configure() + self.ee.execute() + + disc = self.ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline + + self.check_jacobian(location=dirname(__file__), + filename=f'jacobian_{self.study_name}_dataframe_fill_one_column_for_key_2element_deactivated.pkl', + discipline=disc, + step=1e-16, + derr_approx='complex_step', + threshold=1e-5, + local_data=disc.local_data, + inputs=[f'{self.ns}.x_in', f'{self.ns}.z_in'], + outputs=[f'{self.ns}.x'] + ) diff --git a/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py b/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py new file mode 100644 index 0000000..baea4ba --- /dev/null +++ b/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py @@ -0,0 +1,643 @@ +''' +Copyright 2023 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +from os import environ +environ['DUMP_JACOBIAN_UNIT_TEST'] = 'tRUE' +import logging +from copy import deepcopy +from os.path import dirname + +import numpy as np + +from sostrades_core.execution_engine.execution_engine import ExecutionEngine +from sostrades_optimization_plugins.sos_processes.test.test_sellar_coupling_for_design_var.usecase import ( + Study as study_sellar_sub_wodvar, +) +from sostrades_optimization_plugins.sos_processes.test.test_sellar_opt_w_design_var_sub.usecase import ( + Study as study_sellar_sub, +) +from sostrades_optimization_plugins.sos_processes.test.test_sellar_sub_opt_w_design_var.usecase import ( + Study, +) +from sostrades_core.tests.core.abstract_jacobian_unit_test import ( + AbstractJacobianUnittest, +) +from sostrades_core.tools.grad_solvers.validgrad.FDValidGrad import FDValidGrad + + +class SellarOptimSubprocessJacobianDiscTest(AbstractJacobianUnittest): + """ + Class to test Sellar sub process derivatives in several cases. + - test_01_gradient_subprocess_double_level_coupling : coupling inside another coupling process + - test_02_gradient_subprocess_flatten_input_data : coupling of sellar disciplines and Design Var + Func Manager flattened + with input data as input of check_jacobian + - test_03_gradient_subprocess_flatten_local_data : use local data as input of check jacobian + - test_04_gradient_subprocess_flatten_input_data_cache : use input data and cache + - test_05_gradient_subprocess_flatten_input_data_cache_and_warmstart : use input data and cache and warmstart + - test_06_gradient_subprocess_flatten_local_data_deepcopy : deepcopy local data before check jacobian + - test_07_gradient_subprocess_flatten_local_data_different_exec_engine : execute but use another exec engine for check jacobain + - test_08_gradient_subprocess_flatten_local_data_deepcopy_high_step : deepcopy local data before check jacobian and use a high step for complex step + - test_09_FDGradient_Sellar : Test Sellar with no cache nor warmstart using FDGradient class (with no cache nor warm start) + - test_10_FDGradient_Sellar_warm_start_and_cache : Test Sellar gradient with cache and warmstart using FDGradient + - test_11_gradient_subprocess_wo_dvar_fmanager_flatten_local_data : Test Sellar without design var nor func manager + """ + + def analytic_grad_entry(self): + return [self._test_01_gradient_subprocess_double_level_coupling(), + ] + + def setUp(self): + self.name = 'Test' + + def _test_01_gradient_subprocess_double_level_coupling(self): + """ + Test objective lagrangian derivative using a double level coupling of sellar process. + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_sub_opt_w_design_var') + ee.factory.set_builders_to_coupling_builder(builder) + # configure + ee.configure() + # import study and set data + usecase = Study(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + ee.load_study_from_input_dict(full_values_dict) + # call the two methods used before an execute (to ensure that the execution in the linearize step is done in + # the same conditions as a normal execution) + ee.update_from_dm() + ee.prepare_execution() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + # check derivative of objective lagrangian wrt x_in and z_in + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_01.pkl' + inputs = ['Test.Sellar.SellarOptimScenario.x_in', 'Test.Sellar.SellarOptimScenario.z_in'] + outputs = ['Test.Sellar.SellarOptimScenario.objective_lagrangian'] + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-4, derr_approx='finite_differences', threshold=1e-15, + local_data=full_values_dict, + # coupling_disc.mdo_discipline_wrapp.mdo_discipline.local_data, + inputs=inputs, + outputs=outputs) + + def test_02_gradient_subprocess_flatten_input_data(self): + """ + Test objective lagrangian derivative using a one level coupling of sellar process. + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # call the two methods used before an execute (to ensure that the execution in the linearize step is done in + # the same conditions as a normal execution) + ee.update_from_dm() + ee.prepare_execution() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_02.pkl' + inputs = ['Test.x_in', 'Test.z_in'] + outputs = ['Test.objective_lagrangian'] + + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-14, derr_approx='complex_step', threshold=1e-10, + local_data=full_values_dict, + inputs=inputs, + outputs=outputs) + + def test_03_gradient_subprocess_flatten_local_data(self): + """ + Test objective lagrangian derivative using a one level coupling of sellar process and local data. + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # execute + ee.execute() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_03.pkl' + inputs = ['Test.x_in', 'Test.z_in'] + outputs = ['Test.objective_lagrangian'] + + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-14, derr_approx='complex_step', threshold=1e-10, + local_data=coupling_disc.mdo_discipline_wrapp.mdo_discipline.local_data, + inputs=inputs, + outputs=outputs) + + def test_04_gradient_subprocess_flatten_input_data_cache(self): + """ + Test objective lagrangian derivative using a one level coupling of sellar process and input data with cache. + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # call the two methods used before an execute (to ensure that the execution in the linearize step is done in + # the same conditions as a normal execution) + ee.update_from_dm() + ee.prepare_execution() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_04.pkl' + inputs = ['Test.x_in', 'Test.z_in'] + outputs = ['Test.objective_lagrangian'] + + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-25, derr_approx='complex_step', threshold=1e-10, + local_data=full_values_dict, + inputs=inputs, + outputs=outputs) + + def test_05_gradient_subprocess_flatten_input_data_cache_and_warmstart(self): + """ + Test objective lagrangian derivative using a one level coupling of sellar process and input data with + cache and warmstart. + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = True + full_values_dict['Test.SellarCoupling.cache_type'] = 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = True + full_values_dict['Test.cache_type'] = 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # call the two methods used before an execute (to ensure that the execution in the linearize step is done in + # the same conditions as a normal execution) + ee.update_from_dm() + ee.prepare_execution() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_05.pkl' + inputs = ['Test.x_in', 'Test.z_in'] + outputs = ['Test.objective_lagrangian'] + + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-14, derr_approx='complex_step', threshold=1e-10, + local_data=full_values_dict, + inputs=inputs, + outputs=outputs) + + def test_06_gradient_subprocess_flatten_local_data_deepcopy(self): + """ + Test objective lagrangian derivative using a one level coupling of sellar process and local data with deepcopy. + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # execute + ee.execute() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_06.pkl' + inputs = ['Test.x_in', 'Test.z_in'] + outputs = ['Test.objective_lagrangian'] + local_data_after_execute = coupling_disc.mdo_discipline_wrapp.mdo_discipline.local_data + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-15, derr_approx='complex_step', threshold=1e-20, + local_data=deepcopy(local_data_after_execute), + inputs=inputs, + outputs=outputs) + + def test_07_gradient_subprocess_flatten_local_data_different_exec_engine(self): + """ + Use local data on sellar sub process that comes from the execution of Sellar from another test + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # call the two methods used before an execute (to ensure that the execution in the linearize step is done in + # the same conditions as a normal execution) + ee.update_from_dm() + ee.prepare_execution() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_07.pkl' + inputs = ['Test.x_in', 'Test.z_in'] + outputs = ['Test.objective_lagrangian'] + local_data = self.execute_sellar() + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-14, derr_approx='complex_step', threshold=1e-10, + local_data=local_data, + inputs=inputs, + outputs=outputs) + + def test_08_gradient_subprocess_flatten_local_data_deepcopy_high_step(self): + """ + Test objective lagrangian derivative using a one level coupling of sellar process and local data with deepcopy. + We use a high complex step + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # execute + ee.execute() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_08.pkl' + inputs = ['Test.x_in', 'Test.z_in'] + outputs = ['Test.objective_lagrangian'] + local_data_after_execute = coupling_disc.mdo_discipline_wrapp.mdo_discipline.local_data + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-3, derr_approx='complex_step', threshold=1e-20, + local_data=deepcopy(local_data_after_execute), + inputs=inputs, + outputs=outputs) + + + def execute_sellar(self): + """ + Function to execute sellar with a newly created exec engine + Called only in test_07 + """ + # create new exec engine and execute sellar sub process + ee = ExecutionEngine(self.name) + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + self.data_sellar = full_values_dict + ee.load_study_from_input_dict(full_values_dict) + ee.execute() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + return deepcopy(coupling_disc.mdo_discipline_wrapp.mdo_discipline.local_data) + + def test_09_FDGradient_Sellar(self, x=np.array([1., 1., 1., 1., 5., 2.]), *args): + """ + Use FDValidGrad to check gradient + x : point on which gradient is validated + Test on Sellar without cache nor warmstart + """ + fd_valid_grad = FDValidGrad(1j, self.fx_sellar, self.dfx_sellar, fd_step=1e-14) + bool_valid, df_fd, df = fd_valid_grad.compare(x, args=args, force_print=True, return_all=True, treshold=1e-10) + logging.info(f"Derivative complex step : {df_fd}") + logging.info(f"Derivative linearize : {df}") + assert bool_valid + + def test_10_FDGradient_Sellar_warm_start_and_cache(self): + """ + Use FDGradient to test gradient with warmstart and cache + """ + ee, full_values_dict = self.create_ee_init_data() + + # update full values dict to activate warm start and cache + full_values_dict['Test.SellarCoupling.warm_start'] = True + full_values_dict['Test.SellarCoupling.cache_type'] = 'SimpleCache' # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = True + full_values_dict['Test.cache_type'] = 'SimpleCache' # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + ee.load_study_from_input_dict(full_values_dict) + + ee.update_from_dm() + ee.prepare_execution() + self.ee_to_test = ee + # run test 9 with different inputs (cache and warmstart activated) + self.test_09_FDGradient_Sellar(np.array([1., 1., 1., 1., 5., 2.]), ee, full_values_dict) + + def test_11_gradient_subprocess_wo_dvar_fmanager_flatten_local_data(self): + """ + Test objective and constraints derivative using a one level coupling of sellar process without design var + nor func manager and local data. + """ + # create new exec engine + ee = ExecutionEngine(self.name) + # get builder from process + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_coupling_for_design_var') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + # import study and set data + usecase = study_sellar_sub_wodvar(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # set cache to None and warm start to False + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # load_data + ee.load_study_from_input_dict(full_values_dict) + # execute + ee.execute() + ee.display_treeview_nodes() + coupling_disc = ee.root_process.proxy_disciplines[0] + + pkl_name = 'jacobian_obj_vs_design_var_sellar_test_11.pkl' + inputs = ['Test.SellarCoupling.x', 'Test.SellarCoupling.z'] + outputs = ['Test.SellarCoupling.obj', 'Test.SellarCoupling.c_1', 'Test.SellarCoupling.c_2'] + + self.check_jacobian(location=dirname(__file__), filename=pkl_name, + discipline=coupling_disc.mdo_discipline_wrapp.mdo_discipline, + step=1.0e-14, derr_approx='complex_step', threshold=1e-10, + local_data=coupling_disc.mdo_discipline_wrapp.mdo_discipline.local_data, + inputs=inputs, + outputs=outputs) + + def fx_sellar(self, x, *args): + """ + Method to execute Sellar + if given args, no need to load_study to use the same exec engine at each f(x_i+h) + params : + x : array : point of execution of f(x) + args : tuple : (exec_engine, dict) : tuple to use a given execution engine and dictionnary of inputs + return f(x) : value of objective lagrangian + """ + if not args: + # if no given exec engine and data, initialize new one and set data for x_in and z_in + ee, full_values_dict = self.create_ee_init_data() + full_values_dict['Test.x_in'] = x[0:4] + full_values_dict['Test.z_in'] = x[4:] + ee.load_study_from_input_dict(full_values_dict) + ee.execute() + else: + ee = args[0] + full_values_dict = args[1] + full_values_dict['Test.x_in'] = x[0:4] + full_values_dict['Test.z_in'] = x[4:] + # make sure we use the same exec engine at each step + assert self.ee_to_test is ee + ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline.execute(full_values_dict) + + objective_lagr = ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline.get_outputs_by_name( + 'Test.objective_lagrangian') + return objective_lagr + + def dfx_sellar(self, x, *args): + """ + Method to compute df/dx_in , df/dz_in at point x + if given args, no need to load_study to use the same exec engine at each df(x_i+h) + params : + x : array : point of execution of linearize method + args : tuple : (exec_engine, dict) : tuple to use a given execution engine and dictionnary of inputs + return [df/dx_in (x), df/dz_in (x)] + """ + if not args: + # if no given exec engine and data, initialize new one and set data for x_in and z_in + ee, full_values_dict = self.create_ee_init_data() + full_values_dict['Test.x_in'] = x[0:4] + full_values_dict['Test.z_in'] = x[4:] + + ee.load_study_from_input_dict(full_values_dict) + # prepare execution (for linearize step) + ee.update_from_dm() + ee.prepare_execution() + else: + ee = args[0] + full_values_dict = args[1] + full_values_dict['Test.x_in'] = x[0:4] + full_values_dict['Test.z_in'] = x[4:] + # make sure we use the same exec engine at each step + assert self.ee_to_test is ee + + # set differentiated inputs and outputs + ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline.add_differentiated_outputs( + ['Test.objective_lagrangian']) + ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline.add_differentiated_inputs( + ['Test.x_in', 'Test.z_in']) + jac = ee.root_process.proxy_disciplines[0].mdo_discipline_wrapp.mdo_discipline.linearize(full_values_dict) + df_dx_in = jac['Test.objective_lagrangian']['Test.x_in'] + df_dz_in = jac['Test.objective_lagrangian']['Test.z_in'] + # return [df/dx_in, df/dz_in] + return np.concatenate((df_dx_in, df_dz_in), axis=1) + + def create_ee_init_data(self): + """ + Create new exec engine, init data and return the exec engine with Sellar process + """ + ee = ExecutionEngine(self.name) + builder = ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', + 'test_sellar_opt_w_design_var_sub') + ee.factory.set_builders_to_coupling_builder(builder) + ee.configure() + usecase = study_sellar_sub(execution_engine=ee) + usecase.study_name = self.name + values_dict = usecase.setup_usecase() + full_values_dict = {} + for dict_v in values_dict: + full_values_dict.update(dict_v) + # make sure cache and warmstart are deactivated + full_values_dict['Test.SellarCoupling.warm_start'] = False + full_values_dict['Test.SellarCoupling.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.SellarCoupling.propagate_cache_to_children'] = True + full_values_dict['Test.warm_start'] = False + full_values_dict['Test.cache_type'] = None # 'SimpleCache' + full_values_dict['Test.propagate_cache_to_children'] = True + full_values_dict['Test.SellarCoupling.max_mda_iter'] = 30 + full_values_dict['Test.SellarCoupling.tolerance'] = 1e-20 + full_values_dict['Test.SellarCoupling.sub_mda_class'] = 'MDAGaussSeidel' + # ee.load_study_from_input_dict(full_values_dict) + return ee, deepcopy(full_values_dict) diff --git a/sostrades_optimization_plugins/tools/__init__.py b/sostrades_optimization_plugins/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/tools/cst_manager/__init__.py b/sostrades_optimization_plugins/tools/cst_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sostrades_optimization_plugins/tools/cst_manager/common_config.py b/sostrades_optimization_plugins/tools/cst_manager/common_config.py new file mode 100644 index 0000000..c8aec0d --- /dev/null +++ b/sostrades_optimization_plugins/tools/cst_manager/common_config.py @@ -0,0 +1,34 @@ +''' +Copyright 2022 Airbus SAS + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import numpy as np + +gravity = 9.80665 # Earth-surface gravitational acceleration (m/s**2) +rad_to_deg = 180. / np.pi +deg_to_rad = np.pi / 180. +ft2m = 0.3048 # feet to meter +mtft = 3.2808399 # meter to feet +kt2m_s = 0.5144444 +Nm2m = 1852 +### +# "Awesome" breaking model +mu_r = 0.02 # Rolling friction coefficient +mu_b = 0.6 # Breaking friction coefficient + +nr_solver_conf = {} +nr_solver_conf['eps'] = 10**-5 +nr_solver_conf['stop_residual'] = 10**-7 +nr_solver_conf['max_ite'] = 20 +nr_solver_conf['relax_factor'] = 0.95 diff --git a/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py b/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py new file mode 100644 index 0000000..1468d1b --- /dev/null +++ b/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py @@ -0,0 +1,360 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + +import re + +import numpy as np +from matplotlib import pyplot as plt + +from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_core.tools.base_functions.exp_min import ( + compute_dfunc_with_exp_min, + compute_func_with_exp_min, +) +from sostrades_optimization_plugins.tools.cst_manager.constraint_object import ConstraintObject + +# pylint: disable=no-value-for-parameter + +class ConstraintManager: + """ + Class to manage constraints for mission/vehicle design + (if GEMS this class should not be needed anymore) + """ + + def __init__(self): + """ + Constructor for the Constraint manager class + """ + self.constraints = {} + self.main_constraints = [] + + def initialize(self): + """ + Initialize constraints to empty list/dict + """ + self.constraints = {} + self.main_constraints = [] + + def add_constraint(self, cst_name, key_list, values, weights=None): + """ + Add a constraint to the dictionary constraints + + :param cst_name: constraint name + :param key_list: list of key to map the values + :param values: number or list of number or array of number + :param weights: same structure as values to specify weights, if omitted considered as 1 + :return: ConstraintObject + """ + if cst_name not in self.main_constraints: + self.main_constraints.append(cst_name) + + name = '@'.join([cst_name] + key_list) + + if np.any(np.isnan(values)): + raise Exception('NaN in {}, values={}'.format(name, values)) + + if name in self.constraints: + raise Exception( + 'Error in add_constraint: {name} is already in the manager'.format(name=name)) + + cst_obj = ConstraintObject(values, weights) + self.constraints[name] = cst_obj + + return cst_obj + + def get_constraint(self, name, cst_func=None, *args, **kwargs): + """ + Get the constraint with its name + """ + cst_obj_list = [] + + name_list = name.split('@') + + pattern = '(?:@.+)*@'.join(name_list) + '(?:@.+)*' + + for k in self.constraints: + if re.match(pattern, k): + cst_obj_list.append(self.constraints[k]) + + if cst_obj_list: + # From the list of constraints extract and concatenate list of + # values and weights + values = [] + weights = [] + for obj in cst_obj_list: + values.append(obj.values.flatten()) + weights.append(obj.weights.flatten()) + values = np.concatenate(values) + weights = np.concatenate(weights) + + # Apply cst_func + if cst_func is None: + cst_func = smooth_maximum(FunctionManager.cst_func_ineq()) + result = cst_func(values, weights, *args, **kwargs) + + if np.isnan(result): + raise Exception('Error in get_constraint for {} with {}: \ + \n\tvalues={}\n\tweights={}\n\tf_args={}\n\tf_kwargs={}'.format( + name, cst_func.__name__, values, weights, args, kwargs)) + + return result + + else: + return None + + def export_to_csv(self, filename): + """ + Export all constraints to a csv file + """ + strg = '' + for key, value in self.constraints.items(): + strg += ','.join([key] + [str(x) for x in value.values]) + '\n' + + with open(filename, 'w') as file: + file.write(strg) + +def cst_func_hard_max(values): + """ + Function which return the max of values + """ + return np.max(values) + + +def smooth_maximum(cst, alpha=3): + """ + Function + """ + max_exp = 650 # max value for exponention input, higher value gives infinity + + max_alphax = np.max(alpha * cst) + + k = max_alphax - max_exp + + den = np.sum(np.exp(alpha * cst - k)) + num = np.sum(cst * np.exp(alpha * cst - k)) + if den != 0: + result = num / den + else: + result = np.max(cst) + print('Warning in smooth_maximum! den equals 0, hard max is used') + + return result + +def compute_delta_type(delta, type='abs'): + if type == 'abs': + cdelta=np.sqrt(compute_func_with_exp_min(delta ** 2, 1e-15)) + elif type == 'hardmax': + cdelta = -np.sign(delta) * np.sqrt(compute_func_with_exp_min(delta ** 2, 1e-15)) + elif type == 'hardmin': + cdelta = np.sign(delta) * np.sqrt(compute_func_with_exp_min(delta ** 2, 1e-15)) + elif type == 'normal': + cdelta = delta + else: + raise Exception('Unknown type of delta_type') + return cdelta + +def compute_dcdelta_dvalue(delta, ddelta_dvalue, type='abs'): + if type == 'abs': + dcdelta_dvalue = 2 * delta * ddelta_dvalue * compute_dfunc_with_exp_min(delta ** 2, 1e-15)/( + 2*np.sqrt(compute_func_with_exp_min(delta ** 2, 1e-15))) + elif type == 'hardmax': + dcdelta_dvalue = -np.sign(delta) * 2 * delta * ddelta_dvalue * compute_dfunc_with_exp_min(delta ** 2, 1e-15) / ( + 2 * np.sqrt(compute_func_with_exp_min(delta ** 2, 1e-15))) + elif type == 'hardmin': + dcdelta_dvalue = np.sign(delta) * 2 * delta * ddelta_dvalue * compute_dfunc_with_exp_min(delta ** 2, 1e-15) / ( + 2 * np.sqrt(compute_func_with_exp_min(delta ** 2, 1e-15))) + elif type == 'normal': + dcdelta_dvalue = ddelta_dvalue + else: + raise Exception('Unknown type of delta_type') + return dcdelta_dvalue + +def compute_delta_constraint(value, goal, tolerable_delta=1.0, delta_type='abs', reference_value=1.0, eps=1E-3): + # Transform inputs into arrays if needed + length=1 + if isinstance(value, list): + length = len(value) + else: + value=np.array(value) + if not isinstance(goal, list): + goal = np.ones(length)*goal + if not isinstance(tolerable_delta, list): + tolerable_delta = np.ones(length)*tolerable_delta + + delta = (goal - value) + cdelta = compute_delta_type(delta, delta_type) + constraint = ((tolerable_delta - cdelta) / reference_value - eps) + return constraint + +def compute_ddelta_constraint(value, goal, tolerable_delta=1.0, delta_type='abs', reference_value=1.0, eps=1E-3): + # Transform inputs into arrays if needed + length = 1 + if isinstance(value, list): + length = len(value) + else: + value = np.array(value) + if not isinstance(goal, list): + goal = np.ones(length) * goal + if not isinstance(tolerable_delta, list): + tolerable_delta = np.ones(length) * tolerable_delta + + #First step + delta = (goal - value) + ddelta_dvalue = -np.identity(len(value)) + ddelta_dgoal = np.identity(len(goal)) + + # Second step + cdelta = compute_delta_type(delta, delta_type) + dcdelta_dvalue = compute_dcdelta_dvalue(delta, ddelta_dvalue, type=delta_type) + dcdelta_dgoal = compute_dcdelta_dvalue(delta, ddelta_dgoal, type=delta_type) + + # Third step + constraint = ((tolerable_delta - cdelta) / reference_value - eps) + ddelta_constraint_dvalue = -dcdelta_dvalue / reference_value + ddelta_constraint_dgoal = -dcdelta_dgoal / reference_value + ddelta_constraint_dtolerable_delta = np.identity(len(value)) / reference_value + + return ddelta_constraint_dvalue, ddelta_constraint_dgoal, ddelta_constraint_dtolerable_delta + +def delta_constraint_demonstrator(): + """ + Function to plot an example of the delta constraint and its treatment through the function manager + """ + tolerable_delta = 1.0 + eps=1E-3 + x = np.linspace(-2.0, 2.0, 100) + y = x + goal = np.zeros(len(x)) + delta = (goal - y) + min_valid_x, max_valid_x = x[-1], x[0] + for i, val in enumerate(delta): + if np.abs(val) 0: + raise KeyError("Header(s) %s of label %s not in %s" % + (str(key_errors), label, self.HEADERS)) + + def _is_str(self, val): + return isinstance(val, str) + + def __contains__(self, label): + return super(Database, self).__contains__(label) + + def __str__(self): + pp = PrettyPrinter() + return pp.pformat(self) + + +# _labels = ['a', 'b'] +# dico = Database(_labels) +# print(dico.keys()) +# print(dico.values()) +# ff = { +# 'a': { +# 'Comment': 10, +# 'Unit': 10, +# 'Value': 10}, +# 'b': { +# 'Comment': 5, +# 'Unit': 5, +# 'Value': 5}, +# 'c': { +# 'Comment': 5, +# 'Unit': 5, +# 'Value': 5}, +# 'x': { +# 'Comment': 4, +# 'Unit': 4}} +# print(dico.update(**ff)) +# print(dico) +# print(dico.get_value('b')) +# print(dico.get_value('x', 12)) +# print(dico.set_value('c', 99)) diff --git a/sostrades_optimization_plugins/tools/cst_manager/fileutils.py b/sostrades_optimization_plugins/tools/cst_manager/fileutils.py new file mode 100644 index 0000000..a2d13d9 --- /dev/null +++ b/sostrades_optimization_plugins/tools/cst_manager/fileutils.py @@ -0,0 +1,32 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' +import json +import sys + + +def readjson(file_fullpath): + # Load input file into data dictionary + try: + with open(file_fullpath, 'r') as fid: + return json.load(fid) + except Exception as error: + print('ERROR: Cannot decode filename=', str(file_fullpath)) + print(error) + sys.exit(1) + + + diff --git a/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py b/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py new file mode 100644 index 0000000..e5b95fa --- /dev/null +++ b/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py @@ -0,0 +1,260 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 2024/02/13-2024/05/16 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + +# pylint: disable=unsubscriptable-object +import numpy as np + +from sostrades_core.tools.base_functions.exp_min import ( + compute_dfunc_with_exp_min, + compute_func_with_exp_min, +) + +""" +Common file to have methods of func manager (mainly smooth max and it derivative) in other repositories +""" + + +def smooth_maximum(cst, alpha=3): + """ + Function + #-- smooth maximum of values return the value if it was a float + #-- return smooth maximum if objective was an array + """ + max_exp = 650 # max value for exponent input, higher value gives infinity + min_exp = -300 + max_alphax = np.max(alpha * cst) + + k = max_alphax - max_exp + # Deal with underflow . max with exp(-300) + exp_func = np.maximum(min_exp, np.multiply(alpha, cst) - k) + den = np.sum(np.exp(exp_func)) + num = np.sum(cst * np.exp(exp_func)) + if den != 0: + result = num / den + else: + result = np.max(cst) + print('Warning in smooth_maximum! den equals 0, hard max is used') + + return result + + +def smooth_maximum_vect(cst, alpha=3): + """ + Vectorized version of smooth_maximum function + """ + cst_array = np.array(cst) + max_exp = 650 # max value for exponent input, higher value gives infinity + min_exp = -300 + max_alphax = np.amax(alpha * cst_array, axis=1) + + k = max_alphax - max_exp + # Deal with underflow . max with exp(-300) + exp_func = np.maximum(min_exp, alpha * cst_array - + np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape)) + den = np.sum(np.exp(exp_func), axis=1) + num = np.sum(cst_array * np.exp(exp_func), axis=1) + result = np.where(den != 0, num / den, np.amax(cst_array, axis=1)) + if (den == 0).any(): + print('Warning in smooth_maximum! den equals 0, hard max is used') + + return result + + +def get_dsmooth_dvariable(cst, alpha=3): + max_exp = 650.0 # max value for exponent input, higher value gives infinity + min_exp = -300 + alphaxcst = alpha * np.array(cst) + max_alphax = np.max(alphaxcst) + #index_max = alphaxcst.index(max_alphax) + k = max_alphax - max_exp + exp_func = np.maximum(min_exp, alpha * np.array(cst) - k) + den = np.sum(np.exp(exp_func)) + num = np.sum(np.array(cst) * np.exp(exp_func)) + d_den = [] + d_num = [] + grad_value = [] + for elem in cst: + if alpha * elem == max_alphax: + dden = np.sum([-alpha * np.exp(max(min_exp, alpha * elem_cst - k)) + for elem_cst in cst if elem_cst * alpha != max_alphax]) + # derivative of den wto cstmax is 0 + dden = dden + 0.0 + d_den.append(dden) + dnum = np.sum([-alpha * elem_cst * np.exp(max(min_exp, alpha * elem_cst - k)) + for elem_cst in cst if elem_cst * alpha != max_alphax]) + dnum = dnum + 1.0 * np.exp(alpha * np.array(elem) - k) + d_num.append(dnum) + #grad_val_i = dnum / den - (num / den) * (dden / den) + else: + exp_func = max(min_exp, alpha * elem - k) + dden = alpha * np.exp(exp_func) + d_den.append(dden) + dnum = elem * (alpha * np.exp(exp_func) + ) + np.exp(exp_func) + d_num.append(dnum) + # add if den != 0 + grad_val_i = dnum / den - (num / den) * (dden / den) + grad_value.append(grad_val_i) + return grad_value + + +def get_dsmooth_dvariable_vect(cst, alpha=3): + cst_array = np.array(cst) + max_exp = 650.0 # max value for exponent input, higher value gives infinity + min_exp = -300 + alphaxcst = alpha * cst_array + + max_alphax = np.amax(alphaxcst, axis=1) + + k = max_alphax - max_exp + exp_func = np.maximum(min_exp, alpha * cst_array - + np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape)) + den = np.sum(np.exp(exp_func), axis=1) + num = np.sum(cst_array * np.exp(exp_func), axis=1) + + # Vectorized calculation + exp_func = np.maximum(min_exp, alpha * cst_array - + np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape)) + dden = alpha * np.exp(exp_func) + dnum = cst_array * (alpha * np.exp(exp_func) + ) + np.exp(exp_func) + grad_value = dnum / np.repeat(den, cst_array.shape[1]).reshape(cst_array.shape) - (np.repeat(num, cst_array.shape[1]).reshape( + cst_array.shape) / np.repeat(den, cst_array.shape[1]).reshape(cst_array.shape)) * (dden / np.repeat(den, cst_array.shape[1]).reshape(cst_array.shape)) + + # Special case for max element + max_elem = np.amax(cst_array * np.sign(alpha), axis=1) * np.sign(alpha) + non_max_idx = np.array([cst_array[i] != max_elem[i] + for i in np.arange(cst_array.shape[0])]).reshape(cst_array.shape[0], cst_array.shape[1]) + dden_max = np.sum(-alpha * non_max_idx * + np.exp(np.maximum(min_exp, alpha * cst_array - np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape))), axis=1) + dnum_max = np.sum(-alpha * cst_array * non_max_idx * + np.exp(np.maximum(min_exp, alpha * cst_array - np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape))), axis=1) + # derivative of den wto cstmax is 0 + dden_max = dden_max + 0.0 + dnum_max = dnum_max + 1.0 * np.exp(alpha * max_elem - k) + grad_val_max = dnum_max / den - (num / den) * (dden_max / den) + + for i in np.arange(cst_array.shape[0]): + grad_value[i][np.logical_not(non_max_idx)[i]] = grad_val_max[i] + + return grad_value + +def soft_maximum_vect(cst, k=7e2): + """ + Soft maximum function to get the maximum between array of values while always remaining above or equal to the + maximum and ensuring gradient continuity. + The default value of k is intended for arrays scaled between [-1.0, 1.0], the formula will overflow if + k*max(array)>=710. + Quasi-arithmetic mean function. + https://www.johndcook.com/blog/2010/01/13/soft-maximum/ + https://www.johndcook.com/blog/2010/01/20/how-to-compute-the-soft-maximum/ + """ + cst_array = np.array(cst) + cst_array_limited = np.sign(cst_array)*compute_func_with_exp_min(np.abs(cst_array), 1.0E-15/k) + if 'complex' in str(cst_array.dtype): + cst_array_limited += np.imag(cst_array)*1j + if np.amax(abs(cst_array_limited))*k>709: + raise ValueError('The absolute value of k*max(cst_array) is too high and would cause a floating point error') + result = np.log(np.sum(np.exp(k*cst_array_limited), axis=1))/k + return result + +def get_dsoft_maximum_vect(cst, k=7e2): + """ + Return derivative of soft maximum + """ + cst_array = np.array(cst) + cst_array_limited = np.sign(cst_array)*compute_func_with_exp_min(np.abs(cst_array), 1.0E-15/k) + result = np.log(np.sum(np.exp(k * cst_array_limited), axis=1)) / k + + d_cst_array = np.ones(cst_array.shape) + d_cst_array_limited = d_cst_array * \ + compute_dfunc_with_exp_min(np.abs(cst_array), 1.0E-15/k) + d_exp = k*d_cst_array_limited*np.exp(k*cst_array_limited) + d_sum = d_exp + d_log = (1/k) * (d_sum / np.sum(np.exp(k*cst_array_limited), axis=1).reshape(cst_array_limited.shape[0],1)) + d_log = np.where(d_log>1E-20, d_log, 0.0) + return d_log + +def cons_smooth_maximum_vect(cst, alpha=1E16): + """ + Conservative smooth maximum function. + This modified version of the smooth_max adds an epsilon value to the smoothed value + in order to have a conservative value (always smooth_value > max(values)) + """ + cst_array = np.array(cst) + max_exp = 650 # max value for exponent input, higher value gives infinity + min_exp = -300 + max_alphax = np.amax(alpha * cst_array, axis=1) + + k = max_alphax - max_exp + # Deal with underflow . max with exp(-300) + exp_func = np.maximum(min_exp, alpha * cst_array - + np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape)) + den = np.sum(np.exp(exp_func), axis=1) + num = np.sum(cst_array * np.exp(exp_func), axis=1) + result = np.where(den != 0, num / den + 0.3 / alpha, np.amax(cst_array, axis=1)) + if (den == 0).any(): + print('Warning in smooth_maximum! den equals 0, hard max is used') + + return result + +def get_dcons_smooth_dvariable_vect(cst, alpha=1E16): + cst_array = np.array(cst) + max_exp = 650.0 # max value for exponent input, higher value gives infinity + min_exp = -300 + alphaxcst = alpha * cst_array + + max_alphax = np.amax(alphaxcst, axis=1) + + k = max_alphax - max_exp + exp_func = np.maximum(min_exp, alpha * cst_array - + np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape)) + den = np.sum(np.exp(exp_func), axis=1) + num = np.sum(cst_array * np.exp(exp_func), axis=1) + + # Vectorized calculation + exp_func = np.maximum(min_exp, alpha * cst_array - + np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape)) + dden = alpha * np.exp(exp_func) + dnum = cst_array * (alpha * np.exp(exp_func) + ) + np.exp(exp_func) + grad_value = dnum / np.repeat(den, cst_array.shape[1]).reshape(cst_array.shape) - ( + np.repeat(num, cst_array.shape[1]).reshape( + cst_array.shape) / np.repeat(den, cst_array.shape[1]).reshape(cst_array.shape)) * ( + dden / np.repeat(den, cst_array.shape[1]).reshape(cst_array.shape)) + + # Special case for max element + max_elem = np.amax(cst_array * np.sign(alpha), axis=1) * np.sign(alpha) + non_max_idx = np.array([cst_array[i] != max_elem[i] + for i in np.arange(cst_array.shape[0])]).reshape(cst_array.shape[0], cst_array.shape[1]) + dden_max = np.sum(-alpha * non_max_idx * + np.exp(np.maximum(min_exp, + alpha * cst_array - np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape))), + axis=1) + dnum_max = np.sum(-alpha * cst_array * non_max_idx * + np.exp(np.maximum(min_exp, + alpha * cst_array - np.repeat(k, cst_array.shape[1]).reshape(cst_array.shape))), + axis=1) + # derivative of den wto cstmax is 0 + dden_max = dden_max + 0.0 + dnum_max = dnum_max + 1.0 * np.exp(alpha * max_elem - k) + grad_val_max = dnum_max / den - (num / den) * (dden_max / den) + + for i in np.arange(cst_array.shape[0]): + grad_value[i][np.logical_not(non_max_idx)[i]] = grad_val_max[i] + + return grad_value \ No newline at end of file From 36adddbc5f147460f1cf8432ca433c143d70ce17 Mon Sep 17 00:00:00 2001 From: Carlos Ortega Date: Mon, 24 Jun 2024 12:00:03 +0200 Subject: [PATCH 02/13] adding wrong headers test NOK --- .gitignore | 14 +++++ headers_ignore_config.json | 35 +++++++++++++ .../tests/l0_test_header.py | 51 +++++++++++++++++++ .../tests/l1s_test_all_usecases.py | 39 ++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 .gitignore create mode 100644 headers_ignore_config.json create mode 100644 sostrades_optimization_plugins/tests/l0_test_header.py create mode 100644 sostrades_optimization_plugins/tests/l1s_test_all_usecases.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0938fd8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.pyc +__pycache__ +.project +.pydevproject +.settings/ +coverage.xml +nosetests.xml +.coverage +xdsm.html +build/ +dist/ +/.pytest_cache/ +/sostrades_optimization_plugins/tests/__pycache__/ +/sostrades_optimization_plugins/tests/.pytest_cache/ \ No newline at end of file diff --git a/headers_ignore_config.json b/headers_ignore_config.json new file mode 100644 index 0000000..a62a6c3 --- /dev/null +++ b/headers_ignore_config.json @@ -0,0 +1,35 @@ +{ + "extension_to_ignore": [ + "pkl", + "png", + "jpg", + "csv", + "md", + "markdown", + "avif", + "json", + "in", + "gitignore", + "cfg", + "puml", + "pdf", + "txt", + "ipynb", + "zip", + "rst", + "ini", + "coveragerc", + "yaml", + "bat", + "tex", + "toml" + ], + "files_to_ignore": [ + "LICENSE", + ".readthedocs", + "docs/Makefile", + ".flake8", + ".prettierignore" + ], + "airbus_rev_commit": "fb7c7e2" +} \ No newline at end of file diff --git a/sostrades_optimization_plugins/tests/l0_test_header.py b/sostrades_optimization_plugins/tests/l0_test_header.py new file mode 100644 index 0000000..8f631e5 --- /dev/null +++ b/sostrades_optimization_plugins/tests/l0_test_header.py @@ -0,0 +1,51 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + + +import json +import os +import pprint +import unittest + +from sostrades_core.tools.check_headers import HeaderTools + + +class Testheader(unittest.TestCase): + """ + Check headers test class + """ + + def setUp(self): + ''' + Initialize third data needed for testing + ''' + self.pp = pprint.PrettyPrinter(indent=4, compact=True) + + with open(os.path.join(os.path.dirname(__file__),"..","..","headers_ignore_config.json"),"r",encoding="utf-8") as f: + + headers_ignore_config=json.load(f) + + self.extension_to_ignore = headers_ignore_config["extension_to_ignore"] + #Add here the files to ignore + self.files_to_ignore = headers_ignore_config["files_to_ignore"] + #commit from where to compare added, modeified deleted ... + self.airbus_rev_commit = headers_ignore_config["airbus_rev_commit"] + + + + def test_Headers(self): + ht = HeaderTools() + ht.check_headers(self.extension_to_ignore, self.files_to_ignore, self.airbus_rev_commit) diff --git a/sostrades_optimization_plugins/tests/l1s_test_all_usecases.py b/sostrades_optimization_plugins/tests/l1s_test_all_usecases.py new file mode 100644 index 0000000..e75fa25 --- /dev/null +++ b/sostrades_optimization_plugins/tests/l1s_test_all_usecases.py @@ -0,0 +1,39 @@ +''' +Copyright 2022 Airbus SAS +Modifications on 06/07/2024 Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +''' + +import pprint +import unittest + +from sostrades_core.sos_processes.script_test_all_usecases import _test_all_usecases + + +class TestUseCases(unittest.TestCase): + """ + Usecases test class + """ + + def setUp(self): + """Initialize third data needed for testing""" + self.pp = pprint.PrettyPrinter(indent=4, compact=True) + self.processes_repo = 'sostrades_optimization_plugins.sos_processes' + self.maxDiff = None + + def test_all_usecases(self): + test_passed, output_error = _test_all_usecases(processes_repo=self.processes_repo) + if not test_passed: + raise Exception(f'{output_error}') From 2e29bbdf00fa9fe498aec2f8b3edc8795a6c2ad5 Mon Sep 17 00:00:00 2001 From: Carlos Ortega Date: Mon, 24 Jun 2024 12:09:08 +0200 Subject: [PATCH 03/13] clearing pyc files committed --- headers_ignore_config.json | 2 +- ...4_optim_scenario.cpython-39-pytest-7.4.3.pyc | Bin 2980 -> 0 bytes ...ith_func_manager.cpython-39-pytest-7.4.3.pyc | Bin 2558 -> 0 bytes ..._44_func_manager.cpython-39-pytest-7.4.3.pyc | Bin 19024 -> 0 bytes ...gn_var_in_sellar.cpython-39-pytest-7.4.3.pyc | Bin 6476 -> 0 bytes ..._in_sellar.cpython-39-pytest-7.4.3.pyc.14916 | Bin 6461 -> 0 bytes ...st_63_design_var.cpython-39-pytest-7.4.3.pyc | Bin 10179 -> 0 bytes ..._design_var_disc.cpython-39-pytest-7.4.3.pyc | Bin 7552 -> 0 bytes ...optim_subprocess.cpython-39-pytest-7.4.3.pyc | Bin 18638 -> 0 bytes 9 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario.cpython-39-pytest-7.4.3.pyc delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario_with_func_manager.cpython-39-pytest-7.4.3.pyc delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_44_func_manager.cpython-39-pytest-7.4.3.pyc delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_62_design_var_in_sellar.cpython-39-pytest-7.4.3.pyc delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_62_design_var_in_sellar.cpython-39-pytest-7.4.3.pyc.14916 delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l0_test_63_design_var.cpython-39-pytest-7.4.3.pyc delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l1_test_01_gradient_design_var_disc.cpython-39-pytest-7.4.3.pyc delete mode 100644 sostrades_optimization_plugins/tests/__pycache__/l1_test_gradient_sellar_optim_subprocess.cpython-39-pytest-7.4.3.pyc diff --git a/headers_ignore_config.json b/headers_ignore_config.json index a62a6c3..79f028f 100644 --- a/headers_ignore_config.json +++ b/headers_ignore_config.json @@ -31,5 +31,5 @@ ".flake8", ".prettierignore" ], - "airbus_rev_commit": "fb7c7e2" + "airbus_rev_commit": "d833fdf740389587b876e30f084631430687fa5a" } \ No newline at end of file diff --git a/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario.cpython-39-pytest-7.4.3.pyc deleted file mode 100644 index 10470319820df75ac6bb6844f3f5a2d6dc8fbe30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2980 zcmbtWOK&5`5uO;7>*H`mSkJ7O&LqA4L6iDS~E?m z<#}!Q%t{ofOV*bh@(YY0(lN;Y$RWoZ2fpRxzmP+msvb(F=u?mp*i(;wR9AiVjrhWX zZ^84oKNf?(ELzsTP?>&QP~L-|{s#=9MXk^doyew+dDmjsv1}`@(}rDnnzU&=9`)c| zi{@gV`V(J^&V$X37UD&^Xl(W9PTZ#Lc!@33OU1Ny+oUi5mr zOjjoI8z3(mc`o`%{E$Ahtq-lx4_jYbVM}-?KYdlBkHUHJv#%WWck$?zMTsDnZ(c#P zq0_h47WlDaS^jpKjg%Z7=VWVRYm0QHIw~~jcl-XQX($IW;JHi_P0|D>ytTfuxxV%E z35ZYEH-5Fg`D=1(gSW^w&xRtFiS+$XWFQhPLQ*85P$WMVq?_>oUK7t_^0`nNwkKPi z4e|?|usU(AuKmUzr3H!kh$LxF3N2s*smVY_f{3#~WI2%u3DP)=Bu@fCPGx><4w-Dx z@xPpGkRIh6=5m;wjYv9}o<(@>`vjijJkQ=)Uq3xP?Qj!iCso7ssJeq*|734_x7Xi& z0+IUu^CS{l6D3|0Qo#j}MucY&N5GFDx`>~WR1rQ@0(^Ojah@uf%VhYN=yZ^uawYsw z>RidABEOZON$dn}Iz0hW#1pdG?UTL!DtW)#-|Ij2zt}tYX#e>E`J(&mS+{qvx7#QC z&&c+EZ)fjdZ@&lH2c+Bkl6<_^+j&d`qz9@X&N79uLx2)fC&G^37nt0evXB8dQP-IW z5CU+cWLWT_Aj9;fPzhXwWJ1MKWAQYE7W$ElWj?{m5B0t&#dpv=R&UKNy-i}}= z)1Cc3V1z4~t~q6$E5%1;TZ^1!N+x-l8SH^;g=;NT&ddzPqd3($gY5IDynD-KY_Rp6 z-7^ssxO_Jm%0z&v-p`8=kG=mHto}@;0ptO$@&OO>RE+|RaQcDns2YD61YuDdnje~; z)zx1GfNIuEa+phk2x2z=z@9o0d8i4$ZTRVL zVF>N9#jG606YCYq4oJ>P?b5{%uAB?>pk5!_SGH}TcGyO;jBj%#21-pDZb8!N1bjc=?!*FxN-(mkWkN$2LA*4Vv~ z=kZKD@nqp-G4xlgi^jNdc?V8w!n-|gUfCDk*n>0Oi@6v73@u1pt4AA{xy`NfH#HPQ z@la?Mq)K!^1pt`(2;ISy(MLEkEux4k2Do6SY)~Wti+KXLRBJYEi|dLrVM=~}ztcDK zZxyCPy_ladnG5wDCYriD8m4p!@NgmmXvPmA5Gj&eVQJQuN;l!LP#!3yBShI8RB)lo zLxAZ~AtT^6Hc)AN9Wo^d;E6}GJcUaY&}qr=+E~s7X)=(*LJ2j8G58vx$?zn)0IQ3P z0pSH)i?UH?_X=@jS_*0($)iF9HQC74oNzm@hvLBOK zRm~|b!wvpz}GKlY0Tq6)N)Pe6 z4NPTVpjOaU3nE?CfybU_Q|Q6=RtTZed$&U0Z;@j4BN*KFs%Z&m3v3E#lldC_`FG#; Hpn?7iT@ST6 diff --git a/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario_with_func_manager.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l0_test_14_optim_scenario_with_func_manager.cpython-39-pytest-7.4.3.pyc deleted file mode 100644 index 5dc8835031c00b7ec7b1f3813341da6f4aefe428..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2558 zcmbtW&u`l{6qaP!mKEn$fdcIg1(9OI0wi{lcEivOL%k&JjCIaXCkrZMBN&Nxm`b8T zQgs~Ir}nzTfZd7#$#MVAj=~NDcIv;d!?s7+ahT?ogAMbC@W^8r0>d)`Gq6I_v+{Q#vMs|jqM}zaN0PEv21y}wqKa3^_w3M( zX1p2Gcx(j4p!CWJO0;}jeOd5kgEF0aZix<^eQ9{}bpCwd0&WYewz2GxBg-({O`h}x z>mDhzv9_^+8cZCf615v`_iG-o4)cj(JeG*ZTH#)OZN0v6e-)

uVp?*FQ#AF1Uv_ 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 Q*R09>kCk(@1vBb@0n$d)mH+?% diff --git a/sostrades_optimization_plugins/tests/__pycache__/l1_test_01_gradient_design_var_disc.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l1_test_01_gradient_design_var_disc.cpython-39-pytest-7.4.3.pyc deleted file mode 100644 index 842b2dca653be40130ac5871e65694af77f5e780..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7552 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 Cv+>6O diff --git a/sostrades_optimization_plugins/tests/__pycache__/l1_test_gradient_sellar_optim_subprocess.cpython-39-pytest-7.4.3.pyc b/sostrades_optimization_plugins/tests/__pycache__/l1_test_gradient_sellar_optim_subprocess.cpython-39-pytest-7.4.3.pyc deleted file mode 100644 index 86e8f8041d15ea7e184ec542b51488e037803eb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18638 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 c`dae^T2(mvAz~wk;4LIp0iFGWjJALO50 Date: Wed, 26 Jun 2024 16:07:01 +0200 Subject: [PATCH 04/13] /!\ IGNORE ALL HEADERS THAT MUST STAY AIRBUS --- headers_ignore_config.json | 26 ++++++++++++++++++- pytest.ini | 4 +-- sostrades_optimization_plugins/__init__.py | 15 +++++++++++ .../models/__init__.py | 15 +++++++++++ .../models/design_var/__init__.py | 15 +++++++++++ .../models/design_var/design_var.py | 2 +- .../models/func_manager/__init__.py | 15 +++++++++++ .../sos_processes/__init__.py | 15 +++++++++++ .../sos_processes/test/__init__.py | 15 +++++++++++ .../test_sellar_opt_w_design_var/__init__.py | 15 +++++++++++ .../__init__.py | 15 +++++++++++ .../__init__.py | 15 +++++++++++ .../sos_wrapping/__init__.py | 15 +++++++++++ .../tests/__init__.py | 15 +++++++++++ .../tools/__init__.py | 15 +++++++++++ .../tools/cst_manager/__init__.py | 15 +++++++++++ 16 files changed, 223 insertions(+), 4 deletions(-) diff --git a/headers_ignore_config.json b/headers_ignore_config.json index 79f028f..22ce4ec 100644 --- a/headers_ignore_config.json +++ b/headers_ignore_config.json @@ -29,7 +29,31 @@ ".readthedocs", "docs/Makefile", ".flake8", - ".prettierignore" + ".prettierignore", + "sostrades_optimization_plugins/models/design_var/design_var.py", + "sostrades_optimization_plugins/models/design_var/design_var_disc.py", + "sostrades_optimization_plugins/models/func_manager/func_manager.py", + "sostrades_optimization_plugins/models/func_manager/func_manager_disc.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/process.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py", + "sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py", + "sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py", + "sostrades_optimization_plugins/tests/l0_test_44_func_manager.py", + "sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py", + "sostrades_optimization_plugins/tests/l1s_test_all_usecases.py", + "sostrades_optimization_plugins/tools/cst_manager/common_config.py", + "sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py", + "sostrades_optimization_plugins/tools/cst_manager/constraint_object.py", + "sostrades_optimization_plugins/tools/cst_manager/database.py", + "sostrades_optimization_plugins/tools/cst_manager/fileutils.py", + "sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py", + "sostrades_optimization_plugins/tools/cst_manager/__init__.py" ], "airbus_rev_commit": "d833fdf740389587b876e30f084631430687fa5a" } \ No newline at end of file diff --git a/pytest.ini b/pytest.ini index 82142fc..49b5731 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] python_files = l*_test*.py -testpaths = +testpaths = sostrades_optimization_plugins/tests -addopts = --numprocesses=auto +addopts = --numprocesses=auto \ No newline at end of file diff --git a/sostrades_optimization_plugins/__init__.py b/sostrades_optimization_plugins/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/__init__.py +++ b/sostrades_optimization_plugins/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/models/__init__.py b/sostrades_optimization_plugins/models/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/models/__init__.py +++ b/sostrades_optimization_plugins/models/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/models/design_var/__init__.py b/sostrades_optimization_plugins/models/design_var/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/models/design_var/__init__.py +++ b/sostrades_optimization_plugins/models/design_var/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/models/design_var/design_var.py b/sostrades_optimization_plugins/models/design_var/design_var.py index 2d9bd11..3d2b6c2 100644 --- a/sostrades_optimization_plugins/models/design_var/design_var.py +++ b/sostrades_optimization_plugins/models/design_var/design_var.py @@ -30,7 +30,7 @@ class DesignVar(object): VALUE = "value" DATAFRAME_FILL = SoSWrapp.DATAFRAME_FILL COLUMNS_NAMES = SoSWrapp.COLUMNS_NAMES - ONE_COLUMN_PER_KEY = SoSWrapp.ONE_COLUMN_PER_KEY + ONE_COLUMN_PER_KEY = "one column per key" # FIXME: on 4.0.3 --> SoSWrapp.ONE_COLUMN_PER_KEY ONE_COLUMN_FOR_KEY = SoSWrapp.ONE_COLUMN_FOR_KEY DATAFRAME_FILL_POSSIBLE_VALUES = [ONE_COLUMN_PER_KEY, ONE_COLUMN_FOR_KEY] DESIGN_SPACE = 'design_space' diff --git a/sostrades_optimization_plugins/models/func_manager/__init__.py b/sostrades_optimization_plugins/models/func_manager/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/models/func_manager/__init__.py +++ b/sostrades_optimization_plugins/models/func_manager/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/__init__.py b/sostrades_optimization_plugins/sos_processes/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/sos_processes/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/test/__init__.py b/sostrades_optimization_plugins/sos_processes/test/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/sos_processes/test/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py index e69de29..b119fbf 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2022 Airbus SAS + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py index e69de29..b119fbf 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2022 Airbus SAS + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py index e69de29..b119fbf 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2022 Airbus SAS + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/sos_wrapping/__init__.py b/sostrades_optimization_plugins/sos_wrapping/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/sos_wrapping/__init__.py +++ b/sostrades_optimization_plugins/sos_wrapping/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/tests/__init__.py b/sostrades_optimization_plugins/tests/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/tests/__init__.py +++ b/sostrades_optimization_plugins/tests/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/tools/__init__.py b/sostrades_optimization_plugins/tools/__init__.py index e69de29..18d49ea 100644 --- a/sostrades_optimization_plugins/tools/__init__.py +++ b/sostrades_optimization_plugins/tools/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2024 Capgemini + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file diff --git a/sostrades_optimization_plugins/tools/cst_manager/__init__.py b/sostrades_optimization_plugins/tools/cst_manager/__init__.py index e69de29..b119fbf 100644 --- a/sostrades_optimization_plugins/tools/cst_manager/__init__.py +++ b/sostrades_optimization_plugins/tools/cst_manager/__init__.py @@ -0,0 +1,15 @@ +''' +Copyright 2022 Airbus SAS + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' \ No newline at end of file From 62112876cc16742969a7270e5cdbf3104da330cd Mon Sep 17 00:00:00 2001 From: GOYON Guillaume Date: Thu, 27 Jun 2024 10:25:17 +0200 Subject: [PATCH 05/13] New ruff rules and fixes --- ruff.toml | 2 +- .../models/design_var/design_var.py | 3 +-- .../models/design_var/design_var_disc.py | 4 ++-- .../models/func_manager/func_manager.py | 6 ++++-- .../models/func_manager/func_manager_disc.py | 14 ++++++++------ .../test_sellar_coupling_for_design_var/usecase.py | 1 - .../test/test_sellar_opt_w_design_var/usecase.py | 6 ++++-- .../test_sellar_opt_w_design_var_sub/usecase.py | 6 ++++-- .../test/test_sellar_opt_w_func_manager/usecase.py | 6 ++++-- .../usecase.py | 6 ++++-- .../test_sellar_sub_opt_w_design_var/usecase.py | 6 ++++-- .../l0_test_14_optim_scenario_with_func_manager.py | 1 + .../tests/l0_test_44_func_manager.py | 6 ++++-- .../tests/l0_test_62_design_var_in_sellar.py | 12 +++++++----- .../tests/l0_test_63_design_var.py | 8 ++++---- .../tests/l1_test_01_gradient_design_var_disc.py | 9 ++++----- .../l1_test_gradient_sellar_optim_subprocess.py | 11 ++++++----- .../tools/cst_manager/constraint_manager.py | 10 +++++++--- .../tools/cst_manager/func_manager_common.py | 1 - 19 files changed, 69 insertions(+), 49 deletions(-) diff --git a/ruff.toml b/ruff.toml index d0d15c1..d928c99 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,6 @@ [lint] +select = ["I", "TCH", "PLC", "PLE"] # extend-select = ["ALL"] -extend-select = ["I"] ignore = ["E722", "F841", "E501"] # E722 Do not use bare `except` diff --git a/sostrades_optimization_plugins/models/design_var/design_var.py b/sostrades_optimization_plugins/models/design_var/design_var.py index 3d2b6c2..f3e3544 100644 --- a/sostrades_optimization_plugins/models/design_var/design_var.py +++ b/sostrades_optimization_plugins/models/design_var/design_var.py @@ -16,9 +16,8 @@ ''' import numpy as np from pandas import DataFrame, concat - -from sostrades_core.tools.bspline.bspline import BSpline from sostrades_core.execution_engine.sos_wrapp import SoSWrapp +from sostrades_core.tools.bspline.bspline import BSpline class DesignVar(object): diff --git a/sostrades_optimization_plugins/models/design_var/design_var_disc.py b/sostrades_optimization_plugins/models/design_var/design_var_disc.py index 3734a28..7b0d51d 100644 --- a/sostrades_optimization_plugins/models/design_var/design_var_disc.py +++ b/sostrades_optimization_plugins/models/design_var/design_var_disc.py @@ -17,14 +17,14 @@ import numpy as np import plotly.colors as plt_color from plotly import graph_objects as go - -from sostrades_optimization_plugins.models.design_var.design_var import DesignVar from sostrades_core.execution_engine.sos_wrapp import SoSWrapp from sostrades_core.tools.post_processing.charts.chart_filter import ChartFilter from sostrades_core.tools.post_processing.plotly_native_charts.instantiated_plotly_native_chart import ( InstantiatedPlotlyNativeChart, ) +from sostrades_optimization_plugins.models.design_var.design_var import DesignVar + color_list = plt_color.qualitative.Plotly color_list.extend(plt_color.qualitative.Alphabet) diff --git a/sostrades_optimization_plugins/models/func_manager/func_manager.py b/sostrades_optimization_plugins/models/func_manager/func_manager.py index 518f9c1..abbc810 100644 --- a/sostrades_optimization_plugins/models/func_manager/func_manager.py +++ b/sostrades_optimization_plugins/models/func_manager/func_manager.py @@ -15,9 +15,11 @@ limitations under the License. ''' import numpy as np - from sostrades_core.tools.base_functions.exp_min import compute_func_with_exp_min -from sostrades_optimization_plugins.tools.cst_manager.func_manager_common import smooth_maximum + +from sostrades_optimization_plugins.tools.cst_manager.func_manager_common import ( + smooth_maximum, +) class FunctionManager: diff --git a/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py b/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py index 7330fb0..f3b3149 100644 --- a/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py +++ b/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py @@ -25,22 +25,24 @@ import pandas as pd from numpy import asarray, float64, ndarray from plotly import graph_objects as go - -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager from sostrades_core.execution_engine.optim_manager_disc import OptimManagerDisc from sostrades_core.tools.base_functions.exp_min import ( compute_dfunc_with_exp_min, compute_func_with_exp_min, ) -from sostrades_optimization_plugins.tools.cst_manager.func_manager_common import ( - get_dsmooth_dvariable, - smooth_maximum, -) from sostrades_core.tools.post_processing.charts.chart_filter import ChartFilter from sostrades_core.tools.post_processing.plotly_native_charts.instantiated_plotly_native_chart import ( InstantiatedPlotlyNativeChart, ) +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) +from sostrades_optimization_plugins.tools.cst_manager.func_manager_common import ( + get_dsmooth_dvariable, + smooth_maximum, +) + warnings.simplefilter(action='ignore', category=FutureWarning) diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py index 1e2356c..8a7217a 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/usecase.py @@ -15,7 +15,6 @@ ''' import pandas as pd from numpy import array - from sostrades_core.study_manager.study_manager import StudyManager diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py index 4a91a12..b2c45ef 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py @@ -16,12 +16,14 @@ ''' import pandas as pd from numpy import arange, array +from sostrades_core.study_manager.study_manager import StudyManager -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( FunctionManagerDisc, ) -from sostrades_core.study_manager.study_manager import StudyManager AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py index 0f0b77c..a049382 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py @@ -15,12 +15,14 @@ ''' import pandas as pd from numpy import arange, array +from sostrades_core.study_manager.study_manager import StudyManager -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( FunctionManagerDisc, ) -from sostrades_core.study_manager.study_manager import StudyManager AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py index bd8f056..becd644 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py @@ -16,12 +16,14 @@ ''' import pandas as pd from numpy import array +from sostrades_core.study_manager.study_manager import StudyManager -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( FunctionManagerDisc, ) -from sostrades_core.study_manager.study_manager import StudyManager AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py index 25627af..525f839 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py @@ -16,12 +16,14 @@ ''' import pandas as pd from numpy import array +from sostrades_core.study_manager.study_manager import StudyManager -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( FunctionManagerDisc, ) -from sostrades_core.study_manager.study_manager import StudyManager AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py index 8549b0b..30876f6 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py @@ -15,12 +15,14 @@ ''' import pandas as pd from numpy import arange, array +from sostrades_core.study_manager.study_manager import StudyManager -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( FunctionManagerDisc, ) -from sostrades_core.study_manager.study_manager import StudyManager AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM diff --git a/sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py b/sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py index fe359f9..fde75d4 100644 --- a/sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py +++ b/sostrades_optimization_plugins/tests/l0_test_14_optim_scenario_with_func_manager.py @@ -17,6 +17,7 @@ import unittest from sostrades_core.execution_engine.execution_engine import ExecutionEngine + from sostrades_optimization_plugins.sos_processes.test.test_sellar_opt_w_func_manager.usecase import ( Study, ) diff --git a/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py b/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py index 899b774..cfdad5a 100644 --- a/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py +++ b/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py @@ -20,9 +20,11 @@ import numpy as np import pandas as pd from numpy import arange - from sostrades_core.execution_engine.execution_engine import ExecutionEngine -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager + +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( FunctionManagerDisc, ) diff --git a/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py b/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py index 6f91fac..1015b66 100644 --- a/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py +++ b/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py @@ -18,16 +18,18 @@ import numpy as np import pandas as pd - from sostrades_core.execution_engine.execution_engine import ExecutionEngine -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager -from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( - FunctionManagerDisc, -) from sostrades_core.tests.core.abstract_jacobian_unit_test import ( AbstractJacobianUnittest, ) +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) +from sostrades_optimization_plugins.models.func_manager.func_manager_disc import ( + FunctionManagerDisc, +) + AGGR_TYPE = FunctionManagerDisc.AGGR_TYPE AGGR_TYPE_SUM = FunctionManager.AGGR_TYPE_SUM AGGR_TYPE_SMAX = FunctionManager.AGGR_TYPE_SMAX diff --git a/sostrades_optimization_plugins/tests/l0_test_63_design_var.py b/sostrades_optimization_plugins/tests/l0_test_63_design_var.py index e292a4e..080fa16 100644 --- a/sostrades_optimization_plugins/tests/l0_test_63_design_var.py +++ b/sostrades_optimization_plugins/tests/l0_test_63_design_var.py @@ -15,15 +15,15 @@ ''' import numpy as np import pandas as pd - -from sostrades_optimization_plugins.models.design_var.design_var_disc import ( - DesignVarDiscipline, -) from sostrades_core.execution_engine.execution_engine import ExecutionEngine from sostrades_core.tests.core.abstract_jacobian_unit_test import ( AbstractJacobianUnittest, ) +from sostrades_optimization_plugins.models.design_var.design_var_disc import ( + DesignVarDiscipline, +) + class TestDesignVar(AbstractJacobianUnittest): """ diff --git a/sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py b/sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py index 36042fc..3cd0682 100644 --- a/sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py +++ b/sostrades_optimization_plugins/tests/l1_test_01_gradient_design_var_disc.py @@ -17,16 +17,15 @@ import numpy as np import pandas as pd - -from sostrades_optimization_plugins.models.design_var.design_var_disc import ( - DesignVarDiscipline -) - from sostrades_core.execution_engine.execution_engine import ExecutionEngine from sostrades_core.tests.core.abstract_jacobian_unit_test import ( AbstractJacobianUnittest, ) +from sostrades_optimization_plugins.models.design_var.design_var_disc import ( + DesignVarDiscipline, +) + class GradiantAssetDiscTestCase(AbstractJacobianUnittest): """ diff --git a/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py b/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py index baea4ba..3ef5edb 100644 --- a/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py +++ b/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py @@ -14,14 +14,19 @@ limitations under the License. ''' from os import environ + environ['DUMP_JACOBIAN_UNIT_TEST'] = 'tRUE' import logging from copy import deepcopy from os.path import dirname import numpy as np - from sostrades_core.execution_engine.execution_engine import ExecutionEngine +from sostrades_core.tests.core.abstract_jacobian_unit_test import ( + AbstractJacobianUnittest, +) +from sostrades_core.tools.grad_solvers.validgrad.FDValidGrad import FDValidGrad + from sostrades_optimization_plugins.sos_processes.test.test_sellar_coupling_for_design_var.usecase import ( Study as study_sellar_sub_wodvar, ) @@ -31,10 +36,6 @@ from sostrades_optimization_plugins.sos_processes.test.test_sellar_sub_opt_w_design_var.usecase import ( Study, ) -from sostrades_core.tests.core.abstract_jacobian_unit_test import ( - AbstractJacobianUnittest, -) -from sostrades_core.tools.grad_solvers.validgrad.FDValidGrad import FDValidGrad class SellarOptimSubprocessJacobianDiscTest(AbstractJacobianUnittest): diff --git a/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py b/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py index 1468d1b..7f975c6 100644 --- a/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py +++ b/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py @@ -19,13 +19,17 @@ import numpy as np from matplotlib import pyplot as plt - -from sostrades_optimization_plugins.models.func_manager.func_manager import FunctionManager from sostrades_core.tools.base_functions.exp_min import ( compute_dfunc_with_exp_min, compute_func_with_exp_min, ) -from sostrades_optimization_plugins.tools.cst_manager.constraint_object import ConstraintObject + +from sostrades_optimization_plugins.models.func_manager.func_manager import ( + FunctionManager, +) +from sostrades_optimization_plugins.tools.cst_manager.constraint_object import ( + ConstraintObject, +) # pylint: disable=no-value-for-parameter diff --git a/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py b/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py index e5b95fa..196a706 100644 --- a/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py +++ b/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py @@ -17,7 +17,6 @@ # pylint: disable=unsubscriptable-object import numpy as np - from sostrades_core.tools.base_functions.exp_min import ( compute_dfunc_with_exp_min, compute_func_with_exp_min, From 081054c7e992d602e1f2f6b45fc2a7390796d0af Mon Sep 17 00:00:00 2001 From: GOYON Guillaume Date: Fri, 28 Jun 2024 09:42:28 +0200 Subject: [PATCH 06/13] Added default ruff rule selection --- ruff.toml | 2 +- .../tests/l1_test_gradient_sellar_optim_subprocess.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ruff.toml b/ruff.toml index d928c99..017fa23 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,5 @@ [lint] -select = ["I", "TCH", "PLC", "PLE"] +select = ["I", "TCH", "PLC", "PLE", "F", "E"] # extend-select = ["ALL"] ignore = ["E722", "F841", "E501"] diff --git a/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py b/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py index 3ef5edb..66ab92d 100644 --- a/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py +++ b/sostrades_optimization_plugins/tests/l1_test_gradient_sellar_optim_subprocess.py @@ -13,11 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ''' -from os import environ - -environ['DUMP_JACOBIAN_UNIT_TEST'] = 'tRUE' import logging from copy import deepcopy +from os import environ from os.path import dirname import numpy as np @@ -37,6 +35,8 @@ Study, ) +environ['DUMP_JACOBIAN_UNIT_TEST'] = 'True' + class SellarOptimSubprocessJacobianDiscTest(AbstractJacobianUnittest): """ From a938274e3aa43d031be19eba6054cbcc86cccbdd Mon Sep 17 00:00:00 2001 From: GOYON Guillaume Date: Wed, 10 Jul 2024 11:17:29 +0200 Subject: [PATCH 07/13] Added same ruff checks as core --- ruff.toml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ruff.toml b/ruff.toml index 017fa23..e1c8c81 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,8 +1,13 @@ [lint] -select = ["I", "TCH", "PLC", "PLE", "F", "E"] +select = ["I", "TCH", "PLC", "PLE", "F", "E", "W", "PERF"] # extend-select = ["ALL"] -ignore = ["E722", "F841", "E501"] +ignore = ["E722", "F841", "E501", "D400", "D415", "D212", "D300", "PERF203", "ANN101"] # E722 Do not use bare `except` # F841 Local variable `xxx` is assigned to but never used # E501 Line too long +# D400 First line should end with a period +# D415 First line should end with a period, question mark, or exclamation point +# D212 [*] Multi-line docstring summary should start at the first line +# D300 [*] Use triple double quotes `"""` +# ANN101 Missing type annotation for `self` in method From 2ee3c822892bf46008b148ef667472247cff5e1b Mon Sep 17 00:00:00 2001 From: Carlos Ortega Date: Wed, 10 Jul 2024 11:34:22 +0200 Subject: [PATCH 08/13] updating optim plugins with lattest integrity checks and ruff --- .../models/func_manager/func_manager.py | 6 +++--- .../test_sellar_opt_w_design_var/usecase.py | 2 ++ .../test_sellar_opt_w_design_var_sub/usecase.py | 2 ++ .../test_sellar_opt_w_func_manager/usecase.py | 4 +++- .../usecase.py | 2 ++ .../test_sellar_sub_opt_w_design_var/usecase.py | 14 ++++++++------ .../tests/l0_test_44_func_manager.py | 17 ++++++++++++++++- .../tests/l0_test_62_design_var_in_sellar.py | 2 ++ 8 files changed, 38 insertions(+), 11 deletions(-) diff --git a/sostrades_optimization_plugins/models/func_manager/func_manager.py b/sostrades_optimization_plugins/models/func_manager/func_manager.py index abbc810..e44a9ec 100644 --- a/sostrades_optimization_plugins/models/func_manager/func_manager.py +++ b/sostrades_optimization_plugins/models/func_manager/func_manager.py @@ -73,11 +73,11 @@ def __to_array_type(self, value): t_val = type(value) if isinstance(value, np.ndarray): mod_value = value - elif t_val == type(np.array([0.])): + elif t_val is type(np.array([0.])): mod_value = value - elif t_val == type([]): + elif t_val is type([]): mod_value = np.array(value) - elif t_val == type(0.): + elif t_val is type(0.): mod_value = np.array(value) else: raise ValueError('Unsupported type ' + str(t_val)) diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py index b2c45ef..b14b439 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/usecase.py @@ -95,6 +95,8 @@ def setup_usecase(self): func_df = pd.DataFrame( columns=['variable', 'ftype', 'weight', AGGR_TYPE]) func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['parent'] = "parent" + func_df['namespace'] = "ns_functions" func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] func_df['weight'] = [200, 0.000001, 0.1] func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py index a049382..3ddcd0b 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/usecase.py @@ -88,6 +88,8 @@ def setup_usecase(self): func_df = pd.DataFrame( columns=['variable', 'ftype', 'weight', AGGR_TYPE]) func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['parent'] = "parent" + func_df['namespace'] = "ns_functions" func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] func_df['weight'] = [200, 0.000001, 0.1] func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py index becd644..eb80ac7 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/usecase.py @@ -82,6 +82,8 @@ def setup_usecase(self): columns=['variable', 'ftype', 'weight', AGGR_TYPE]) func_df['variable'] = ['c_1', 'c_2', 'obj'] func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] + func_df['parent'] = "parent" + func_df['namespace'] = "ns_functions" func_df['weight'] = [200, 0.000001, 0.1] func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] func_mng_name = 'FunctionManager' @@ -99,4 +101,4 @@ def setup_usecase(self): uc_cls = Study() uc_cls.load_data() uc_cls.execution_engine.display_treeview_nodes(display_variables=True) - uc_cls.run() + uc_cls.test() diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py index 525f839..bde48c3 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py @@ -79,6 +79,8 @@ def setup_usecase(self): func_df = pd.DataFrame( columns=['variable', 'ftype', 'weight', AGGR_TYPE]) func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['parent'] = "parent" + func_df['namespace'] = "ns_functions" func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] func_df['weight'] = [200, 0.000001, 0.1] func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py index 30876f6..c71e57d 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/usecase.py @@ -55,14 +55,14 @@ def setup_usecase(self): 'index': arange(0, 4, 1), 'index_name': 'index', 'namespace_in': 'ns_OptimSellar', - 'namespace_out': 'ns_OptimSellar' + 'namespace_out': 'ns_functions' }, 'z_in': {'out_name': 'z', 'out_type': 'array', 'index': [0, 1], 'index_name': 'index', 'namespace_in': 'ns_OptimSellar', - 'namespace_out': 'ns_OptimSellar' + 'namespace_out': 'ns_functions' } } @@ -73,15 +73,17 @@ def setup_usecase(self): # Sellar and design var inputs - disc_dict[f'{ns}.Sellar.{self.optim_name}.x_in'] = array([1., 1., 1., 1.]) + disc_dict[f'{ns}.Sellar.SellarOptimScenario.x_in'] = array([1., 1., 1., 1.]) disc_dict[f'{ns}.Sellar.{self.optim_name}.y_1'] = 5. disc_dict[f'{ns}.Sellar.{self.optim_name}.y_2'] = 1. - disc_dict[f'{ns}.Sellar.{self.optim_name}.z_in'] = array([5., 2.]) + disc_dict[f'{ns}.Sellar.SellarOptimScenario.z_in'] = array([5., 2.]) disc_dict[f'{ns}.{self.coupling_name}.Sellar_Problem.local_dv'] = 10. func_df = pd.DataFrame( columns=['variable', 'ftype', 'weight', AGGR_TYPE]) func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['parent'] = "parent" + func_df['namespace'] = "ns_functions" func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] func_df['weight'] = [200, 0.000001, 0.1] func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] @@ -94,11 +96,11 @@ def setup_usecase(self): disc_dict.update(values_dict) - return [disc_dict] + return disc_dict if '__main__' == __name__: uc_cls = Study() uc_cls.load_data() uc_cls.execution_engine.display_treeview_nodes(display_variables=True) - uc_cls.run() + uc_cls.test() diff --git a/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py b/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py index cfdad5a..8f79c7d 100644 --- a/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py +++ b/sostrades_optimization_plugins/tests/l0_test_44_func_manager.py @@ -197,6 +197,8 @@ def test_06_configure_func_manager_disc(self): 'eqcst1', 'eqcst2', 'obj1', 'obj2'] func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, INEQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] + func_df['aggr'] = "sum" + func_df['parent'] = 'obj' func_df['weight'] = [1., 1., 1., 1, 1, 0.8, 0.2] func_df['namespace'] = ['ns_functions'] * 6 + ['ns_functions_2'] values_dict = {} @@ -284,6 +286,10 @@ def test_07_jacobian_func_manager_disc(self): func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, INEQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] func_df['weight'] = [1., 1., 1., 1, 1, 0.8, 0.2] + func_df['aggr'] = "sum" + func_df['parent'] = 'obj' + func_df['namespace'] = '' + values_dict = {} values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df @@ -373,6 +379,10 @@ def test_08_jacobian_func_manager_disc2(self): INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] func_df['weight'] = [-0.5, -1., -1., -1., 0.8, 0.2] + func_df['aggr'] = "sum" + func_df['parent'] = 'obj' + func_df['namespace'] = '' + values_dict = {} values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df @@ -524,6 +534,8 @@ def test_10_jacobian_func_manager_disc_different_aggr(self): INEQ_CONSTRAINT, EQ_CONSTRAINT, EQ_CONSTRAINT, OBJECTIVE, OBJECTIVE] func_df['weight'] = [1., 1., 1., 1, 1, 0.8, 0.2] func_df['aggr'] = ['sum', 'sum', 'sum', 'sum', 'sum', 'smax', 'sum'] + func_df['aggr'] = "sum" + func_df['namespace'] = '' func_df['parent'] = ['ineqcst', 'ineqcst', 'ineqcst', 'eqcst', 'eqcst', 'obj', 'obj'] values_dict = {} values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df @@ -618,6 +630,8 @@ def test_11_jacobian_eq_delta_and_lin_to_quad(self): OBJECTIVE, OBJECTIVE] func_df['weight'] = [0.5, -1., -0.2, 0.2, 1.2, -1.0, 0.01, 0.8, 0.2] func_df['aggr'] = ['sum', 'sum', 'sum', 'sum', 'delta', 'lin_to_quad', 'lin_to_quad', 'smax', 'sum'] + func_df['parent'] = 'parent' + func_df['namespace'] = '' values_dict = {} values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df @@ -681,7 +695,8 @@ def test_11_jacobian_eq_delta_and_lin_to_quad(self): def test_12_test_number_iteration_output_optim_df(self): self.name = 'Test12' self.ee = ExecutionEngine(self.name) - + ns_dict = {'ns_functions': self.name + '.' + 'FunctionManager2'} + self.ee.ns_manager.add_ns_def(ns_dict) builder = self.ee.factory.get_builder_from_process('sostrades_optimization_plugins.sos_processes.test', 'test_sellar_opt_w_func_manager' ) diff --git a/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py b/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py index 1015b66..275aca6 100644 --- a/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py +++ b/sostrades_optimization_plugins/tests/l0_test_62_design_var_in_sellar.py @@ -121,6 +121,8 @@ def setUp(self): func_df = pd.DataFrame( columns=['variable', 'ftype', 'weight', AGGR_TYPE]) func_df['variable'] = ['c_1', 'c_2', 'obj'] + func_df['parent'] = "parent" + func_df['namespace'] = "ns_functions" func_df['ftype'] = [INEQ_CONSTRAINT, INEQ_CONSTRAINT, OBJECTIVE] func_df['weight'] = [200, 0.000001, 0.1] func_df[AGGR_TYPE] = [AGGR_TYPE_SUM, AGGR_TYPE_SUM, AGGR_TYPE_SUM] From 3b942bcc12da3e03788d79c9c8616c4bbba434e5 Mon Sep 17 00:00:00 2001 From: Carlos Ortega Date: Mon, 15 Jul 2024 12:29:44 +0200 Subject: [PATCH 09/13] ruff --- sostrades_optimization_plugins/__init__.py | 2 +- sostrades_optimization_plugins/models/__init__.py | 2 +- .../models/design_var/__init__.py | 2 +- .../models/design_var/design_var_disc.py | 4 ++-- .../models/func_manager/__init__.py | 2 +- .../models/func_manager/func_manager.py | 10 +++------- .../models/func_manager/func_manager_disc.py | 14 +++++++------- .../sos_processes/__init__.py | 2 +- .../sos_processes/test/__init__.py | 2 +- .../__init__.py | 2 +- .../test/test_sellar_opt_w_design_var/__init__.py | 2 +- .../test_sellar_opt_w_design_var_sub/__init__.py | 2 +- .../test_sellar_opt_w_func_manager/__init__.py | 2 +- .../test/test_sellar_opt_w_func_manager/process.py | 8 ++++---- .../__init__.py | 2 +- .../process.py | 6 +++--- .../usecase.py | 2 +- .../test_sellar_sub_opt_w_design_var/__init__.py | 2 +- .../sos_wrapping/__init__.py | 2 +- sostrades_optimization_plugins/tests/__init__.py | 2 +- .../tests/l0_test_header.py | 2 +- sostrades_optimization_plugins/tools/__init__.py | 2 +- .../tools/cst_manager/__init__.py | 2 +- .../tools/cst_manager/constraint_manager.py | 7 ++----- .../tools/cst_manager/func_manager_common.py | 2 +- 25 files changed, 40 insertions(+), 47 deletions(-) diff --git a/sostrades_optimization_plugins/__init__.py b/sostrades_optimization_plugins/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/__init__.py +++ b/sostrades_optimization_plugins/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/models/__init__.py b/sostrades_optimization_plugins/models/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/models/__init__.py +++ b/sostrades_optimization_plugins/models/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/models/design_var/__init__.py b/sostrades_optimization_plugins/models/design_var/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/models/design_var/__init__.py +++ b/sostrades_optimization_plugins/models/design_var/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/models/design_var/design_var_disc.py b/sostrades_optimization_plugins/models/design_var/design_var_disc.py index 7b0d51d..a0f058b 100644 --- a/sostrades_optimization_plugins/models/design_var/design_var_disc.py +++ b/sostrades_optimization_plugins/models/design_var/design_var_disc.py @@ -332,8 +332,8 @@ def get_post_processing_list(self, filters=None): def get_chart_BSpline(self, parameter, init_xvect=False): """ - Function to create post-proc for the design variables with display of the control points used to - calculate the B-Splines. + Function to create post-proc for the design variables with display of the control points used to calculate the + B-Splines. The activation/deactivation of control points is accounted for by inserting the values from the design space dataframe into the ctrl_pts if need be (activated_elem==False) and at the appropriate index. Input: parameter (name), parameter values, design_space diff --git a/sostrades_optimization_plugins/models/func_manager/__init__.py b/sostrades_optimization_plugins/models/func_manager/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/models/func_manager/__init__.py +++ b/sostrades_optimization_plugins/models/func_manager/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/models/func_manager/func_manager.py b/sostrades_optimization_plugins/models/func_manager/func_manager.py index e44a9ec..68960ae 100644 --- a/sostrades_optimization_plugins/models/func_manager/func_manager.py +++ b/sostrades_optimization_plugins/models/func_manager/func_manager.py @@ -173,9 +173,7 @@ def build_aggregated_functions(self, eps=1e-3, alpha=3): self.aggregated_functions[self.OBJECTIVE] += obj_dict[self.VALUE] #-- Inequality constraint aggregation: takes the smooth maximum - ineq_cst_val = [] - for ineq_dict in all_mod_ineq_cst: - ineq_cst_val.append(ineq_dict[self.VALUE]) + ineq_cst_val = [ineq_dict[self.VALUE] for ineq_dict in all_mod_ineq_cst] ineq_cst_val = np.array(ineq_cst_val) if len(ineq_cst_val) > 0: if self.aggr_mod_ineq == 'smooth_max': @@ -188,9 +186,7 @@ def build_aggregated_functions(self, eps=1e-3, alpha=3): self.aggregated_functions[self.INEQ_CONSTRAINT] = 0. #-- Equality constraint aggregation: takes the smooth maximum - eq_cst_val = [] - for eq_dict in all_mod_eq_cst: - eq_cst_val.append(eq_dict[self.VALUE]) + eq_cst_val = [eq_dict[self.VALUE] for eq_dict in all_mod_eq_cst] eq_cst_val = np.array(eq_cst_val) if len(eq_cst_val) > 0: if self.aggr_mod_eq == 'smooth_max': @@ -295,7 +291,7 @@ def cst_func_smooth_maximum(self, values, alpha=3, drop_zeros=False): return smooth_maximum(val, alpha=alpha) def get_mod_func_val(self, tag): - ''' + ''' get modified value ''' return self.mod_functions[tag][self.VALUE] diff --git a/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py b/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py index f3b3149..54a3aa1 100644 --- a/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py +++ b/sostrades_optimization_plugins/models/func_manager/func_manager_disc.py @@ -960,7 +960,7 @@ def get_chart_lagrangian_objective_iterations(self, optim_output, main_parameter name='lagrangian objective'): """ Function to create the post proc of aggregated objectives and constraints - A dropdown menu is used to select between: + A dropdown menu is used to select between: -"Simple" : Simple scatter+line of lagrangian objective -Detailed Contribution" : scatter+line of aggregated (sum*100) lagrangian objective + summed area of individual contributions (*100) Inputs: main_parameters (lagrangian objective) name, list of sub-parameters (aggregated objectives, inequality constraints and @@ -1049,12 +1049,12 @@ def get_chart_aggregated_iterations(self, optim_output, main_parameters, objecti eq_constraints={}, name='aggregated'): """ Function to create the post proc of aggregated objectives and constraints - A dropdown menu is used to select between: + A dropdown menu is used to select between: -"All Aggregated" : Simple scatter+line of all the aggregated values -"Objective - Detailed" : scatter+line of aggregated (sum) objective + summed area of individual contributions - -"Ineq Constraint - Detailed" : scatter+line of aggregated (smax) inequality constraint + area of individual contributions - -"Eq Constraint - Detailed": scatter+line of aggregated (smax) equality constraint + area of individual contributions - Inputs: main_parameters (aggregated) names, list of objectives, inequality constraints and equality constraints names, + -"Ineq Constraint - Detailed" : scatter+line of aggregated (smax) inequality constraint + area of individual contributions + -"Eq Constraint - Detailed": scatter+line of aggregated (smax) equality constraint + area of individual contributions + Inputs: main_parameters (aggregated) names, list of objectives, inequality constraints and equality constraints names, and name of the plot Ouput: instantiated plotly chart """ @@ -1233,7 +1233,7 @@ def get_chart_parameters_mod_iterations(self, optim_output, parameters_df, name) # parents_list = list(set(np.asarray([parent.split( # '-') for parent in parameters_df[self.PARENT].to_list()]).flatten())) - for lvl, parent_level in level_list.items(): + for parent_level in level_list.values(): for parent in parent_level: # if parent isn't in df if parent not in parameters_df[self.VARIABLE]: @@ -1357,7 +1357,7 @@ def get_chart_parameters_mod_iterations(self, optim_output, parameters_df, name) def get_chart_obj_constraints_iterations(self, func_df, optim_output, objectives, name): """ Function to create a summary post proc of the optim problem - In black : the aggregated objective + In black : the aggregated objective In colorscale from green to red : the sum of all the constraints (green == negative values) Additionnal information such as the name and value of the dominant constraint are shown in the hovertext Inputs: objective name, name of the plot and boolean for log scale diff --git a/sostrades_optimization_plugins/sos_processes/__init__.py b/sostrades_optimization_plugins/sos_processes/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/sos_processes/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_processes/test/__init__.py b/sostrades_optimization_plugins/sos_processes/test/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/sos_processes/test/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py index 0815bbd..ae166eb 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_coupling_for_design_var/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py index b119fbf..435dd37 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py index 0815bbd..ae166eb 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_design_var_sub/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py index b119fbf..435dd37 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py index 15afa72..ad80beb 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager/process.py @@ -36,14 +36,14 @@ def get_builders(self): ''' # add disciplines Sellar disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar.' - + mod_func = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' - + mods_dict = {'Sellar_Problem': disc_dir + 'SellarProblem', 'Sellar_2': disc_dir + 'Sellar2', 'Sellar_1': disc_dir + 'Sellar1', 'FunctionManager': mod_func} - + ns_dict = {'ns_functions': self.ee.study_name + '.SellarOptimScenario', 'ns_optim': self.ee.study_name + '.SellarOptimScenario', 'ns_OptimSellar': self.ee.study_name + '.SellarOptimScenario'} @@ -56,5 +56,5 @@ def get_builders(self): opt_builder = self.ee.factory.create_optim_builder( 'SellarOptimScenario', [coupling_builder]) - + return opt_builder diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py index b119fbf..435dd37 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py index 86f8a62..cac6330 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/process.py @@ -38,12 +38,12 @@ def get_builders(self): disc_dir = 'sostrades_core.sos_wrapping.test_discs.sellar.' mod_func = 'sostrades_optimization_plugins.models.func_manager.func_manager_disc.FunctionManagerDisc' - + mods_dict = {'Sellar_Problem': disc_dir + 'SellarProblem', 'Sellar_1': disc_dir + 'Sellar1', 'Sellar_3': disc_dir + 'Sellar3', 'FunctionManager': mod_func} - + ns_dict = {'ns_functions': self.ee.study_name + '.' + 'SellarOptimScenario.SellarCoupling', 'ns_optim': self.ee.study_name + '.' + 'SellarOptimScenario.SellarCoupling', 'ns_OptimSellar': self.ee.study_name + '.SellarOptimScenario.SellarCoupling'} @@ -53,5 +53,5 @@ def get_builders(self): #coupling_builder.set_builder_info('with_data_io', True) opt_builder = self.ee.factory.create_optim_builder( 'SellarOptimScenario', [coupling_builder]) - + return opt_builder diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py index bde48c3..155f2b6 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_opt_w_func_manager_faulty/usecase.py @@ -88,7 +88,7 @@ def setup_usecase(self): prefix = self.study_name + f'.{self.optim_name}.' + f'{self.subcoupling_name}.' + func_mng_name + '.' values_dict = {} - values_dict[prefix + + values_dict[prefix + FunctionManagerDisc.FUNC_DF] = func_df disc_dict.update(values_dict) diff --git a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py index 0815bbd..ae166eb 100644 --- a/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py +++ b/sostrades_optimization_plugins/sos_processes/test/test_sellar_sub_opt_w_design_var/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/sos_wrapping/__init__.py b/sostrades_optimization_plugins/sos_wrapping/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/sos_wrapping/__init__.py +++ b/sostrades_optimization_plugins/sos_wrapping/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/tests/__init__.py b/sostrades_optimization_plugins/tests/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/tests/__init__.py +++ b/sostrades_optimization_plugins/tests/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/tests/l0_test_header.py b/sostrades_optimization_plugins/tests/l0_test_header.py index 8f631e5..bc916c2 100644 --- a/sostrades_optimization_plugins/tests/l0_test_header.py +++ b/sostrades_optimization_plugins/tests/l0_test_header.py @@ -44,7 +44,7 @@ def setUp(self): #commit from where to compare added, modeified deleted ... self.airbus_rev_commit = headers_ignore_config["airbus_rev_commit"] - + def test_Headers(self): ht = HeaderTools() diff --git a/sostrades_optimization_plugins/tools/__init__.py b/sostrades_optimization_plugins/tools/__init__.py index 18d49ea..888f891 100644 --- a/sostrades_optimization_plugins/tools/__init__.py +++ b/sostrades_optimization_plugins/tools/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/tools/cst_manager/__init__.py b/sostrades_optimization_plugins/tools/cst_manager/__init__.py index b119fbf..435dd37 100644 --- a/sostrades_optimization_plugins/tools/cst_manager/__init__.py +++ b/sostrades_optimization_plugins/tools/cst_manager/__init__.py @@ -12,4 +12,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -''' \ No newline at end of file +''' diff --git a/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py b/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py index 7f975c6..fd68e80 100644 --- a/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py +++ b/sostrades_optimization_plugins/tools/cst_manager/constraint_manager.py @@ -84,15 +84,12 @@ def get_constraint(self, name, cst_func=None, *args, **kwargs): """ Get the constraint with its name """ - cst_obj_list = [] name_list = name.split('@') pattern = '(?:@.+)*@'.join(name_list) + '(?:@.+)*' - for k in self.constraints: - if re.match(pattern, k): - cst_obj_list.append(self.constraints[k]) + cst_obj_list = [self.constraints[k] for k in self.constraints if re.match(pattern, k)] if cst_obj_list: # From the list of constraints extract and concatenate list of @@ -361,4 +358,4 @@ def ineq_constraint_demonstrator(): axs[2].axvline(min_valid_x, linestyle=':', color='black') axs[2].axvline(max_valid_x, linestyle=':', color='black') axs[2].legend() - plt.show() \ No newline at end of file + plt.show() diff --git a/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py b/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py index 196a706..ba0e1fb 100644 --- a/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py +++ b/sostrades_optimization_plugins/tools/cst_manager/func_manager_common.py @@ -256,4 +256,4 @@ def get_dcons_smooth_dvariable_vect(cst, alpha=1E16): for i in np.arange(cst_array.shape[0]): grad_value[i][np.logical_not(non_max_idx)[i]] = grad_val_max[i] - return grad_value \ No newline at end of file + return grad_value From 210e6224730ddba421f74f4dd5495bc114ac29a4 Mon Sep 17 00:00:00 2001 From: Carlos Ortega Date: Mon, 15 Jul 2024 12:40:19 +0200 Subject: [PATCH 10/13] update platform version required --- platform_version_required.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform_version_required.txt b/platform_version_required.txt index bbbf720..5469c48 100644 --- a/platform_version_required.txt +++ b/platform_version_required.txt @@ -1 +1 @@ -v4.0.3 \ No newline at end of file +v4.1.0 \ No newline at end of file From efb0b6dead052156e47910e9142c2736da4ef062 Mon Sep 17 00:00:00 2001 From: perrotcap Date: Mon, 15 Jul 2024 21:21:54 +0200 Subject: [PATCH 11/13] improved design var discpline graphs --- .../models/design_var/design_var_disc.py | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/sostrades_optimization_plugins/models/design_var/design_var_disc.py b/sostrades_optimization_plugins/models/design_var/design_var_disc.py index 7b0d51d..d5790b5 100644 --- a/sostrades_optimization_plugins/models/design_var/design_var_disc.py +++ b/sostrades_optimization_plugins/models/design_var/design_var_disc.py @@ -332,7 +332,7 @@ def get_post_processing_list(self, filters=None): def get_chart_BSpline(self, parameter, init_xvect=False): """ - Function to create post-proc for the design variables with display of the control points used to + Function to create post-proc for the design variables with display of the control points used to calculate the B-Splines. The activation/deactivation of control points is accounted for by inserting the values from the design space dataframe into the ctrl_pts if need be (activated_elem==False) and at the appropriate index. @@ -342,15 +342,25 @@ def get_chart_BSpline(self, parameter, init_xvect=False): design_space = self.get_sosdisc_inputs(self.DESIGN_SPACE) pts = self.get_sosdisc_inputs(parameter) - ctrl_pts = list(pts) + ctrl_pts_values = list(pts) + # activated_elems = design_space.loc[design_space['variable'] == parameter, self.LIST_ACTIVATED_ELEM].to_list()[0] + ctrl_pts_lower_bnd = list( + design_space.loc[design_space['variable'] == parameter, self.LOWER_BOUND].to_list()[0]) + ctrl_pts_upper_bnd = list( + design_space.loc[design_space['variable'] == parameter, self.UPPER_BOUND].to_list()[0]) starting_pts = list( - design_space[design_space['variable'] == parameter]['value'].values[0]) + design_space[design_space['variable'] == parameter][self.VALUE].values[0]) for i, activation in enumerate(design_space.loc[design_space['variable'] - == parameter, 'activated_elem'].to_list()[0]): + == parameter, self.LIST_ACTIVATED_ELEM].to_list()[0]): if not activation and len( - design_space.loc[design_space['variable'] == parameter, 'value'].to_list()[0]) > i: - ctrl_pts.insert(i, design_space.loc[design_space['variable'] - == parameter, 'value'].to_list()[0][i]) + design_space.loc[design_space['variable'] == parameter, self.VALUE].to_list()[0]) > i: + ctrl_pts_values.insert(i, + design_space.loc[design_space['variable'] == parameter, self.VALUE].to_list()[0][ + i]) + ctrl_pts_lower_bnd.insert(i, design_space.loc[ + design_space['variable'] == parameter, self.LOWER_BOUND].to_list()[0][i]) + ctrl_pts_upper_bnd.insert(i, design_space.loc[ + design_space['variable'] == parameter, self.UPPER_BOUND].to_list()[0][i]) eval_pts = None design_var_descriptor = self.get_sosdisc_inputs(self.DESIGN_VAR_DESCRIPTOR) @@ -381,30 +391,50 @@ def get_chart_BSpline(self, parameter, init_xvect=False): else: chart_name = f'B-Spline for {parameter}' fig = go.Figure() - if 'complex' in str(type(ctrl_pts[0])): - ctrl_pts = [np.real(value) for value in ctrl_pts] + + if 'complex' in str(type(ctrl_pts_values[0])): + ctrl_pts_values = [np.real(value) for value in ctrl_pts_values] + if 'complex' in str(type(ctrl_pts_lower_bnd[0])): + ctrl_pts_lower_bnd = [np.real(value) for value in ctrl_pts_lower_bnd] + if 'complex' in str(type(ctrl_pts_upper_bnd[0])): + ctrl_pts_upper_bnd = [np.real(value) for value in ctrl_pts_upper_bnd] if 'complex' in str(type(eval_pts[0])): eval_pts = [np.real(value) for value in eval_pts] if 'complex' in str(type(starting_pts[0])): starting_pts = [np.real(value) for value in starting_pts] x_ctrl_pts = np.linspace( - index[0], index[-1], len(ctrl_pts)) - marker_dict = dict(size=150 / len(ctrl_pts), line=dict( - width=150 / (3 * len(ctrl_pts)), color='DarkSlateGrey')) + index[0], index[-1], len(ctrl_pts_values)) + marker_dict_values = dict(size=150 / len(ctrl_pts_values), line=dict( + width=150 / (3 * len(ctrl_pts_values)), color='DarkSlateGrey')) + marker_dict_low_bnd = dict(size=150 / len(ctrl_pts_lower_bnd), line=dict( + width=150 / (2 * len(ctrl_pts_lower_bnd)), color='darkturquoise')) + marker_dict_upper_bnd = dict(size=150 / len(ctrl_pts_upper_bnd), line=dict( + width=150 / (3 * len(ctrl_pts_upper_bnd)), color='salmon')) + fig.add_trace(go.Scatter(x=list(x_ctrl_pts), + y=list(ctrl_pts_lower_bnd), name='lower bounds', + line=dict(color=color_list[0]), + mode='markers', + marker=marker_dict_low_bnd)) fig.add_trace(go.Scatter(x=list(x_ctrl_pts), - y=list(ctrl_pts), name='Poles', + y=list(ctrl_pts_upper_bnd), name='upper bounds', line=dict(color=color_list[0]), mode='markers', - marker=marker_dict)) + marker=marker_dict_upper_bnd)) fig.add_trace(go.Scatter(x=list(index), y=list(eval_pts), name='B-Spline', line=dict(color=color_list[0]), )) + + fig.add_trace(go.Scatter(x=list(x_ctrl_pts), + y=list(ctrl_pts_values), name='Poles', + line=dict(color=color_list[0]), + mode='markers', + marker=marker_dict_values)) if init_xvect: - marker_dict['opacity'] = 0.5 + marker_dict_values['opacity'] = 0.5 fig.add_trace(go.Scatter(x=list(x_ctrl_pts), y=list(starting_pts), name='Initial Poles', mode='markers', line=dict(color=color_list[0]), - marker=marker_dict)) + marker=marker_dict_values)) fig.update_layout(title={'text': chart_name, 'x': 0.5, 'y': 1.0, 'xanchor': 'center', 'yanchor': 'top'}, xaxis_title=index_name, yaxis_title=f'value of {parameter}') new_chart = InstantiatedPlotlyNativeChart( From 920420a2fa4d47678ddc09fe02f6ab9ecf595d61 Mon Sep 17 00:00:00 2001 From: b4pm-devops Date: Tue, 16 Jul 2024 02:25:08 +0000 Subject: [PATCH 12/13] merge post_integration to validation From 14e11e088d16d92e7276da80728f7a14e96c9336 Mon Sep 17 00:00:00 2001 From: b4pm-devops <150457968+b4pm-devops@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:22:38 +0200 Subject: [PATCH 13/13] Update README.md Signed-off-by: b4pm-devops <150457968+b4pm-devops@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2daac5d..e2847ed 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# sostrades-optimization-plugin \ No newline at end of file +# sostrades-optimization-plugin