diff --git a/applications/OptimizationApplication/python_scripts/base_schema.py b/applications/OptimizationApplication/python_scripts/base_schema.py new file mode 100644 index 000000000000..96cd255e0744 --- /dev/null +++ b/applications/OptimizationApplication/python_scripts/base_schema.py @@ -0,0 +1,42 @@ +import pydantic, json +from pydantic import BaseModel, Field +from pydantic import ValidationError +from pydantic.types import Literal +from typing import List, Union + +from KratosMultiphysics.OptimizationApplication.schema.schema_controls import ThicknessControl, Control +from KratosMultiphysics.OptimizationApplication.schema.schema_responses import Response, MassResponse, LinearStrainEnergyResponse +from KratosMultiphysics.OptimizationApplication.schema.schema_analysis import KratosAnalysisBase + + +class ProblemDataSchema(BaseModel): + parallel_type: Literal["OpenMP"] = Field("OpenMP", description="Type of parallelism") + echo_level: int = Field(0, description="Verbosity level") + +class ModelPartsSchema(BaseModel): + class Settings(BaseModel): + model_part_name: str = Field(..., description="Name of the model part") + domain_size: Literal[2, 3] = Field(..., description="Dimension of the model part") + input_filename: str = Field(..., description="Name of the input file") + settings: Settings + +class BaseSchema(BaseModel): + problem_data: ProblemDataSchema = Field(..., description="Problem data") + model_parts: List[ModelPartsSchema] = Field(..., description="List of model parts") + analyses: List[KratosAnalysisBase] = Field(..., description="List of responses") + responses: List[Union[Response, MassResponse, LinearStrainEnergyResponse]] = Field(..., description="List of responses") + controls: List[Union[ThicknessControl, Control]] = Field(..., description="List of controls") + algorithm_settings: dict = Field(..., description="Algorithm settings") + processes: dict = Field(..., description="List of processes") + +def ValidateJSON(data): + valid = False + try: + valid_data = BaseSchema(**data) + valid = True + except ValidationError as e: + print(e) + + return valid, valid_data + + \ No newline at end of file diff --git a/applications/OptimizationApplication/python_scripts/schema/optimization_application_schema.json b/applications/OptimizationApplication/python_scripts/schema/optimization_application_schema.json new file mode 100644 index 000000000000..c642cd2bca5d --- /dev/null +++ b/applications/OptimizationApplication/python_scripts/schema/optimization_application_schema.json @@ -0,0 +1,518 @@ +{ + "$defs": { + "AnalysisOutputSettings": { + "properties": { + "nodal_solution_step_data_variables": { + "description": "List of nodal solution step data variables", + "items": { + "type": "string" + }, + "title": "Nodal Solution Step Data Variables", + "type": "array" + }, + "nodal_data_value_variables": { + "description": "List of nodal data value variables", + "items": { + "type": "string" + }, + "title": "Nodal Data Value Variables", + "type": "array" + }, + "element_data_value_variables": { + "description": "List of element data value variables", + "items": { + "type": "string" + }, + "title": "Element Data Value Variables", + "type": "array" + }, + "condition_data_value_variables": { + "description": "List of condition data value variables", + "items": { + "type": "string" + }, + "title": "Condition Data Value Variables", + "type": "array" + } + }, + "title": "AnalysisOutputSettings", + "type": "object" + }, + "Control": { + "properties": { + "name": { + "description": "Name of the control", + "title": "Name", + "type": "string" + }, + "type": { + "description": "Type of the control", + "title": "Type", + "type": "string" + }, + "settings": { + "title": "Settings", + "type": "object" + } + }, + "required": [ + "name", + "type", + "settings" + ], + "title": "Control", + "type": "object" + }, + "FilterSettings": { + "properties": { + "filter_type": { + "description": "Type of filter", + "title": "Filter Type", + "type": "string" + }, + "filter_radius": { + "description": "Radius of the filter", + "title": "Filter Radius", + "type": "number" + } + }, + "required": [ + "filter_type", + "filter_radius" + ], + "title": "FilterSettings", + "type": "object" + }, + "KratosAnalysisBase": { + "properties": { + "name": { + "description": "Name of the analysis", + "title": "Name", + "type": "string" + }, + "type": { + "const": "kratos_analysis_execution_policy", + "description": "Type of the analysis", + "title": "Type", + "type": "string" + }, + "settings": { + "$ref": "#/$defs/KratosAnalysisSettings" + } + }, + "required": [ + "name", + "type", + "settings" + ], + "title": "KratosAnalysisBase", + "type": "object" + }, + "KratosAnalysisSettings": { + "properties": { + "model_part_names": { + "description": "List of model part names", + "items": { + "type": "string" + }, + "title": "Model Part Names", + "type": "array" + }, + "analysis_module": { + "description": "Analysis module", + "title": "Analysis Module", + "type": "string" + }, + "analysis_type": { + "description": "Analysis type", + "title": "Analysis Type", + "type": "string" + }, + "analysis_settings": { + "description": "Analysis settings", + "title": "Analysis Settings", + "type": "object" + }, + "analysis_output_settings": { + "$ref": "#/$defs/AnalysisOutputSettings", + "description": "Analysis output settings" + } + }, + "required": [ + "model_part_names", + "analysis_module", + "analysis_type", + "analysis_settings" + ], + "title": "KratosAnalysisSettings", + "type": "object" + }, + "LinearStrainEnergyResponse": { + "properties": { + "name": { + "description": "Name of the response", + "title": "Name", + "type": "string" + }, + "type": { + "const": "linear_strain_energy_response_function", + "default": "linear_strain_energy_response_function", + "description": "Type of the response", + "title": "Type", + "type": "string" + }, + "settings": { + "$ref": "#/$defs/LinearStrainEnergyResponseSettings" + } + }, + "required": [ + "name", + "settings" + ], + "title": "LinearStrainEnergyResponse", + "type": "object" + }, + "LinearStrainEnergyResponseSettings": { + "properties": { + "evaluated_model_part_names": { + "description": "List of model part names to evaluate", + "items": { + "type": "string" + }, + "title": "Evaluated Model Part Names", + "type": "array" + }, + "primal_analysis_name": { + "description": "Name of the primal analysis", + "title": "Primal Analysis Name", + "type": "string" + }, + "perturbation_size": { + "description": "Perturbation size for the response", + "title": "Perturbation Size", + "type": "number" + } + }, + "required": [ + "evaluated_model_part_names", + "primal_analysis_name", + "perturbation_size" + ], + "title": "LinearStrainEnergyResponseSettings", + "type": "object" + }, + "MassResponse": { + "properties": { + "name": { + "description": "Name of the response", + "title": "Name", + "type": "string" + }, + "type": { + "const": "mass_response_function", + "default": "mass_response_function", + "description": "Type of the response", + "title": "Type", + "type": "string" + }, + "settings": { + "$ref": "#/$defs/MassResponseSettings" + } + }, + "required": [ + "name", + "settings" + ], + "title": "MassResponse", + "type": "object" + }, + "MassResponseSettings": { + "properties": { + "evaluated_model_part_names": { + "description": "List of model part names to evaluate", + "items": { + "type": "string" + }, + "title": "Evaluated Model Part Names", + "type": "array" + } + }, + "required": [ + "evaluated_model_part_names" + ], + "title": "MassResponseSettings", + "type": "object" + }, + "ModelPartsSchema": { + "properties": { + "settings": { + "$ref": "#/$defs/Settings" + } + }, + "required": [ + "settings" + ], + "title": "ModelPartsSchema", + "type": "object" + }, + "ProblemDataSchema": { + "properties": { + "parallel_type": { + "const": "OpenMP", + "default": "OpenMP", + "description": "Type of parallelism", + "title": "Parallel Type", + "type": "string" + }, + "echo_level": { + "default": 0, + "description": "Verbosity level", + "title": "Echo Level", + "type": "integer" + } + }, + "title": "ProblemDataSchema", + "type": "object" + }, + "ProjectionSettings": { + "properties": { + "type": { + "description": "Type of projection", + "title": "Type", + "type": "string" + }, + "initial_value": { + "description": "Initial value for projection", + "title": "Initial Value", + "type": "number" + }, + "max_value": { + "description": "Maximum value for projection", + "title": "Max Value", + "type": "number" + }, + "increase_fac": { + "description": "Increase factor for projection", + "title": "Increase Fac", + "type": "number" + }, + "update_period": { + "description": "Update period for projection", + "title": "Update Period", + "type": "integer" + }, + "penalty_factor": { + "description": "Penalty factor for projection", + "title": "Penalty Factor", + "type": "number" + } + }, + "required": [ + "type", + "initial_value", + "max_value", + "increase_fac", + "update_period", + "penalty_factor" + ], + "title": "ProjectionSettings", + "type": "object" + }, + "Response": { + "properties": { + "name": { + "description": "Name of the response", + "title": "Name", + "type": "string" + }, + "type": { + "description": "Type of the response", + "title": "Type", + "type": "string" + }, + "settings": { + "title": "Settings", + "type": "object" + } + }, + "required": [ + "name", + "type", + "settings" + ], + "title": "Response", + "type": "object" + }, + "Settings": { + "properties": { + "model_part_name": { + "description": "Name of the model part", + "title": "Model Part Name", + "type": "string" + }, + "domain_size": { + "description": "Dimension of the model part", + "enum": [ + 2, + 3 + ], + "title": "Domain Size", + "type": "integer" + }, + "input_filename": { + "description": "Name of the input file", + "title": "Input Filename", + "type": "string" + } + }, + "required": [ + "model_part_name", + "domain_size", + "input_filename" + ], + "title": "Settings", + "type": "object" + }, + "ThicknessControl": { + "properties": { + "name": { + "description": "Name of the control", + "title": "Name", + "type": "string" + }, + "type": { + "description": "Type of the control", + "title": "Type", + "type": "string" + }, + "settings": { + "$ref": "#/$defs/ThicknessControlSettings" + } + }, + "required": [ + "name", + "type", + "settings" + ], + "title": "ThicknessControl", + "type": "object" + }, + "ThicknessControlSettings": { + "properties": { + "controlled_model_part_names": { + "description": "List of model part names to control", + "items": { + "type": "string" + }, + "title": "Controlled Model Part Names", + "type": "array" + }, + "filter_settings": { + "$ref": "#/$defs/FilterSettings", + "description": "Filter settings" + }, + "output_all_fields": { + "description": "Flag to output all fields", + "title": "Output All Fields", + "type": "boolean" + }, + "physical_thicknesses": { + "description": "List of physical thicknesses", + "items": { + "type": "number" + }, + "title": "Physical Thicknesses", + "type": "array" + }, + "thickness_projection_settings": { + "$ref": "#/$defs/ProjectionSettings" + } + }, + "required": [ + "controlled_model_part_names", + "filter_settings", + "output_all_fields", + "physical_thicknesses", + "thickness_projection_settings" + ], + "title": "ThicknessControlSettings", + "type": "object" + } + }, + "properties": { + "problem_data": { + "$ref": "#/$defs/ProblemDataSchema", + "description": "Problem data" + }, + "model_parts": { + "description": "List of model parts", + "items": { + "$ref": "#/$defs/ModelPartsSchema" + }, + "title": "Model Parts", + "type": "array" + }, + "analyses": { + "description": "List of responses", + "items": { + "$ref": "#/$defs/KratosAnalysisBase" + }, + "title": "Analyses", + "type": "array" + }, + "responses": { + "description": "List of responses", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/Response" + }, + { + "$ref": "#/$defs/MassResponse" + }, + { + "$ref": "#/$defs/LinearStrainEnergyResponse" + } + ] + }, + "title": "Responses", + "type": "array" + }, + "controls": { + "description": "List of controls", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/ThicknessControl" + }, + { + "$ref": "#/$defs/Control" + } + ] + }, + "title": "Controls", + "type": "array" + }, + "algorithm_settings": { + "description": "Algorithm settings", + "title": "Algorithm Settings", + "type": "object" + }, + "processes": { + "description": "List of processes", + "title": "Processes", + "type": "object" + } + }, + "required": [ + "problem_data", + "model_parts", + "analyses", + "responses", + "controls", + "algorithm_settings", + "processes" + ], + "title": "BaseSchema", + "type": "object" +} \ No newline at end of file diff --git a/applications/OptimizationApplication/python_scripts/schema/schema_algorithms.py b/applications/OptimizationApplication/python_scripts/schema/schema_algorithms.py new file mode 100644 index 000000000000..62f10a378afc --- /dev/null +++ b/applications/OptimizationApplication/python_scripts/schema/schema_algorithms.py @@ -0,0 +1,40 @@ + + + + + + + +# "algorithm_settings": { +# "type": "algorithm_relaxed_gradient_projection", +# "settings": { +# "echo_level": 0, +# "line_search": { +# "type": "const_step", +# "init_step": 1e-2, +# "gradient_scaling": "inf_norm" +# }, +# "conv_settings": { +# "type": "max_iter", +# "max_iter": 10 +# }, +# "linear_solver_settings": { +# "solver_type": "LinearSolversApplication.dense_col_piv_householder_qr" +# } +# }, +# "controls": [ +# "thickness_control" +# ], +# "objective": { +# "response_name": "mass", +# "type": "minimization", +# "scaling": 1.0 +# }, +# "constraints": [ +# { +# "response_name": "strain_energy", +# "type": "<=", +# "scaled_ref_value": "initial_value" +# } +# ] +# } \ No newline at end of file diff --git a/applications/OptimizationApplication/python_scripts/schema/schema_analysis.py b/applications/OptimizationApplication/python_scripts/schema/schema_analysis.py new file mode 100644 index 000000000000..41f8d806458e --- /dev/null +++ b/applications/OptimizationApplication/python_scripts/schema/schema_analysis.py @@ -0,0 +1,18 @@ +from pydantic import BaseModel, Field +from pydantic.types import Literal + +class KratosAnalysisBase(BaseModel): + name: str = Field(..., description="Name of the analysis") + type: Literal["kratos_analysis_execution_policy"] = Field(..., description="Type of the analysis") + class KratosAnalysisSettings(BaseModel): + model_part_names: list[str] = Field(..., description="List of model part names") + analysis_module: str = Field(..., description="Analysis module") + analysis_type: str = Field(..., description="Analysis type") + analysis_settings: dict = Field(..., description="Analysis settings") + class AnalysisOutputSettings(BaseModel): + nodal_solution_step_data_variables: list[str] = Field(list, description="List of nodal solution step data variables") + nodal_data_value_variables: list[str] = Field(list, description="List of nodal data value variables") + element_data_value_variables: list[str] = Field(list, description="List of element data value variables") + condition_data_value_variables: list[str] = Field(list, description="List of condition data value variables") + analysis_output_settings: AnalysisOutputSettings = Field(dict, description="Analysis output settings") + settings: KratosAnalysisSettings diff --git a/applications/OptimizationApplication/python_scripts/schema/schema_controls.py b/applications/OptimizationApplication/python_scripts/schema/schema_controls.py new file mode 100644 index 000000000000..7c616524a2d1 --- /dev/null +++ b/applications/OptimizationApplication/python_scripts/schema/schema_controls.py @@ -0,0 +1,29 @@ +from pydantic import BaseModel, Field + +class FilterSettings(BaseModel): + filter_type: str = Field(..., description="Type of filter") + filter_radius: float = Field(..., description="Radius of the filter") + +class Control(BaseModel): + name: str = Field(..., description="Name of the control") + type: str = Field(..., description="Type of the control") + settings: dict + +class ThicknessControlSettings(BaseModel): + controlled_model_part_names: list[str] = Field(..., description="List of model part names to control") + filter_settings: FilterSettings = Field(..., description="Filter settings") + output_all_fields: bool = Field(..., description="Flag to output all fields") + physical_thicknesses: list[float] = Field(..., description="List of physical thicknesses") + class ProjectionSettings(BaseModel): + type: str = Field(..., description="Type of projection") + initial_value: float = Field(..., description="Initial value for projection") + max_value: float = Field(..., description="Maximum value for projection") + increase_fac: float = Field(..., description="Increase factor for projection") + update_period: int = Field(..., description="Update period for projection") + penalty_factor: float = Field(..., description="Penalty factor for projection") + thickness_projection_settings: ProjectionSettings + +class ThicknessControl(Control): + settings: ThicknessControlSettings + + diff --git a/applications/OptimizationApplication/python_scripts/schema/schema_responses.py b/applications/OptimizationApplication/python_scripts/schema/schema_responses.py new file mode 100644 index 000000000000..0b02577f0a5b --- /dev/null +++ b/applications/OptimizationApplication/python_scripts/schema/schema_responses.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel, Field +from pydantic.types import PositiveInt, Literal + + +class MassResponseSettings(BaseModel): + evaluated_model_part_names: list[str] = Field(..., description="List of model part names to evaluate") + +class LinearStrainEnergyResponseSettings(BaseModel): + evaluated_model_part_names: list[str] = Field(..., description="List of model part names to evaluate") + primal_analysis_name: str = Field(..., description="Name of the primal analysis") + perturbation_size: float = Field(..., description="Perturbation size for the response") + +class Response(BaseModel): + name: str = Field(..., description="Name of the response") + type: str = Field(..., description="Type of the response") + settings: dict + + +class MassResponse(Response): + type: Literal["mass_response_function"] = Field("mass_response_function", description="Type of the response") + settings: MassResponseSettings + +class LinearStrainEnergyResponse(Response): + type: Literal["linear_strain_energy_response_function"] = Field("linear_strain_energy_response_function", description="Type of the response") + settings: LinearStrainEnergyResponseSettings diff --git a/applications/OptimizationApplication/tests/test_optimization_schema.py b/applications/OptimizationApplication/tests/test_optimization_schema.py new file mode 100644 index 000000000000..9dc22cc0e471 --- /dev/null +++ b/applications/OptimizationApplication/tests/test_optimization_schema.py @@ -0,0 +1,55 @@ +import KratosMultiphysics as Kratos +import KratosMultiphysics.KratosUnittest as KratosUnittest +import json +from KratosMultiphysics.OptimizationApplication.base_schema import ValidateJSON, BaseSchema + +class TestOptimizationSchema(KratosUnittest.TestCase): + def test_0(self): + json_schema = BaseSchema.model_json_schema() + with open('../python_scripts/schema/optimization_application_schema.json', 'w') as file: + json.dump(json_schema, file, indent=4) # Convert dictionary to JSON string and write to file + + def test_1(self): + with open("algorithm_tests/analysis_based_tests/algorithm_gradient_projection/optimization_parameters.json", "r") as file_input: + json_data = json.load(file_input) + valid, validate_data = ValidateJSON(json_data) + self.assertTrue(valid) + + def test_2(self): + with open("algorithm_tests/analysis_based_tests/algorithm_nesterov_accelerated_gradient/optimization_parameters.json", "r") as file_input: + json_data = json.load(file_input) + valid, validate_data = ValidateJSON(json_data) + self.assertTrue(valid) + + def test_3(self): + with open("algorithm_tests/analysis_based_tests/algorithm_relaxed_gradient_projection/optimization_parameters.json", "r") as file_input: + json_data = json.load(file_input) + valid, validate_data = ValidateJSON(json_data) + self.assertTrue(valid) + + def test_3(self): + with open("algorithm_tests/analysis_based_tests/algorithm_relaxed_gradient_projection/optimization_parameters.json", "r") as file_input: + json_data = json.load(file_input) + valid, validate_data = ValidateJSON(json_data) + self.assertTrue(valid) + + def test_4(self): + with open("algorithm_tests/analysis_based_tests/algorithm_steepest_descent/optimization_parameters.json", "r") as file_input: + json_data = json.load(file_input) + valid, validate_data = ValidateJSON(json_data) + self.assertTrue(valid) + + def test_5(self): + with open("algorithm_tests/analysis_based_tests/algorithm_steepest_descent_qnbb/optimization_parameters.json", "r") as file_input: + json_data = json.load(file_input) + valid, validate_data = ValidateJSON(json_data) + self.assertTrue(valid) + + def test_6(self): + with open("algorithm_tests/nlopt_tests/mma_shell_thickness_opt/optimization_parameters.json", "r") as file_input: + json_data = json.load(file_input) + valid, validate_data = ValidateJSON(json_data) + self.assertTrue(valid) + +if __name__ == '__main__': + KratosUnittest.main() \ No newline at end of file