From 52448418d571356ea2732be7f7c1b413b8312f8e Mon Sep 17 00:00:00 2001 From: PanekOndrej <108389964+PanekOndrej@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:17:32 +0200 Subject: [PATCH] Feat/proxy solver node (#328) Co-authored-by: Ondrej Panek (External) --- doc/source/_static/03_ProxySolver.png | Bin 0 -> 10802 bytes doc/source/api/derived_node_classes.rst | 1 + examples/workflow_creation/03_proxy_solver.py | 213 ++++++++++++++++++ src/ansys/optislang/core/node_types.py | 3 + src/ansys/optislang/core/nodes.py | 63 +++++- src/ansys/optislang/core/tcp/nodes.py | 87 ++++++- src/ansys/optislang/core/tcp/osl_server.py | 67 +++++- .../optislang/core/tcp/server_commands.py | 34 ++- .../optislang/core/tcp/server_queries.py | 19 ++ tests/tcp/test_tcp_server_commands.py | 85 ++++--- tests/tcp/test_tcp_server_queries.py | 18 ++ tests/test_nodes.py | 1 + 12 files changed, 557 insertions(+), 34 deletions(-) create mode 100644 doc/source/_static/03_ProxySolver.png create mode 100644 examples/workflow_creation/03_proxy_solver.py diff --git a/doc/source/_static/03_ProxySolver.png b/doc/source/_static/03_ProxySolver.png new file mode 100644 index 0000000000000000000000000000000000000000..1fd67c284babd3b4380e80b51cd455c000fce209 GIT binary patch literal 10802 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 literal 0 HcmV?d00001 diff --git a/doc/source/api/derived_node_classes.rst b/doc/source/api/derived_node_classes.rst index e8d71197d..7d9fa3189 100644 --- a/doc/source/api/derived_node_classes.rst +++ b/doc/source/api/derived_node_classes.rst @@ -6,3 +6,4 @@ Derived node classes :toctree: _autosummary IntegrationNode + ProxySolverNode diff --git a/examples/workflow_creation/03_proxy_solver.py b/examples/workflow_creation/03_proxy_solver.py new file mode 100644 index 000000000..4c4b51ff6 --- /dev/null +++ b/examples/workflow_creation/03_proxy_solver.py @@ -0,0 +1,213 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_proxy_solver: + +Proxy solver +------------ + +This example demonstrates how to obtain designs from parametric system and process them externally. + +It creates a proxy solver node inside parametric system and solves it's designs externally. +""" + +######################################################### +# Perform required imports +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Perform the required imports. +import time + +from ansys.optislang.core import Optislang +import ansys.optislang.core.node_types as node_types +from ansys.optislang.core.nodes import DesignFlow, ParametricSystem, ProxySolverNode +from ansys.optislang.core.project_parametric import ( + ComparisonType, + ObjectiveCriterion, + OptimizationParameter, +) +from ansys.optislang.core.utils import find_all_osl_exec + +######################################################### +# Create solver +# ~~~~~~~~~~~~~ +# Define a simple calculator function to solve the variations + + +def calculator(hid, X1, X2, X3, X4, X5): + from math import sin + + Y = 0.5 * X1 + X2 + 0.5 * X1 * X2 + 5 * sin(X3) + 0.2 * X4 + 0.1 * X5 + return Y + + +def calculate(designs): + result_design_list = [] + print(f"Calculate {len(designs)} designs") + for design in designs: + hid = design["hid"] + parameters = design["parameters"] + X1 = 0.0 + X2 = 0.0 + X3 = 0.0 + X4 = 0.0 + X5 = 0.0 + for parameter in parameters: + if parameter["name"] == "X1": + X1 = parameter["value"] + elif parameter["name"] == "X2": + X2 = parameter["value"] + elif parameter["name"] == "X3": + X3 = parameter["value"] + elif parameter["name"] == "X4": + X4 = parameter["value"] + elif parameter["name"] == "X5": + X5 = parameter["value"] + Y = calculator(hid, X1, X2, X3, X4, X5) + + result_design = {} + result_design["hid"] = hid + responses = [{"name": "Y", "value": Y}] + result_design["responses"] = responses + result_design_list.append(result_design) + + print(f"Return {len(result_design_list)} designs") + return result_design_list + + +######################################################### +# Create optiSLang instance +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Find the optiSLang >= 25.1 executable. Initialize the Optislang class instance with the executable. + +available_optislang_executables = find_all_osl_exec() +version, executables = available_optislang_executables.popitem(last=False) +if not version >= 251: + raise KeyError("OptiSLang installation >= 25R1 wasn't found, please specify path manually.") + +osl = Optislang(executable=executables[0]) + +print(f"Using optiSLang version {osl.osl_version_string}") + + +######################################################### +# Create workflow +# ~~~~~~~~~~~~~~~ + +root_system = osl.application.project.root_system + +# Create the algorithm system of your choice. + +algorithm_system: ParametricSystem = root_system.create_node( + type_=node_types.Sensitivity, name="Sensitivity" +) + +num_discretization = 2000 + +algorithm_settings = algorithm_system.get_property("AlgorithmSettings") +algorithm_settings["num_discretization"] = num_discretization +algorithm_system.set_property("AlgorithmSettings", algorithm_settings) + +# Fast running solver settings + +algorithm_system.set_property("AutoSaveMode", "no_auto_save") +algorithm_system.set_property("SolveTwice", True) +algorithm_system.set_property("UpdateResultFile", "never") +algorithm_system.set_property("WriteDesignStartSetFlag", False) + +# Add the Proxy Solver node and set the desired maximum number of designs you handle in one go. + +proxy_solver: ProxySolverNode = algorithm_system.create_node( + type_=node_types.ProxySolver, name="Calculator", design_flow=DesignFlow.RECEIVE_SEND +) + +multi_design_launch_num = 99 # set -1 to solve all designs simultaneously +proxy_solver.set_property("MultiDesignLaunchNum", multi_design_launch_num) +proxy_solver.set_property("ForwardHPCLicenseContextEnvironment", True) + +# Add parameters to the algorithm system and register them in the proxy solver. + +for i in range(1, 6): + parameter = OptimizationParameter(name=f"X{i}", reference_value=1.0, range=(-3.14, 3.14)) + algorithm_system.parameter_manager.add_parameter(parameter) + proxy_solver.register_location_as_parameter( + {"dir": {"value": "input"}, "name": parameter.name, "value": parameter.reference_value} + ) + +# Register response in the proxy solver and create criterion in algorithm + +proxy_solver.register_location_as_response({"dir": {"value": "output"}, "name": "Y", "value": 3.0}) +criterion = ObjectiveCriterion( + name="obj", expression="Y", expression_value=3.0, criterion=ComparisonType.MIN +) +algorithm_system.criteria_manager.add_criterion(criterion) + + +######################################################### +# Optionally save project +# ~~~~~~~~~~~~~~~~~~~~~~~ +# If you want to save the project to some desired location, +# uncomment and edit these lines: +# +# .. code:: python +# +# dir_path = Path(r"") +# project_name = "proxy_solver_workflow.opf" +# osl.application.save_as(dir_path / project_name) + + +######################################################### +# Run workflow +# ~~~~~~~~~~~~ +# Run the workflow created by the preceding scripts. + +# Start the optiSLang project execution. +osl.application.project.start(wait_for_finished=False) + + +# Now loop until get_status() returns "Processing done" for the root system. Use the GET_DESIGNS query and the SET_DESIGNS command for the Proxy Solver node to get designs and set responses until the system is done. + +while not osl.project.root_system.get_status() == "Processing done": + design_list = proxy_solver.get_designs() + if len(design_list): + responses_dict = calculate(design_list) + proxy_solver.set_designs(responses_dict) + time.sleep(0.1) + +print("Solved Successfully!") + + +######################################################### +# Stop and cancel project +# ~~~~~~~~~~~~~~~~~~~~~~~ +# Stop and cancel the project. +osl.dispose() + +######################################################### +# View generated workflow +# ~~~~~~~~~~~~~~~~~~~~~~~ +# This image shows the generated workflow. +# +# .. image:: ../../_static/03_ProxySolver.png +# :width: 400 +# :alt: Result of script. +# diff --git a/src/ansys/optislang/core/node_types.py b/src/ansys/optislang/core/node_types.py index e6dc86366..30e101d05 100644 --- a/src/ansys/optislang/core/node_types.py +++ b/src/ansys/optislang/core/node_types.py @@ -291,6 +291,9 @@ def subtype(self) -> AddinType: ProEProcess = NodeType( id="ProEProcess", subtype=AddinType.BUILT_IN, osl_class_type=NodeClassType.INTEGRATION_NODE ) +ProxySolver = NodeType( + id="ProxySolver", subtype=AddinType.BUILT_IN, osl_class_type=NodeClassType.PROXY_SOLVER +) Python2 = NodeType( id="Python2", subtype=AddinType.BUILT_IN, osl_class_type=NodeClassType.INTEGRATION_NODE ) diff --git a/src/ansys/optislang/core/nodes.py b/src/ansys/optislang/core/nodes.py index f31c85a88..c44758b59 100644 --- a/src/ansys/optislang/core/nodes.py +++ b/src/ansys/optislang/core/nodes.py @@ -95,6 +95,7 @@ class NodeClassType(Enum): PARAMETRIC_SYSTEM = 2 ROOT_SYSTEM = 3 INTEGRATION_NODE = 4 + PROXY_SOLVER = 5 @classmethod def from_str(cls, string: str) -> NodeClassType: @@ -796,12 +797,17 @@ def get_registered_responses( pass @abstractmethod - def load(self) -> None: # pragma: no cover + def load(self, args: Optional[dict] = None) -> None: # pragma: no cover """Explicitly load the node. Some optiSLang nodes support/need an explicit load prior to being able to register or to make registering more convenient. + Parameters + ---------- + args: Optional[dict], optional + Additional arguments, by default ``None``. + Raises ------ OslCommunicationError @@ -1044,6 +1050,55 @@ def re_register_locations_as_response(self) -> None: # pragma: no cover pass +class ProxySolverNode(IntegrationNode): + """Base class for classes which provide for creating and operating on an proxy solver node.""" + + @abstractmethod + def __init__(self): # pragma: no cover + """``ProxySolverNode`` class is an abstract base class and cannot be instantiated.""" + pass + + @abstractmethod + def get_designs(self) -> Any: # pragma: no cover + """Get pending designs from parent node. + + Returns + ------- + Any + Pending designs. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with the server. + OslCommandError + Raised when a command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + pass + + @abstractmethod + def set_designs(self, designs: Any) -> None: # pragma: no cover + """Set calculated designs. + + Parameters + ---------- + Any + Calculated designs. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with the server. + OslCommandError + Raised when a command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + pass + + class System(Node): """Base class for classes which provide for creating and operating on a system.""" @@ -1077,6 +1132,12 @@ def create_node( Raises ------ + OslCommunicationError + Raised when an error occurs while communicating with the server. + OslCommandError + Raised when a command or query fails. + TimeoutError + Raised when the timeout float value expires. TypeError Raised when unsupported type of ``type_`` is given. ValueError diff --git a/src/ansys/optislang/core/tcp/nodes.py b/src/ansys/optislang/core/tcp/nodes.py index 2c4587ec7..6eb0d8a27 100644 --- a/src/ansys/optislang/core/tcp/nodes.py +++ b/src/ansys/optislang/core/tcp/nodes.py @@ -50,6 +50,7 @@ NodeClassType, OutputSlot, ParametricSystem, + ProxySolverNode, RootSystem, Slot, SlotType, @@ -1095,7 +1096,7 @@ def __init__( type_: NodeType, logger=None, ) -> None: - """Create an ``TcpSystemProxy`` instance. + """Create an ``TcpIntegrationNodeProxy`` instance. Parameters ---------- @@ -1295,12 +1296,17 @@ def get_registered_responses(self, include_reference_values: bool = True) -> Tup ) ) - def load(self) -> None: + def load(self, args: Optional[Dict[str, Any]] = None) -> None: """Explicitly load the node. Some optiSLang nodes support/need an explicit LOAD prior to being able to register or to make registering more convenient. + Parameters + ---------- + args: Optional[Dict[str, any]], optional + Additional arguments, by default ``None``. + Raises ------ OslCommunicationError @@ -1311,7 +1317,7 @@ def load(self) -> None: Raised when the timeout float value expires. """ # TODO: test - self._osl_server.load(self.uid) + self._osl_server.load(uid=self.uid, args=args) def register_location_as_input_slot( self, @@ -1549,6 +1555,80 @@ def re_register_locations_as_response(self) -> None: self._osl_server.re_register_locations_as_response(uid=self.uid) +class TcpProxySolverNodeProxy(TcpIntegrationNodeProxy, ProxySolverNode): + """Provides for creating and operating on integration nodes.""" + + def __init__( + self, + uid: str, + osl_server: TcpOslServer, + type_: NodeType, + logger=None, + ) -> None: + """Create an ``TcpProxySolverProxy`` instance. + + Parameters + ---------- + uid: str + Unique ID. + osl_server: TcpOslServer + Object providing access to the optiSLang server. + type_: NodeType + Instance of the ``NodeType`` class. + logger: Any, optional + Object for logging. If ``None``, standard logging object is used. Defaults to ``None``. + """ + super().__init__( + uid=uid, + osl_server=osl_server, + type_=type_, + logger=logger, + ) + + def get_designs(self) -> List[dict]: + """Get pending designs from parent node. + + Returns + ------- + List[dict] + List of pending designs. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with the server. + OslCommandError + Raised when a command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + return self._osl_server.get_designs(self.uid) + + def set_designs(self, designs: Iterable[dict]) -> None: + """Set calculated designs. + + Parameters + ---------- + Iterable[dict] + Iterable of calculated designs. + Design format: + { + "hid": "0.1", + "responses": [{"name": "res1", "value": 1.0}, {...}, ...], + } + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with the server. + OslCommandError + Raised when a command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + self._osl_server.set_designs(actor_uid=self.uid, designs=designs) + + # endregion @@ -3228,6 +3308,7 @@ def connect_to(self, to_slot: TcpSlotProxy) -> Edge: NodeClassType.INTEGRATION_NODE.name: TcpIntegrationNodeProxy, NodeClassType.SYSTEM.name: TcpSystemProxy, NodeClassType.PARAMETRIC_SYSTEM.name: TcpParametricSystemProxy, + NodeClassType.PROXY_SOLVER.name: TcpProxySolverNodeProxy, NodeClassType.ROOT_SYSTEM.name: TcpRootSystemProxy, } diff --git a/src/ansys/optislang/core/tcp/osl_server.py b/src/ansys/optislang/core/tcp/osl_server.py index 1633e6530..2deb3a861 100644 --- a/src/ansys/optislang/core/tcp/osl_server.py +++ b/src/ansys/optislang/core/tcp/osl_server.py @@ -2188,6 +2188,38 @@ def get_criterion(self, uid: str, name: str) -> Dict: max_request_attempts=self.max_request_attempts_register.get_value(current_func_name), )["criteria"] + def get_designs(self, uid: str) -> List[dict]: + """Get pending designs from parent node. + + Parameters + ---------- + uid : str + Actor uid. + + Returns + ------- + List[dict] + List of pending designs. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with server. + OslCommandError + Raised when the command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + current_func_name = self.get_designs.__name__ + return self.send_command( + command=queries.get_designs( + uid=uid, + password=self.__password, + ), + timeout=self.timeouts_register.get_value(current_func_name), + max_request_attempts=self.max_request_attempts_register.get_value(current_func_name), + )["designs"] + def get_doe_size(self, uid: str, sampling_type: str, num_discrete_levels: int) -> int: """Get the DOE size for given sampling type and number of levels for a specific actor. @@ -2212,7 +2244,7 @@ def get_doe_size(self, uid: str, sampling_type: str, num_discrete_levels: int) - TimeoutError Raised when the timeout float value expires. """ - current_func_name = self.add_criterion.__name__ + current_func_name = self.get_doe_size.__name__ return self.send_command( command=queries.doe_size( uid=uid, @@ -2945,13 +2977,15 @@ def get_working_dir(self) -> Path: return None return Path(project_info.get("projects", [{}])[0].get("working_dir", None)) - def load(self, uid: str) -> None: + def load(self, uid: str, args: Optional[Dict[str, Any]] = None) -> None: """Explicit load of node. Parameters ---------- uid: str Actor uid. + args: Optional[Dict[str, any]], optional + Additional arguments, by default ``None``. Raises ------ @@ -2967,6 +3001,7 @@ def load(self, uid: str) -> None: self.send_command( command=commands.load( actor_uid=uid, + args=args, password=self.__password, ), timeout=self.timeouts_register.get_value(current_func_name), @@ -3851,6 +3886,34 @@ def set_criterion_property( max_request_attempts=self.max_request_attempts_register.get_value(current_func_name), ) + def set_designs(self, actor_uid: str, designs: Iterable[dict]) -> None: + """Set an actor property. + + Parameters + ---------- + actor_uid : str + Actor uid. + designs : Iterable[dict] + Iterable of calculated designs. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with server. + OslCommandError + Raised when the command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + current_func_name = self.set_designs.__name__ + self.send_command( + command=commands.set_designs( + actor_uid=actor_uid, designs=designs, password=self.__password + ), + timeout=self.timeouts_register.get_value(current_func_name), + max_request_attempts=self.max_request_attempts_register.get_value(current_func_name), + ) + @deprecated( version="0.5.0", reason="Use :py:attr:`TcpOslServer.timeouts_register.default_value` instead.", diff --git a/src/ansys/optislang/core/tcp/server_commands.py b/src/ansys/optislang/core/tcp/server_commands.py index 96d3b3c50..e76175e48 100644 --- a/src/ansys/optislang/core/tcp/server_commands.py +++ b/src/ansys/optislang/core/tcp/server_commands.py @@ -71,6 +71,7 @@ _SET_ACTOR_SETTING = "SET_ACTOR_SETTING" _SET_ACTOR_STATE_PROPERTY = "SET_ACTOR_STATE_PROPERTY" _SET_CRITERION_PROPERTY = "SET_CRITERION_PROPERTY" +_SET_DESIGNS = "SET_DESIGNS" _SET_PLACEHOLDER_VALUE = "SET_PLACEHOLDER_VALUE" _SET_PROJECT_SETTING = "SET_PROJECT_SETTING" _SET_REGISTERED_FILE_VALUE = "SET_REGISTERED_FILE_VALUE" @@ -547,13 +548,15 @@ def link_registered_file(actor_uid: str, uid: str, password: Optional[str] = Non ) -def load(actor_uid: str, password: Optional[str] = None) -> str: +def load(actor_uid: str, args: Optional[CommandArgs] = None, password: Optional[str] = None) -> str: """Generate JSON string of ``load`` command. Parameters ---------- actor_uid: str Actor uid entry. + args: Optional[CommandArgs], optional + Dictionary with additional arguments, by default ``None``. password : Optional[str], optional Password, by default ``None``. @@ -562,7 +565,9 @@ def load(actor_uid: str, password: Optional[str] = None) -> str: str JSON string of ``load`` command. """ - return _to_json(_gen_server_command(command=_LOAD, actor_uid=actor_uid, password=password)) + return _to_json( + _gen_server_command(command=_LOAD, args=args, actor_uid=actor_uid, password=password) + ) def new(password: Optional[str] = None) -> str: @@ -1490,6 +1495,31 @@ def set_criterion_property( ) +def set_designs(actor_uid: str, designs: Iterable[dict], password: Optional[str] = None) -> str: + """Generate JSON string of ``set_designs`` command. + + Parameters + ---------- + actor_uid: str + Actor uid entry. + designs: Iterable[dict] + Iterable of calculated designs. + password : Optional[str], optional + Password. Defaults to ``None``. + + Returns + ------- + str + JSON string of ``set_designs`` command. + """ + args: CommandArgs = {} + args["designs"] = designs + + return _to_json( + _gen_server_command(command=_SET_DESIGNS, actor_uid=actor_uid, args=args, password=password) + ) + + def set_placeholder_value(name: str, value: str, password: Optional[str] = None) -> str: """Generate JSON string of ``set placeholder value`` command. diff --git a/src/ansys/optislang/core/tcp/server_queries.py b/src/ansys/optislang/core/tcp/server_queries.py index 40303ca3c..d2eaff1b5 100644 --- a/src/ansys/optislang/core/tcp/server_queries.py +++ b/src/ansys/optislang/core/tcp/server_queries.py @@ -46,6 +46,7 @@ _FULL_PROJECT_TREE_WITH_PROPERTIES = "FULL_PROJECT_TREE_WITH_PROPERTIES" _GET_CRITERIA = "GET_CRITERIA" _GET_CRITERION = "GET_CRITERION" +_GET_DESIGNS = "GET_DESIGNS" _HPC_LICENSING_FORWARDED_ENVIRONMENT = "HPC_LICENSING_FORWARDED_ENVIRONMENT" _INPUT_SLOT_VALUE = "INPUT_SLOT_VALUE" _OUTPUT_SLOT_VALUE = "OUTPUT_SLOT_VALUE" @@ -619,6 +620,24 @@ def get_criterion(uid: str, name: str, password: Optional[str] = None) -> str: ) +def get_designs(uid: str, password: Optional[str] = None) -> str: + """Generate JSON string of get_designs query. + + Parameters + ---------- + uid: str + Uid entry. + password : Optional[str], optional + Password. Defaults to ``None``. + + Returns + ------- + str + JSON string of get_designs query. + """ + return _to_json(_gen_query(what=_GET_DESIGNS, uid=uid, password=password)) + + def hpc_licensing_forwarded_environment(uid: str, password: Optional[str] = None) -> str: """Generate JSON string of hpc_licensing_forwarded_environment query. diff --git a/tests/tcp/test_tcp_server_commands.py b/tests/tcp/test_tcp_server_commands.py index 1640e6e59..9bf1e403a 100644 --- a/tests/tcp/test_tcp_server_commands.py +++ b/tests/tcp/test_tcp_server_commands.py @@ -1154,32 +1154,6 @@ def test_set_actor_property(): sc.set_actor_property(name="MaxParallel", value="32") -def test_set_criterion_property(): - "Test set_criterion_property." - # basic - json_string = sc.set_criterion_property( - actor_uid=actor_uid, criterion_name="obj2", name="type", value="min" - ) - dictionary = json.loads(json_string) - requiered_string = json.loads( - '{"projects": [{"commands": [{"actor_uid": "5cdfb20b-bef6-4412-9985-89f5ded5ee95", \ - "args": {"criterion_name": "obj2", "name": "type", "value": "min"}, \ - "command": "SET_CRITERION_PROPERTY", "type": "builtin"}]}]}' - ) - assert type(json_string) == str - assert sorted(dictionary.items()) == sorted(requiered_string.items()) - # with password - json_string = sc.set_criterion_property( - actor_uid=actor_uid, - criterion_name="obj2", - name="type", - value="min", - password=example_password, - ) - dictionary = json.loads(json_string) - dictionary["Password"] == example_password - - def test_set_actor_setting(): "Test set_actor_setting." # basic @@ -1243,6 +1217,65 @@ def test_set_actor_state_property(): sc.set_actor_state_property(name="stop_after_execution", value="true") +def test_set_criterion_property(): + "Test set_criterion_property." + # basic + json_string = sc.set_criterion_property( + actor_uid=actor_uid, criterion_name="obj2", name="type", value="min" + ) + dictionary = json.loads(json_string) + requiered_string = json.loads( + '{"projects": [{"commands": [{"actor_uid": "5cdfb20b-bef6-4412-9985-89f5ded5ee95", \ + "args": {"criterion_name": "obj2", "name": "type", "value": "min"}, \ + "command": "SET_CRITERION_PROPERTY", "type": "builtin"}]}]}' + ) + assert type(json_string) == str + assert sorted(dictionary.items()) == sorted(requiered_string.items()) + # with password + json_string = sc.set_criterion_property( + actor_uid=actor_uid, + criterion_name="obj2", + name="type", + value="min", + password=example_password, + ) + dictionary = json.loads(json_string) + dictionary["Password"] == example_password + + +def test_set_designs(): + "Test set_designs." + # basic + designs = [ + { + "hid": "0.1", + "responses": [{"name": "res1", "value": 1.0}], + }, + { + "hid": "0.2", + "responses": [{"name": "res1", "value": 2.0}], + }, + ] + json_string = sc.set_designs(actor_uid=actor_uid, designs=designs) + dictionary = json.loads(json_string) + requiered_string = json.loads( + '{"projects": [{"commands": [{"actor_uid": "5cdfb20b-bef6-4412-9985-89f5ded5ee95", \ + "args": {"designs": [{"hid": "0.1", "responses": [{"name": "res1", "value": 1.0}]}, \ + {"hid": "0.2","responses": [{"name": "res1", "value": 2.0}]}]}, \ + "command": "SET_DESIGNS","type": "builtin"}]}]}' + ) + assert type(json_string) == str + assert sorted(dictionary.items()) == sorted(requiered_string.items()) + # with password + json_string = sc.set_designs( + actor_uid=actor_uid, + designs=designs, + password=example_password, + ) + dictionary = json.loads(json_string) + dictionary["Password"] == example_password + + def test_set_placeholder_value(): "Test set_placeholder_value." # basic diff --git a/tests/tcp/test_tcp_server_queries.py b/tests/tcp/test_tcp_server_queries.py index 8b16e3c33..f335b6ed9 100644 --- a/tests/tcp/test_tcp_server_queries.py +++ b/tests/tcp/test_tcp_server_queries.py @@ -387,6 +387,24 @@ def test_get_criterion(): assert dictionary["Password"] == example_password +def test_get_designs(): + "Test get_designs." + json_string = sq.get_designs(uid=example_uid) + dictionary = json.loads(json_string) + requiered_string = json.loads( + '{"What": "GET_DESIGNS", "uid": "5cdfb20b-bef6-4412-9985-89f5ded5ee95"}' + ) + assert type(json_string) == str + assert sorted(dictionary.items()) == sorted(requiered_string.items()) + # with password + json_string = sq.get_designs( + uid=example_uid, + password=example_password, + ) + dictionary = json.loads(json_string) + assert dictionary["Password"] == example_password + + def test_hpc_licensing_forwarded_environment(): "Test hpc_licensing_forwarded_environment." json_string = sq.hpc_licensing_forwarded_environment(uid=example_uid) diff --git a/tests/test_nodes.py b/tests/test_nodes.py index fc87f0a62..4368a079f 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -84,6 +84,7 @@ def test_design_flow(name: str): "PARAMETRIC_SYSTEM", "ROOT_SYSTEM", "INTEGRATION_NODE", + "PROXY_SOLVER", ], ) def test_node_class_type(name: str):