diff --git a/doc/source/_static/01_ten_bar_truss_evaluate_design.png b/doc/source/_static/01_ten_bar_truss_evaluate_design.png new file mode 100644 index 000000000..8aacf1f06 Binary files /dev/null and b/doc/source/_static/01_ten_bar_truss_evaluate_design.png differ diff --git a/doc/source/_templates/class.rst b/doc/source/_templates/class.rst new file mode 100644 index 000000000..5b0d621b8 --- /dev/null +++ b/doc/source/_templates/class.rst @@ -0,0 +1,34 @@ +{{ name | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + + {% block methods %} + + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + :toctree: + {% for item in methods %} + {% if item != "__init__" %} + {{ name }}.{{ item }} + {% endif %} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + :toctree: + {% for item in attributes %} + {% if item.0 != item.upper().0 %} + {{ name }}.{{ item }} + {% endif %} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 2ec6a4957..f025b9391 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -7,9 +7,12 @@ This section gives an overview of the API of several public PyOptiSLang classes, functions, and attributes. Use the search feature or click links to view API documentation. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 optislang + project + nodes + project_parametric logging utils diff --git a/doc/source/api/logging.rst b/doc/source/api/logging.rst index afa782792..672cb6ec2 100644 --- a/doc/source/api/logging.rst +++ b/doc/source/api/logging.rst @@ -29,9 +29,12 @@ of the tools provided in this module to extend or modify these loggers. Logging API ----------- +These classes are specific to the :mod:`ansys.optislang.core.logging ` module: + .. currentmodule:: ansys.optislang.core.logging .. autosummary:: - :toctree: _autosummary - - OslLogger + :toctree: _autosummary + :template: class.rst + + OslLogger diff --git a/doc/source/api/nodes.rst b/doc/source/api/nodes.rst new file mode 100644 index 000000000..d117b8fb6 --- /dev/null +++ b/doc/source/api/nodes.rst @@ -0,0 +1,14 @@ +Nodes +===== +These classes are specific to the :mod:`ansys.optislang.core.nodes ` module: + +.. currentmodule:: ansys.optislang.core.nodes + +.. autosummary:: + :toctree: _autosummary + :template: class.rst + + Node + System + ParametricSystem + RootSystem \ No newline at end of file diff --git a/doc/source/api/optislang.rst b/doc/source/api/optislang.rst index a9d6c1dfb..215ce397a 100644 --- a/doc/source/api/optislang.rst +++ b/doc/source/api/optislang.rst @@ -1,10 +1,11 @@ Optislang ========= -These classes are specific to the ``optislang`` module: +These classes are specific to the :mod:`ansys.optislang.core.optislang ` module: .. currentmodule:: ansys.optislang.core.optislang .. autosummary:: :toctree: _autosummary + :template: class.rst Optislang diff --git a/doc/source/api/project.rst b/doc/source/api/project.rst new file mode 100644 index 000000000..a6efafd4d --- /dev/null +++ b/doc/source/api/project.rst @@ -0,0 +1,11 @@ +Project +======= +These classes are specific to the :mod:`ansys.optislang.core.project ` module: + +.. currentmodule:: ansys.optislang.core.project + +.. autosummary:: + :toctree: _autosummary + :template: class.rst + + Project \ No newline at end of file diff --git a/doc/source/api/project_parametric.rst b/doc/source/api/project_parametric.rst new file mode 100644 index 000000000..e88679e17 --- /dev/null +++ b/doc/source/api/project_parametric.rst @@ -0,0 +1,17 @@ +Project parametric +================== +These classes are specific to the :mod:`ansys.optislang.core.project_parametric ` module: + +.. currentmodule:: ansys.optislang.core.project_parametric + +.. autosummary:: + :toctree: _autosummary + :template: class.rst + + ParameterManager + Design + DesignVariable + DependentParameter + MixedParameter + OptimizationParameter + StochasticParameter \ No newline at end of file diff --git a/doc/source/api/utils.rst b/doc/source/api/utils.rst index 48be4f439..41e2dfac2 100644 --- a/doc/source/api/utils.rst +++ b/doc/source/api/utils.rst @@ -1,6 +1,6 @@ Helper functions ================ -These classes are specific to the ``utils`` module: +These methods are specific to the :mod:`ansys.optislang.core.utils ` module: .. currentmodule:: ansys.optislang.core.utils diff --git a/doc/source/conf.py b/doc/source/conf.py index 03ba71aaf..5198e8d0a 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,37 +1,23 @@ """Sphinx documentation configuration file.""" from datetime import datetime +import os -from ansys_sphinx_theme import pyansys_logo_black +from ansys_sphinx_theme import ansys_favicon, get_version_match, pyansys_logo_black from sphinx_gallery.sorting import FileNameSortKey import ansys.optislang.core as pyoptislang from ansys.optislang.core import __version__ # Project information -project = "ansys-optislang-core" +project = "ansys.optislang.core" copyright = f"(c) {datetime.now().year} ANSYS, Inc. All rights reserved" author = "ANSYS, Inc." release = version = __version__ - -# use the default pyansys logo -html_logo = pyansys_logo_black -html_theme = "ansys_sphinx_theme" - -html_short_title = html_title = "ansys-optislang-core" +cname = os.getenv("DOCUMENTATION_CNAME", "nocname.com") # necessary when building the sphinx gallery pyoptislang.BUILDING_GALLERY = True -# specify the location of your github repo -html_theme_options = { - "github_url": "https://github.com/pyansys/pyoptislang", - "show_prev_next": False, - "show_breadcrumbs": True, - "additional_breadcrumbs": [ - ("PyAnsys", "https://docs.pyansys.com/"), - ], -} - # Sphinx extensions extensions = [ "sphinx.ext.autodoc", @@ -77,6 +63,9 @@ # type, unless multiple values are being returned" } +# Favicon +html_favicon = ansys_favicon + # static path html_static_path = ["_static"] @@ -89,6 +78,18 @@ # The master toctree document. master_doc = "index" +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build"] + # -- Sphinx Gallery Options --------------------------------------------------- sphinx_gallery_conf = { # convert rst to md for ipynb @@ -112,3 +113,32 @@ # Execute scripts? "plot_gallery": False, } + +# -- Options for HTML output ------------------------------------------------- +html_short_title = html_title = "PyOptiSLang" +html_theme = "ansys_sphinx_theme" +html_logo = pyansys_logo_black +html_theme_options = { + "github_url": "https://github.com/pyansys/pyoptislang", + "show_prev_next": False, + "show_breadcrumbs": True, + "collapse_navigation": True, + "use_edit_page_button": True, + "additional_breadcrumbs": [ + ("PyAnsys", "https://docs.pyansys.com/"), + ], + "switcher": { + "json_url": f"{cname}/release/versions.json", + "version_match": get_version_match(__version__), + }, + "navbar_end": ["version-switcher", "theme-switcher", "navbar-icon-links"], + "navigation_depth": 1, +} + +html_context = { + "display_github": True, # Integrate GitHub + "github_user": "pyansys", + "github_repo": "pyoptislang", + "github_version": "main", + "doc_path": "doc/source", +} diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst index a03f90cb6..8756d28de 100644 --- a/doc/source/getting_started/index.rst +++ b/doc/source/getting_started/index.rst @@ -68,10 +68,10 @@ Verify installation .. code:: python - >>> from ansys.optislang.core import Optislang - >>> osl = Optislang() - >>> print(osl) - >>> osl.dispose() + from ansys.optislang.core import Optislang + osl = Optislang() + print(osl) + osl.dispose() If you see a response from the server, congratulations, you're ready to get started using OptiSLang as a service. For details regarding the diff --git a/doc/source/user_guide/design_evaluation.rst b/doc/source/user_guide/design_evaluation.rst new file mode 100644 index 000000000..0d4d3fd81 --- /dev/null +++ b/doc/source/user_guide/design_evaluation.rst @@ -0,0 +1,141 @@ +.. _ref_design_evaluation: + +================== +Design evaluation +================== +Instance of the :class:`RootSystem() ` allows user to +create new designs and evaluate them. Instance of the +:class:`RootSystem() ` is accessible from instance of the +:class:`Optislang() ` via its +:func:`project ` property. + +.. code:: python + + from ansys.optislang.core import Optislang + from ansys.optislang.core.project_parametric import Design + from ansys.optislang.core import examples + from pathlib.Path import Path + + # open project with defined parameters + parametric_project = examples.get_files('calculator_with_params')[1][0] + osl = Optislang(project_path=parametric_project) + + # do not modify original file + osl.save_as(Path.cwd() / 'parametric_project.opf') + + # get root system + root_system = osl.project.root_system + +.. note:: + + Creation and evaluation of designs is currently supported only on the + :class:`RootSystem() `. Therefore, project has to have + parameters already defined at the project root system and workflow has to be + prepared for this (``Receive designs`` and ``Send back designs`` must be set in design flow). + Please, see example :ref:`ref_ten_bar_truss_evaluate_design`. + + +Create new design +----------------- +New :class:`Design() ` can be created by obtaining +design with reference values from :class:`RootSystem() ` +and modifying its parameters or design parameters can be specified from scratch. + + +From reference design +~~~~~~~~~~~~~~~~~~~~~ +Design with all parameters specified in the project with their reference values can be obtained by +:func:`get_reference_design() ` method. +Parameters values may be modified by methods of the instance of +:class:`Design() ` class. + +.. code:: python + + # ... + + from ansys.optislang.core.project_parametric import DesignVariable + + reference_design = root_system.get_reference_design() + + # modify parameter value using either ``name`` and ``value`` + reference_design.set_parameter_by_name(name = 'a', value = 12) + + # instance of ``DesignVariable`` or ``Parameter`` may be used as well + a = DesignVariable(name='a', value=12) + reference_design.set_parameter(parameter=a) + + +From empty design +~~~~~~~~~~~~~~~~~~~ +Design can be also created from scratch directly creating instance of the +:class:`Design() ` class. +Parameters don't have to be provided when initializing new design. + +.. code:: python + + # design created using directly Design() class + direct_design = Design(parameters = {'a': 3, 'b': 4}) + + # create empty design and add parameters afterward + empty_design = Design() + empty_design.set_parameter_by_name(name = 'a', value = 3) + empty_design.set_parameter_by_name(name = 'q', value = 4) + + # parameters may also be removed + empty_design.remove_parameter(name = 'c') + + # or remove all parameters + empty_design.clear_parameters() + + +Check design parameters +~~~~~~~~~~~~~~~~~~~~~~~ +In order to check whether design contains all parameters defined in the project, +:func:`get_missing_parameters_names() ` +method can be used. To check, whether design contains parameters which are not defined in the project, method +:func:`get_undefined_parameters_names() ` +may be used. This step is not necessary though, because this is always done internally while evaluating design. + +.. code:: python + + # ... + + missing_parameters = root_system.get_missing_parameters(empty_design) + undefined_parameters = root_system.get_undefined_parameters(direct_design) + + +Evaluate design +--------------- +Designs can be evaluated using method +:func:`evaluate_design() `. This method +returns the same instance of :class:`Design() ` +class with updated results. + +.. code:: python + + # ... + + # single design + result_design = root_system.evaluate_design(design = reference_design) + +.. note:: + + Please, note that the optiSLang retains only last evaluated design at the project root system. + Therefore, results of previous designs have to be stored locally if they are required for + further usage, for example as an instance of + :class:`Design() ` class. + +Finally, when everything is done and +:class:`Optislang() ` instance is not needed any more, +terminate it. + +.. code:: python + + osl.dispose() + + + + + + + diff --git a/doc/source/user_guide/functions.rst b/doc/source/user_guide/functions.rst index 24458d4c3..aa21ef1ac 100644 --- a/doc/source/user_guide/functions.rst +++ b/doc/source/user_guide/functions.rst @@ -9,8 +9,7 @@ In order to start project, use :func:`start ` instance is no longer needed, terminate connection with optiSLang server by running: diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index 888471f8f..d13883f0a 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -12,6 +12,8 @@ PyOptiSLang library. launch functions + project_content + design_evaluation run_python troubleshooting @@ -31,10 +33,11 @@ by the ``project_path`` parameter of the import os from ansys.optislang.core import Optislang + from pathlib import Path - path = os.getcwd() + path = Path.cwd() file_name = 'test_optislang.opf' - with Optislang(project_path = os.path.join(path, file_name)) as osl: + with Optislang(project_path = path / file_name) as osl: print(osl) diff --git a/doc/source/user_guide/launch.rst b/doc/source/user_guide/launch.rst index 76fe56ab2..6ef065e07 100644 --- a/doc/source/user_guide/launch.rst +++ b/doc/source/user_guide/launch.rst @@ -63,13 +63,13 @@ In order to open specific project or create new one, launch .. code:: python - import os from ansys.optislang.core import Optislang + from pathlib import Path - path = os.getcwd() + path = Path.cwd() project_name = 'test.opf' - osl = Optislang(project_path = os.path.join(path, project_name)) + osl = Optislang(project_path = path / project_name) print(osl) osl.dispose() diff --git a/doc/source/user_guide/project_content.rst b/doc/source/user_guide/project_content.rst new file mode 100644 index 000000000..f609d469a --- /dev/null +++ b/doc/source/user_guide/project_content.rst @@ -0,0 +1,95 @@ +.. _ref_project_content: + +Project +------- +Instance of the :class:`Project() `, that is accessible +from the instance of :class:`Optislang() `, contains +methods for obtaining information about loaded project and it's content. + +.. code:: python + + from ansys.optislang.core import Optislang + from ansys.optislang.core import examples + from pathlib.Path import Path + + example = examples.get_files("calculator_with_params")[1][0] + osl = Optislang(project_path=example) + osl.save_copy(Path.cwd() / 'project_content.opf') + project = osl.project + + # print project info + print(project) + # obtain these information directly + name = project.get_name() + location = project.get_location() + status = project.get_status() + + +Project structure +----------------- +The optiSLang project can be represented by a rooted tree structure. This structure consists +of nodes and connections between them. On the top level, there is one node designated as a project +root system which is represented by the :class:`RootSystem() ` +instance. Each :class:`System() ` +(for example :class:`RootSystem() `, +:class:`ParametricSystem() `) has a method +:func:`get_nodes() ` which returns all its direct children +nodes. This provides an ability to determine whole project structure. The example below shows +how to go through all nodes in the project and print information about them. + +.. code:: python + + # ... + + def print_node_info(node): + name = node.get_name() + type_ = node.get_type() + status = node.get_status() + print(name, type_, status) + + + def process_nodes(nodes): + for node in nodes: + print_node_info(node) + if isinstance(node, System): + process_nodes(node.get_nodes()) + + + root_system = project.root_system + nodes = root_system.get_nodes() + process_nodes(nodes) + + +Parameters +---------- +In order to obtain defined parameters of any parametric system, instance of the +:class:`ParameterManager() ` +may be used. This class contains methods +:func:`get_parameters() `, +that returns tuple of instances of the +:class:`OptimizationParameter() `, +:class:`StochasticParameter() `, +:class:`MixedParameter() ` or +:class:`DepenedentParameter() ` +classes with detailed information and simplified method +:func:`get_parameters_names() `, +that returns tuple of only parameters names. + +.. code:: python + + # ... + + parameter_manager = root_system.parameter_manager + parameters = parameter_manager.get_parameters() + parameters_names = parameter.get_parameters_names() + + +When the :class:`Optislang() ` instance is no longer +needed, terminate connection with optiSLang server by running: + +.. code:: python + + osl.dispose() + + + diff --git a/doc/source/user_guide/run_python.rst b/doc/source/user_guide/run_python.rst index 8f198a89c..266469204 100644 --- a/doc/source/user_guide/run_python.rst +++ b/doc/source/user_guide/run_python.rst @@ -30,6 +30,7 @@ calculator (see example :ref:`ref_simple_calculator`): from ansys.optislang.core import Optislang from ansys.optislang.core import examples + osl = Optislang() path_to_file = examples.get_files('simple_calculator')[0] osl.run_python_file(file_path=path_to_file) diff --git a/doc/source/user_guide/troubleshooting.rst b/doc/source/user_guide/troubleshooting.rst index 178bff5a1..6665c78d5 100644 --- a/doc/source/user_guide/troubleshooting.rst +++ b/doc/source/user_guide/troubleshooting.rst @@ -10,6 +10,7 @@ parameter ``loglevel`` when launching optiSLang: .. code:: python from ansys.optislang.core import Optislang + osl = Optislang(loglevel='DEBUG') osl.dispose() @@ -24,6 +25,7 @@ when launching optiSLang: .. code:: python from ansys.optislang.core import Optislang + osl = Optislang(ini_timeout=30) osl.dispose() diff --git a/examples/README.txt b/examples/README.txt index b606b47bd..e69de29bb 100644 --- a/examples/README.txt +++ b/examples/README.txt @@ -1,3 +0,0 @@ -Run python script -================= -These examples demonstrate using :func:`run_python_script() `. \ No newline at end of file diff --git a/examples/evaluate_design/01_ten_bar_truss.py b/examples/evaluate_design/01_ten_bar_truss.py new file mode 100644 index 000000000..986c68f05 --- /dev/null +++ b/examples/evaluate_design/01_ten_bar_truss.py @@ -0,0 +1,138 @@ +""" +.. _ref_ten_bar_truss_evaluate_design: + +Ten bar truss +------------- + +Use ``evaluate_design()`` method at the root level of project. Use reference design at first +and then decrease cross section areas in each iteration in order to minimize mass, while +satisfying defined constraints of maximum stress for two loading conditions. +""" + +######################################################### +# Used workflow: +# .. image:: ../../../_static/01_ten_bar_truss_evaluate_design.png +# :width: 400 +# :alt: Result of script. +# +######################################################### + +from pathlib import Path +import tempfile + +import matplotlib.pyplot as plt + +#################################################### +# Import necessary modules. +#################################################### +from ansys.optislang.core import Optislang +import ansys.optislang.core.examples as examples + +################################################################################# +# Create :class:`Optislang ` instance. +################################################################################# +example_path = examples.get_files("ten_bar_truss")[1][0] +tmp_dir = Path(tempfile.mkdtemp()) +file_path = tmp_dir / "evaluate_design_example.opf" + +osl = Optislang(project_path=example_path) +osl.save_as(file_path) +print(osl.get_working_dir()) + +######################################################### +# Get reference design and evaluate it, extract results. +######################################################### +rs = osl.project.root_system +parameters_count = len(rs.parameter_manager.get_parameters_names()) +try_decrease_param = [True for i in range(parameters_count)] +successfull_designs = [] +unsuccessfull_designs = [] + +design = rs.get_reference_design() +rs.evaluate_design(design) +if design.feasibility: + successfull_designs.append(design) +else: + raise ValueError("Constraints not satisfied for reference design, do not start example.") + +plot_mass_successfull = [(1, design.objectives[0].value)] +plot_mass_unsuccessfull = [] +plot_max_stress_lc1 = [abs(max(design.responses[1].value, key=abs))] +plot_max_stress_lc2 = [abs(max(design.responses[2].value, key=abs))] + +######################################################################################### +# Copy last successful design and gradually decrease cross section areas of each truss, +# store results. If decreasing of cross sectional area leads to violation of constraints, +# stop decreasing it. + +######################################################################################### +design_count = 1 +while True in try_decrease_param: + for j in range(parameters_count): + if not try_decrease_param[j]: + continue + design_count += 1 + design = successfull_designs[-1].copy_unevaluated_design() + parameters = design.parameters + parameter_value = parameters[j].value + if parameter_value > 1: + parameters[j].value = parameter_value - 1 + else: + try_decrease_param[j] = False + continue + rs.evaluate_design(design) + if design.feasibility: + successfull_designs.append(design) + plot_mass_successfull.append((design_count, design.objectives[0].value)) + else: + unsuccessfull_designs.append(design) + try_decrease_param[j] = False + plot_mass_unsuccessfull.append((design_count, design.objectives[0].value)) + plot_max_stress_lc1.append(abs(max(design.responses[1].value, key=abs))) + plot_max_stress_lc2.append(abs(max(design.responses[2].value, key=abs))) + +######################################################################################## +# Extract cross sectional areas, objective and constraints from last successful design. +######################################################################################## +best_design = successfull_designs[-1] +print("*-----------BEST-DESIGN-PARAMETERS-------------*") +for parameter in best_design.parameters: + print(parameter.name, parameter.value) +print("*-----------BEST-DESIGN-OBJECTIVE-------------*") +for objective in best_design.objectives: + print(objective.name, objective.value) + +######################################################### +# Terminate and cancel project. +######################################################### +osl.dispose() + +######################################################### +# Plot extracted results. +######################################################### +fig, axs = plt.subplots(2) +fig.suptitle("Optimization of ten bar truss cross section areas") + +s1 = axs[0].scatter( + [xy[0] for xy in plot_mass_successfull], + [xy[1] for xy in plot_mass_successfull], + color="g", + label="successful", +) +s2 = axs[0].scatter( + [xy[0] for xy in plot_mass_unsuccessfull], + [xy[1] for xy in plot_mass_unsuccessfull], + color="r", + label="unsuccessful", +) +axs[0].set_ylabel("mass") +axs[0].legend() + +(l1,) = axs[1].plot(range(1, design_count + 1), plot_max_stress_lc1, color="b", label="lc1") +axs[1].hlines(y=25000, xmin=1, xmax=design_count, color="b", linestyles="--") +(l2,) = axs[1].plot(range(1, design_count + 1), plot_max_stress_lc2, color="c", label="lc2") +axs[1].hlines(y=35000, xmin=1, xmax=design_count, color="c", linestyles="--") +axs[1].set_xlabel("design ID") +axs[1].set_ylabel("max stress") +axs[1].legend(handles=[l1, l2]) +plt.show() diff --git a/examples/evaluate_design/README.txt b/examples/evaluate_design/README.txt new file mode 100644 index 000000000..5b575b01d --- /dev/null +++ b/examples/evaluate_design/README.txt @@ -0,0 +1,3 @@ +Evaluate design +=============== +These examples demonstrate using :func:`evaluate_design() `. \ No newline at end of file diff --git a/examples/01_ten_bar_truss.py b/examples/run_python_script/01_ten_bar_truss.py similarity index 95% rename from examples/01_ten_bar_truss.py rename to examples/run_python_script/01_ten_bar_truss.py index b7760dd29..1325b2ee8 100644 --- a/examples/01_ten_bar_truss.py +++ b/examples/run_python_script/01_ten_bar_truss.py @@ -43,7 +43,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -54,7 +54,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/01_ten_bar_truss.png +# .. image:: ../../../_static/01_ten_bar_truss.png # :width: 400 # :alt: Result of script. # diff --git a/examples/02_1_oscillator_robustness.py b/examples/run_python_script/02_1_oscillator_robustness.py similarity index 94% rename from examples/02_1_oscillator_robustness.py rename to examples/run_python_script/02_1_oscillator_robustness.py index 3b93a2914..d5ca9fa4c 100644 --- a/examples/02_1_oscillator_robustness.py +++ b/examples/run_python_script/02_1_oscillator_robustness.py @@ -37,7 +37,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -48,7 +48,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/02_1_oscillator_robustness.png +# .. image:: ../../../_static/02_1_oscillator_robustness.png # :width: 600 # :alt: Result of script. # diff --git a/examples/02_2_oscillator_python_system.py b/examples/run_python_script/02_2_oscillator_python_system.py similarity index 94% rename from examples/02_2_oscillator_python_system.py rename to examples/run_python_script/02_2_oscillator_python_system.py index 28f72765c..7bfe20a60 100644 --- a/examples/02_2_oscillator_python_system.py +++ b/examples/run_python_script/02_2_oscillator_python_system.py @@ -37,7 +37,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -48,7 +48,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/02_2_python_system.png +# .. image:: ../../../_static/02_2_python_system.png # :width: 300 # :alt: Result of script. # diff --git a/examples/02_3_oscillator_optimization_on_EA.py b/examples/run_python_script/02_3_oscillator_optimization_on_EA.py similarity index 94% rename from examples/02_3_oscillator_optimization_on_EA.py rename to examples/run_python_script/02_3_oscillator_optimization_on_EA.py index 3c330990a..4e748e90f 100644 --- a/examples/02_3_oscillator_optimization_on_EA.py +++ b/examples/run_python_script/02_3_oscillator_optimization_on_EA.py @@ -38,7 +38,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -49,7 +49,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/02_3_optimization_on_EA.png +# .. image:: ../../../_static/02_3_optimization_on_EA.png # :width: 400 # :alt: Result of script. # diff --git a/examples/02_4_oscillator_MOP_sensitivity_and_optimization.py b/examples/run_python_script/02_4_oscillator_MOP_sensitivity_and_optimization.py similarity index 93% rename from examples/02_4_oscillator_MOP_sensitivity_and_optimization.py rename to examples/run_python_script/02_4_oscillator_MOP_sensitivity_and_optimization.py index 8b25cfdc9..067740086 100644 --- a/examples/02_4_oscillator_MOP_sensitivity_and_optimization.py +++ b/examples/run_python_script/02_4_oscillator_MOP_sensitivity_and_optimization.py @@ -42,7 +42,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -53,7 +53,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/02_4_oscillator_MOP_sensitivity_and_optimization.png +# .. image:: ../../../_static/02_4_oscillator_MOP_sensitivity_and_optimization.png # :width: 600 # :alt: Result of script. # diff --git a/examples/02_5_oscillator_calibration_systems.py b/examples/run_python_script/02_5_oscillator_calibration_systems.py similarity index 94% rename from examples/02_5_oscillator_calibration_systems.py rename to examples/run_python_script/02_5_oscillator_calibration_systems.py index 43c334a8c..576d3f87b 100644 --- a/examples/02_5_oscillator_calibration_systems.py +++ b/examples/run_python_script/02_5_oscillator_calibration_systems.py @@ -41,7 +41,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -52,7 +52,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/02_5_oscillator_calibration_systems.png +# .. image:: ../../../_static/02_5_oscillator_calibration_systems.png # :width: 400 # :alt: Result of script. # diff --git a/examples/03_etk_abaqus.py b/examples/run_python_script/03_etk_abaqus.py similarity index 94% rename from examples/03_etk_abaqus.py rename to examples/run_python_script/03_etk_abaqus.py index 7371fcacb..7cd8a2926 100644 --- a/examples/03_etk_abaqus.py +++ b/examples/run_python_script/03_etk_abaqus.py @@ -32,7 +32,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -43,7 +43,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/03_etk_abaqus.png +# .. image:: ../../../_static/03_etk_abaqus.png # :width: 400 # :alt: Result of script. # diff --git a/examples/04_python_node_and_help.py b/examples/run_python_script/04_python_node_and_help.py similarity index 94% rename from examples/04_python_node_and_help.py rename to examples/run_python_script/04_python_node_and_help.py index ab9a15c07..7c3cd229d 100644 --- a/examples/04_python_node_and_help.py +++ b/examples/run_python_script/04_python_node_and_help.py @@ -35,7 +35,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -46,7 +46,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/04_python_node_and_help.png +# .. image:: ../../../_static/04_python_node_and_help.png # :width: 300 # :alt: Result of script. # diff --git a/examples/05_optimizer_settings.py b/examples/run_python_script/05_optimizer_settings.py similarity index 93% rename from examples/05_optimizer_settings.py rename to examples/run_python_script/05_optimizer_settings.py index a53b70996..37316c2ff 100644 --- a/examples/05_optimizer_settings.py +++ b/examples/run_python_script/05_optimizer_settings.py @@ -33,7 +33,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -44,7 +44,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/05_optimizer_settings.png +# .. image:: ../../../_static/05_optimizer_settings.png # :width: 300 # :alt: Result of script. # diff --git a/examples/06_sensitivity_settings.py b/examples/run_python_script/06_sensitivity_settings.py similarity index 93% rename from examples/06_sensitivity_settings.py rename to examples/run_python_script/06_sensitivity_settings.py index b93199f43..1e1db8bf2 100644 --- a/examples/06_sensitivity_settings.py +++ b/examples/run_python_script/06_sensitivity_settings.py @@ -32,7 +32,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -43,7 +43,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/06_sensitivity_settings.png +# .. image:: ../../../_static/06_sensitivity_settings.png # :width: 300 # :alt: Result of script. # diff --git a/examples/07_simple_calculator.py b/examples/run_python_script/07_simple_calculator.py similarity index 94% rename from examples/07_simple_calculator.py rename to examples/run_python_script/07_simple_calculator.py index a88d947e8..242cc43ef 100644 --- a/examples/07_simple_calculator.py +++ b/examples/run_python_script/07_simple_calculator.py @@ -37,7 +37,7 @@ # .. code:: python # # path = r'' -# osl.save_copy(os.path.join(path, "test_project.opf")) +# osl.save_as(os.path.join(path, "test_project.opf")) # ###################################################################### @@ -48,7 +48,7 @@ ######################################################### # Generated workflow: -# .. image:: ../../_static/07_simple_calculator.png +# .. image:: ../../../_static/07_simple_calculator.png # :width: 400 # :alt: Result of script. # diff --git a/examples/run_python_script/README.txt b/examples/run_python_script/README.txt new file mode 100644 index 000000000..b606b47bd --- /dev/null +++ b/examples/run_python_script/README.txt @@ -0,0 +1,3 @@ +Run python script +================= +These examples demonstrate using :func:`run_python_script() `. \ No newline at end of file diff --git a/requirements/requirements_tests.txt b/requirements/requirements_tests.txt index c55bf5daa..eee82f00d 100644 --- a/requirements/requirements_tests.txt +++ b/requirements/requirements_tests.txt @@ -1,2 +1,3 @@ pytest>=7.1.3 pytest-cov>=3.0.0 +matplotlib>=3.5.3 diff --git a/src/ansys/optislang/core/examples/00_run_script/files/calculator_with_params.opf b/src/ansys/optislang/core/examples/00_run_script/files/calculator_with_params.opf new file mode 100644 index 000000000..96479de5f Binary files /dev/null and b/src/ansys/optislang/core/examples/00_run_script/files/calculator_with_params.opf differ diff --git a/src/ansys/optislang/core/examples/00_run_script/files/connect_nodes.opf b/src/ansys/optislang/core/examples/00_run_script/files/connect_nodes.opf new file mode 100644 index 000000000..9b1eb7dbf Binary files /dev/null and b/src/ansys/optislang/core/examples/00_run_script/files/connect_nodes.opf differ diff --git a/src/ansys/optislang/core/examples/00_run_script/files/nested_systems.opf b/src/ansys/optislang/core/examples/00_run_script/files/nested_systems.opf new file mode 100644 index 000000000..295db11c4 Binary files /dev/null and b/src/ansys/optislang/core/examples/00_run_script/files/nested_systems.opf differ diff --git a/src/ansys/optislang/core/examples/00_run_script/files/ten_bar_truss.opf b/src/ansys/optislang/core/examples/00_run_script/files/ten_bar_truss.opf new file mode 100644 index 000000000..5dde724d2 Binary files /dev/null and b/src/ansys/optislang/core/examples/00_run_script/files/ten_bar_truss.opf differ diff --git a/src/ansys/optislang/core/examples/downloads.py b/src/ansys/optislang/core/examples/downloads.py index ae07ad258..71d4fe9a2 100644 --- a/src/ansys/optislang/core/examples/downloads.py +++ b/src/ansys/optislang/core/examples/downloads.py @@ -50,6 +50,9 @@ def get_files(scriptname: str) -> Tuple[Path, Tuple[Path, ...]]: else: file_path = None - script_path = _download_script(scriptname) + if examples.example_scripts[scriptname] is not None: + script_path = _download_script(scriptname) + else: + script_path = None return (script_path, file_path) diff --git a/src/ansys/optislang/core/examples/examples.py b/src/ansys/optislang/core/examples/examples.py index 0748b7a72..70dd02832 100644 --- a/src/ansys/optislang/core/examples/examples.py +++ b/src/ansys/optislang/core/examples/examples.py @@ -58,6 +58,12 @@ "optimizer_settings": None, "sensitivity_settings": None, "simple_calculator": (module_path / "00_run_script" / "files" / "calculator.opf",), + "calculator_with_params": ( + module_path / "00_run_script" / "files" / "calculator_with_params.opf", + ), + "nodes_connection": (module_path / "00_run_script" / "files" / "connect_nodes.opf",), + "nested_systems": (module_path / "00_run_script" / "files" / "nested_systems.opf",), + "ten_bar_truss": (module_path / "00_run_script" / "files" / "ten_bar_truss.opf",), } # dictionary of scripts to be run @@ -131,4 +137,8 @@ "optimizer_settings": module_path / "00_run_script" / "scripts" / "optimizer_settings.py", "sensitivity_settings": module_path / "00_run_script" / "scripts" / "sensitivity_settings.py", "simple_calculator": module_path / "00_run_script" / "scripts" / "simple_calculator.py", + "calculator_with_params": None, + "nodes_connection": None, + "nested_systems": None, + "ten_bar_truss": None, } diff --git a/src/ansys/optislang/core/logging.py b/src/ansys/optislang/core/logging.py index 8baac9370..665bdddeb 100644 --- a/src/ansys/optislang/core/logging.py +++ b/src/ansys/optislang/core/logging.py @@ -94,7 +94,7 @@ def __init__( loglevel : str, optional Level of logging, by default LOG_LEVEL. log_to_file : bool, optional - Record logs to file, by default ``True``. + Record logs to file, by default ``False``. logfile_name: str, optional Output file name, by default FILE_NAME log_to_stdout : bool, optional diff --git a/src/ansys/optislang/core/nodes.py b/src/ansys/optislang/core/nodes.py new file mode 100644 index 000000000..e78737dc7 --- /dev/null +++ b/src/ansys/optislang/core/nodes.py @@ -0,0 +1,858 @@ +"""Contains classes Node, System, ParametricSystem and RootSystem.""" +from __future__ import annotations + +from enum import Enum +from typing import TYPE_CHECKING, Iterable, List, Tuple, Union + +from ansys.optislang.core.project_parametric import Design, ParameterManager + +if TYPE_CHECKING: + from ansys.optislang.core.osl_server import OslServer + + +class DesignFlow(Enum): + """Available design flow options.""" + + NONE = 0 + RECEIVE = 1 + SEND = 2 + RECEIVE_SEND = 3 + + @staticmethod + def from_str(label: str) -> DesignFlow: + """Convert string to ``DesignFlow``. + + Parameters + ---------- + label: str + String that shall be converted. + + Returns + ------- + DesignFlow + Instance of the ``DesignFlow`` class. + + Raises + ------ + TypeError + Raised when inappropriate type of ``label`` was given. + ValueError + Raised when inappropriate value of ``label`` was given. + """ + if not isinstance(label, str): + raise TypeError(f"String was expected, but `{type(label)}` was given.") + label = label.upper() + try: + return eval("DesignFlow." + label) + except: + raise ValueError(f"Option `{label}` not available in ``DesignFlow`` options.") + + +class Node: + """Class responsible for creation and operations on Node.""" + + def __init__( + self, + uid: str, + osl_server: OslServer, + ) -> None: + """Create a new instance of ``Node``. + + Parameters + ---------- + uid: str + Uid. + osl_server: OslServer + Object providing access to optiSLang server. + """ + self._osl_server = osl_server + self.__uid = uid + + def __str__(self): + """Return formatted string.""" + return f"Node type: {self.get_type()} Name: {self.get_name()} Uid: {self.uid}" + + @property + def uid(self) -> str: + """Get node uid. + + Returns + ------- + str + Node uid. + """ + return self.__uid + + def get_name(self) -> str: + """Get name of the current node. + + Returns + ------- + str + Node name. + + 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. + """ + actor_info = self._osl_server.get_actor_info(uid=self.__uid) + return actor_info["name"] + + def get_parent(self) -> Node: + """Get instance of the parent node. + + Returns + ------- + Node + Instance of the parent node. + + 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. + """ + project_tree = self._osl_server.get_full_project_tree_with_properties() + root_system_uid = project_tree["projects"][0]["system"]["uid"] + parent_tree = project_tree["projects"][0]["system"] + parent_uid = Node._find_parent_node_uid( + tree=parent_tree, + parent_uid=root_system_uid, + node_uid=self.uid, + ) + properties_dicts_list = [ + { + "uid": parent_uid, + "kind": "root_system" if parent_uid == root_system_uid else "system", + } + ] + return self._create_nodes_from_properties_dicts( + properties_dicts_list=properties_dicts_list + )[0] + + def get_parent_name(self) -> str: + """Get name of the parent node. + + Returns + ------- + str + Parent system name. + + 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. + """ + parent_uid = self._get_parent_uid() + actor_info = self._osl_server.get_actor_info(uid=parent_uid) + return actor_info["name"] + + def get_properties(self) -> dict: + """Get raw server output with node properties. + + Returns + ------- + dict + Dictionary with node properties + + 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. + """ + return self._osl_server.get_actor_properties(self.uid) + + def get_status(self) -> str: + """Get status of the current node. + + Returns + ------- + str + Status of node. + + 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. + """ + actor_info = self._osl_server.get_actor_info(uid=self.__uid) + return actor_info["status"] + + def get_type(self) -> str: + """Get type of the current node. + + Returns + ------- + str + Type of node. + + 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. + """ + actor_info = self._osl_server.get_actor_info(uid=self.__uid) + return actor_info["type"] + + def _get_parent_uid(self) -> str: + """Get uid of the parent node. + + Return + ------ + str + Parents node uid. + + 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. + """ + project_tree = self._osl_server.get_full_project_tree_with_properties() + root_system_uid = project_tree["projects"][0]["system"]["uid"] + parent_tree = project_tree["projects"][0]["system"] + return Node._find_parent_node_uid( + tree=parent_tree, + parent_uid=root_system_uid, + node_uid=self.uid, + ) + + def _create_nodes_from_properties_dicts( + self, properties_dicts_list: List[dict] + ) -> Tuple[Node, ...]: + """Create nodes from properties dict. + + Parameters + ---------- + properties_dicts_list : List[dict] + Properties of nodes. + + Returns + ------- + Tuple[Node, ...] + Tuple of Nodes. + + Raises + ------ + TypeError + Raised when unknown type of component was found. + """ + nodes_list = [] + for node in properties_dicts_list: + kind = node["kind"] + uid = node["uid"] + if kind == "actor": + nodes_list.append(Node(uid=uid, osl_server=self._osl_server)) + elif kind == "system": + if self._is_parametric_system(uid=uid): + nodes_list.append(ParametricSystem(uid=uid, osl_server=self._osl_server)) + else: + nodes_list.append(System(uid=uid, osl_server=self._osl_server)) + elif kind == "root_system": + nodes_list.append(RootSystem(uid=node["uid"], osl_server=self._osl_server)) + else: + TypeError( + f'Unknown kind of component: "{kind}", ' + '"node", "system" or "root_system" were expected.' + ) + + return tuple(nodes_list) + + def _is_parametric_system(self, uid: str) -> bool: + """Check whether system is parametric. + + Parameters + ---------- + uid : str + System uid. + + Returns + ------- + bool + True/False. + + 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. + """ + props = self._osl_server.get_actor_properties(uid=uid) + return "ParameterManager" in props["properties"] + + @staticmethod + def _find_parent_node_uid(tree: dict, parent_uid: str, node_uid: str) -> str: + """Get uid of the parent node. + + Parameters + ---------- + tree: dict + Dictionary with children nodes. + parent_uid: str + Uid of the system that is being looped through. + node_uid: str + Uid of the node for which the parent is being searched. + + Return + ------ + str + Uid of the parent node. + """ + for node in tree["nodes"]: + if node["uid"] == node_uid: + return parent_uid + if node["kind"] == "system": + Node._find_parent_node_uid(tree=node, parent_uid=tree["uid"], node_uid=node_uid) + raise RuntimeError(f'Node "{node_uid}" was not located in structure tree.') + + +class System(Node): + """Class responsible for creation and operations on System.""" + + def __init__( + self, + uid: str, + osl_server: OslServer, + ) -> None: + """Create a new instance of ``System``. + + Parameters + ---------- + uid: str + Uid. + osl_server: OslServer + Object providing access to optiSLang server. + """ + super().__init__( + uid=uid, + osl_server=osl_server, + ) + + def find_node_by_uid(self, uid: str, search_depth: int = 1) -> Union[Node, None]: + """Find node with the specified uid in the current system. + + Search only in the current system descendant nodes. + + Parameters + ---------- + uid : str + Node uid. + search_depth: int, optional + Search depth of the current node subtree. Level-1 corresponds to direct children nodes + of the current system. + + Returns + ------- + Union[Node, None] + ``Node`` with defined uid; ``None`` if wasn't located in any descendant node. + + 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. + TypeError + Raised when unknown type of component was found. + """ + project_tree = self._osl_server.get_full_project_tree_with_properties() + if self.uid == project_tree["projects"][0]["system"]["uid"]: + system_tree = project_tree["projects"][0]["system"] + else: + system_tree = System._find_subtree( + tree=project_tree["projects"][0]["system"], + uid=self.uid, + ) + properties_dicts_list = System._find_node_with_uid( + uid=uid, + tree=system_tree, + properties_dicts_list=[], + current_depth=1, + max_search_depth=search_depth, + ) + + if len(properties_dicts_list) == 0: + self._osl_server._logger.error(f"Node `{uid}` not found in the current system.") + return None + + return self._create_nodes_from_properties_dicts( + properties_dicts_list=properties_dicts_list + )[0] + + def find_nodes_by_name(self, name: str, search_depth: int = 1) -> Tuple[Node, ...]: + """Find nodes with the specified name in the current system. + + Search only in the current system descendant nodes. + + Parameters + ---------- + name : str + Name of the node. + search_depth: int, optional + Search depth of the current node subtree. Level-1 corresponds to direct children nodes + of the current system. + + Returns + ------- + Tuple[Node, ...] + Tuple of nodes with given name. + + 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. + TypeError + Raised when unknown type of component was found. + """ + project_tree = self._osl_server.get_full_project_tree_with_properties() + if self.uid == project_tree["projects"][0]["system"]["uid"]: + system_tree = project_tree["projects"][0]["system"] + else: + system_tree = System._find_subtree( + tree=project_tree["projects"][0]["system"], + uid=self.uid, + ) + properties_dicts_list = System._find_nodes_with_name( + name=name, + tree=system_tree, + properties_dicts_list=[], + current_depth=1, + max_search_depth=search_depth, + ) + + if len(properties_dicts_list) == 0: + self._osl_server._logger.error(f"Node `{name}` not found in the current system.") + return tuple() + + return self._create_nodes_from_properties_dicts(properties_dicts_list=properties_dicts_list) + + def get_nodes(self) -> Tuple[Node, ...]: + """Get direct children nodes. + + Returns + ------- + Tuple[Node, ...] + Current system nodes. + + 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. + """ + return self._create_nodes_from_properties_dicts( + properties_dicts_list=self._get_nodes_dicts() + ) + + def _get_nodes_dicts(self) -> List[dict]: + """Get children nodes data. + + Returns + ------- + List[dict] + List of dictionaries with children nodes data. + + 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. + RuntimeError + Raised when the system wasn't located in the project tree. + """ + project_tree = self._osl_server.get_full_project_tree_with_properties() + if self.uid == project_tree["projects"][0]["system"]["uid"]: + system_tree = project_tree["projects"][0]["system"] + else: + system_tree = System._find_subtree( + tree=project_tree["projects"][0]["system"], + uid=self.uid, + ) + if len(system_tree) == 0: + raise RuntimeError(f"System `{self.uid}` wasn't found.") + + children_dicts_list = [] + for node in system_tree["nodes"]: + children_dicts_list.append( + { + "type": node["type"], + "name": node["name"], + "uid": node["uid"], + "kind": node["kind"], + } + ) + return children_dicts_list + + @staticmethod + def _find_nodes_with_name( + name: str, + tree: dict, + properties_dicts_list: List[dict], + current_depth: int, + max_search_depth: int, + ) -> List[dict]: + """Find nodes with specified name. + + Parameters + ---------- + name : str + Nodes name. + tree : dict + Tree, where nodes with specified name are supposed to be searched. + properties_dicts_list : dict + Dictionary with properties. + current_depth: int + Current depth of search. + max_search_depth: int + Maximum depth of search. + + Returns + ------- + dict + Dictionary with necessary info for creation of Node. + """ + for node in tree["nodes"]: + if node["name"] == name: + properties_dicts_list.append( + { + "type": node["type"], + "name": node["name"], + "uid": node["uid"], + "parent_uid": tree["uid"], + "parent_name": tree["name"], + "kind": node["kind"], + } + ) + if node["kind"] == "system" and current_depth < max_search_depth: + System._find_nodes_with_name( + name=name, + tree=node, + properties_dicts_list=properties_dicts_list, + current_depth=current_depth + 1, + max_search_depth=max_search_depth, + ) + return properties_dicts_list + + @staticmethod + def _find_node_with_uid( + uid: str, + tree: dict, + properties_dicts_list: List[dict], + current_depth: int, + max_search_depth: int, + ) -> List[dict]: + """Find node with specified uid. + + Parameters + ---------- + uid : str + Nodes uid. + tree : dict + Tree, where node with specified uid is supposed to be searched. + properties_dicts_list : List[dict] + Dictionary with properties. + current_depth: int + Current depth of search. + max_search_depth: int + Maximum depth of search. + + Returns + ------- + dict + Dictionary with necessary info for creation of Node. + """ + for node in tree["nodes"]: + if node["uid"] == uid: + properties_dicts_list.append( + { + "type": node["type"], + "name": node["name"], + "uid": node["uid"], + "parent_uid": tree["uid"], + "parent_name": tree["name"], + "kind": node["kind"], + } + ) + if node["kind"] == "system" and current_depth < max_search_depth: + System._find_node_with_uid( + uid=uid, + tree=node, + properties_dicts_list=properties_dicts_list, + current_depth=current_depth + 1, + max_search_depth=max_search_depth, + ) + return properties_dicts_list + + @staticmethod + def _find_subtree(tree: dict, uid: str) -> dict: + """Find subtree with root node matching given uid. + + Parameters + ---------- + tree: dict + Dictionary with parent structure. + uid: str + Uid of the subtree root node. + + Returns + ------- + dict + Dictionary representing found subtree. + """ + for node in tree["nodes"]: + if node["uid"] == uid: + return node + if node["kind"] == "system": + System._find_subtree(tree=node, uid=uid) + + +class ParametricSystem(System): + """Class responsible for creation and operations on parametric system.""" + + def __init__( + self, + uid: str, + osl_server: OslServer, + ) -> None: + """Create a new instance of ``ParametricSystem``. + + Parameters + ---------- + uid: str + Uid. + osl_server: OslServer + Object providing access to optiSLang server. + """ + super().__init__( + uid=uid, + osl_server=osl_server, + ) + self.__parameter_manager = ParameterManager(uid, osl_server) + + @property + def parameter_manager(self) -> ParameterManager: + """Get instance of the ``ParameterManager`` class. + + Returns + ------- + ParameterManager + Instance of the ``ParameterManager`` class. + """ + return self.__parameter_manager + + +class RootSystem(ParametricSystem): + """Class responsible for creation and operations on project system.""" + + def __init__( + self, + uid: str, + osl_server: OslServer, + ) -> None: + """Create a new instance of ``RootSystem``. + + Parameters + ---------- + uid: str + Uid. + osl_server: OslServer + Object providing access to optiSLang server. + """ + super().__init__( + uid=uid, + osl_server=osl_server, + ) + + def evaluate_design(self, design: Design) -> Design: + """Evaluate given design. + + Parameters + ---------- + design: Design + Instance of ``Design`` class with defined parameters. + + Returns + ------- + Design + Evaluated design. + + 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. + """ + evaluate_dict = {} + for parameter in design.parameters: + evaluate_dict[parameter.name] = parameter.value + output_dict = self._osl_server.evaluate_design(evaluate_dict=evaluate_dict) + design._receive_results(output_dict[0]) + + design_parameters = design.parameters_names + output_parameters = output_dict[0]["result_design"]["parameter_names"] + missing_parameters = RootSystem._compare_two_sets(output_parameters, design_parameters) + undefined_parameters = RootSystem._compare_two_sets(design_parameters, output_parameters) + + if undefined_parameters: + self._osl_server._logger.debug(f"Parameters ``{undefined_parameters}`` weren't used.") + if missing_parameters: + self._osl_server._logger.warning( + f"Parameters ``{missing_parameters}`` were missing, " + "reference values were used for evaluation and list of parameters will be updated." + ) + + for parameter in missing_parameters: + position = output_dict[0]["result_design"]["parameter_names"].index(parameter) + design.set_parameter_by_name( + parameter, + output_dict[0]["result_design"]["parameter_values"][position], + False, + ) + + return design + + def get_reference_design(self) -> Design: + """Get design with reference values of parameters. + + Returns + ------- + Design + Instance of ``Design`` class with defined parameters and reference values. + + 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. + """ + pm = self.parameter_manager + parameters = pm.get_parameters() + return Design(parameters=parameters) + + def get_missing_parameters_names(self, design: Design) -> Tuple[str, ...]: + """Get names of parameters which are missing in given design. + + Compare design parameters with root system's parameters. + + Parameters + ---------- + design: Design + Instance of ``Design`` class with defined parameters. + + Returns + ------- + Tuple[str, ...] + Names of parameters which are missing in the instance of ``Design`` class. + + 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. + """ + return RootSystem._compare_two_sets( + first=self.parameter_manager.get_parameters_names(), + second=design.parameters_names, + ) + + def get_undefined_parameters_names(self, design: Design) -> Tuple[str, ...]: + """Get names of parameters which are not defined in root system. + + Compare design parameters with root systems parameters. + + Parameters + ---------- + design: Design + Instance of ``Design`` class with defined parameters. + + Returns + ------- + Tuple[str, ...] + Names of parameters which are not defined in the root system. + + 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. + """ + return RootSystem._compare_two_sets( + first=design.parameters_names, + second=self.parameter_manager.get_parameters_names(), + ) + + @staticmethod + def _compare_two_sets(first: Iterable[str], second: Iterable[str]) -> Tuple[str, ...]: + """Get sorted asymmetric difference of two string sets. + + Execute difference of sets: ``first - second`` + + Parameters + ---------- + first: Iterable[str] + Iterable of strings. + second: Iterable[str] + Iterable of string. + + Returns + ------- + Tuple[str, ...] + Tuple with sorted difference. + """ + diff = list(set(first) - set(second)) + diff.sort() + return tuple(diff) diff --git a/src/ansys/optislang/core/optislang.py b/src/ansys/optislang/core/optislang.py index 68351a18d..090c5a2c7 100644 --- a/src/ansys/optislang/core/optislang.py +++ b/src/ansys/optislang/core/optislang.py @@ -1,13 +1,19 @@ """Contains Optislang class which provides python API for optiSLang application.""" +from __future__ import annotations + from pathlib import Path -from typing import Sequence, Tuple, Union +from typing import TYPE_CHECKING, Sequence, Tuple, Union from importlib_metadata import version from ansys.optislang.core import LOG -from ansys.optislang.core.osl_server import OslServer +from ansys.optislang.core.project import Project from ansys.optislang.core.tcp_osl_server import TcpOslServer +if TYPE_CHECKING: + from ansys.optislang.core.logging import OslLogger + from ansys.optislang.core.osl_server import OslServer + class Optislang: """Connects to the optiSLang application and provides an API to control it. @@ -99,6 +105,10 @@ def __init__( self.__shutdown_on_finished = shutdown_on_finished self.__logger = LOG.add_instance_logger(self.name, self, loglevel) self.__osl_server: OslServer = self.__init_osl_server("tcp") + project_uid = self.__osl_server.get_project_uid() + self.__project = ( + Project(osl_server=self.__osl_server, uid=project_uid) if project_uid else None + ) def __init_osl_server(self, server_type: str) -> OslServer: """Initialize optiSLang server. @@ -167,7 +177,7 @@ def __exit__(self, exc_type, exc_value, exc_tb): @property def name(self) -> str: - """Instance unique identifier.""" + """Get instance unique identifier.""" if not self.__name: if self.__host or self.__port: self.__name = f"optiSLang_{self.__host}:{self.__port}" @@ -176,8 +186,8 @@ def name(self) -> str: return self.__name @property - def log(self): - """Return instance logger.""" + def log(self) -> OslLogger: + """Get instance logger.""" return self.__logger @property @@ -199,8 +209,20 @@ def has_active_project(self) -> bool: TimeoutError Raised when the timeout float value expires. """ - return self.__osl_server.get_project_name() is not None + return self.__osl_server.get_project_uid() is not None + + @property + def project(self) -> Project: + """Get instance of the ``Project`` class. + + Returns + ------- + Project + Loaded project. If no project is loaded, returns ``None``. + """ + return self.__project + # close method doesn't work properly in optiSLang 2023R1, therefore it was commented out # def close(self) -> None: # """Close the current project. @@ -214,6 +236,7 @@ def has_active_project(self) -> bool: # Raised when the timeout float value expires. # """ # self.__osl_server.close() + # self.__project = None def dispose(self) -> None: """Terminate all local threads and unregister listeners. @@ -268,86 +291,6 @@ def get_osl_version(self) -> Tuple[Union[int, None], ...]: """ return self.__osl_server.get_osl_version() - def get_project_description(self) -> str: - """Get description of optiSLang project. - - Returns - ------- - str - optiSLang project description. If no project is loaded in the optiSLang, - returns ``None``. - - 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. - """ - return self.__osl_server.get_project_description() - - def get_project_location(self) -> Path: - """Get path to the optiSLang project file. - - Returns - ------- - Path - Path to the optiSLang project file. If no project is loaded in the optiSLang, - returns ``None``. - - 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. - """ - return self.__osl_server.get_project_location() - - def get_project_name(self) -> str: - """Get name of the optiSLang project. - - Returns - ------- - str - Name of the optiSLang project. If no project is loaded in the optiSLang, - returns ``None``. - - 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. - """ - return self.__osl_server.get_project_name() - - def get_project_status(self) -> str: - """Get status of the optiSLang project. - - Returns - ------- - str - optiSLang project status. If no project is loaded in the optiSLang, - returns ``None``. - - 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. - """ - return self.__osl_server.get_project_status() - def get_timeout(self) -> Union[float, None]: """Get current timeout value for execution of commands. @@ -404,6 +347,9 @@ def new(self) -> None: Raised when the timeout float value expires. """ self.__osl_server.new() + self.__project = Project( + osl_server=self.__osl_server, uid=self.__osl_server.get_project_uid() + ) def open( self, @@ -438,6 +384,9 @@ def open( Raised when the timeout float value expires. """ self.__osl_server.open(file_path=file_path, force=force, restore=restore, reset=reset) + self.__project = Project( + osl_server=self.__osl_server, uid=self.__osl_server.get_project_uid() + ) def reset(self) -> None: """Reset complete project. @@ -690,7 +639,6 @@ def stop(self, wait_for_finished: bool = True) -> None: self.__osl_server.stop(wait_for_finished) # stop_gently method doesn't work properly in optiSLang 2023R1, therefore it was commented out - # def stop_gently(self, wait_for_finished: bool = True) -> None: # """Stop project execution after the current design is finished. diff --git a/src/ansys/optislang/core/osl_server.py b/src/ansys/optislang/core/osl_server.py index bc7ad33b5..680d89fba 100644 --- a/src/ansys/optislang/core/osl_server.py +++ b/src/ansys/optislang/core/osl_server.py @@ -1,8 +1,9 @@ """Contains abstract optiSLang server class.""" +from __future__ import annotations from abc import ABC, abstractmethod from pathlib import Path -from typing import Sequence, Tuple, Union +from typing import Dict, List, Sequence, Tuple, Union class OslServer(ABC): @@ -24,7 +25,7 @@ class OslServer(ABC): # pass @abstractmethod - def dispose(self) -> None: + def dispose(self) -> None: # pragma: no cover """Terminate all local threads and unregister listeners. Raises @@ -39,7 +40,122 @@ def dispose(self) -> None: pass @abstractmethod - def get_osl_version_string(self) -> str: + def evaluate_design(self, evaluate_dict: Dict[str, float]) -> List[dict]: # pragma: no cover + """Evaluate requested design. + + Parameters + ---------- + evaluate_dict: Dict[str, float] + {'parName': value, ...} + + Returns + ------- + List[dict] + Output from optislang server. + + 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. + """ + pass + + @abstractmethod + def get_actor_info(self, uid: str) -> Dict: # pragma: no cover + """Get info about actor defined by uid. + + Parameters + ---------- + uid : str + Actor uid. + + Returns + ------- + Dict + Info about actor defined by uid. + + 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. + """ + pass + + @abstractmethod + def get_actor_properties(self, uid: str) -> Dict: # pragma: no cover + """Get properties of actor defined by uid. + + Parameters + ---------- + uid : str + Actor uid. + + Returns + ------- + Dict + Properties of actor defined by uid. + + 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. + """ + pass + + @abstractmethod + def get_full_project_tree_with_properties(self) -> Dict: # pragma: no cover + """Get full project tree with properties.. + + Returns + ------- + Dict + Properties of actor defined by uid. + + 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. + """ + pass + + @abstractmethod + def get_project_uid(self) -> str: # pragma: no cover + """Get project uid. + + Returns + ------- + str + Project uid. If no project is loaded in the optiSLang, returns `None`. + + 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. + """ + pass + + @abstractmethod + def get_osl_version_string(self) -> str: # pragma: no cover """Get version of used optiSLang. Returns @@ -59,7 +175,7 @@ def get_osl_version_string(self) -> str: pass @abstractmethod - def get_osl_version(self) -> Tuple[Union[int, None], ...]: + def get_osl_version(self) -> Tuple[Union[int, None], ...]: # pragma: no cover """Get version of used optiSLang. Returns @@ -80,7 +196,7 @@ def get_osl_version(self) -> Tuple[Union[int, None], ...]: pass @abstractmethod - def get_project_description(self) -> str: + def get_project_description(self) -> str: # pragma: no cover """Get description of optiSLang project. Returns @@ -101,7 +217,7 @@ def get_project_description(self) -> str: pass @abstractmethod - def get_project_location(self) -> Path: + def get_project_location(self) -> Path: # pragma: no cover """Get path to the optiSLang project file. Returns @@ -122,7 +238,7 @@ def get_project_location(self) -> Path: pass @abstractmethod - def get_project_name(self) -> str: + def get_project_name(self) -> str: # pragma: no cover """Get name of the optiSLang project. Returns @@ -143,7 +259,7 @@ def get_project_name(self) -> str: pass @abstractmethod - def get_project_status(self) -> str: + def get_project_status(self) -> str: # pragma: no cover """Get status of the optiSLang project. Returns @@ -164,7 +280,7 @@ def get_project_status(self) -> str: pass @abstractmethod - def get_timeout(self) -> Union[float, None]: + def get_timeout(self) -> Union[float, None]: # pragma: no cover """Get current timeout value for execution of commands. Returns @@ -188,7 +304,7 @@ def get_timeout(self) -> Union[float, None]: pass @abstractmethod - def get_working_dir(self) -> Path: + def get_working_dir(self) -> Path: # pragma: no cover """Get path to the optiSLang project working directory. Returns @@ -209,7 +325,7 @@ def get_working_dir(self) -> Path: pass @abstractmethod - def new(self) -> None: + def new(self) -> None: # pragma: no cover """Create a new project. Raises @@ -230,7 +346,7 @@ def open( force: bool = True, restore: bool = False, reset: bool = False, - ) -> None: + ) -> None: # pragma: no cover """Open a new project. Parameters @@ -259,7 +375,7 @@ def open( pass @abstractmethod - def reset(self): + def reset(self): # pragma: no cover """Reset complete project. Raises @@ -278,7 +394,7 @@ def run_python_script( self, script: str, args: Union[Sequence[object], None] = None, - ) -> Tuple[str, str]: + ) -> Tuple[str, str]: # pragma: no cover """Load a Python script in a project context and execute it. Parameters @@ -309,7 +425,7 @@ def run_python_file( self, file_path: Union[str, Path], args: Union[Sequence[object], None] = None, - ) -> Tuple[str, str]: + ) -> Tuple[str, str]: # pragma: no cover """Read python script from the file, load it in a project context and execute it. Parameters @@ -338,7 +454,7 @@ def run_python_file( pass @abstractmethod - def save(self) -> None: + def save(self) -> None: # pragma: no cover """Save the changed data and settings of the current project. Raises @@ -359,7 +475,7 @@ def save_as( force: bool = True, restore: bool = False, reset: bool = False, - ) -> None: + ) -> None: # pragma: no cover """Save and open the current project at a new location. Parameters @@ -388,7 +504,7 @@ def save_as( pass @abstractmethod - def save_copy(self, file_path: Union[str, Path]) -> None: + def save_copy(self, file_path: Union[str, Path]) -> None: # pragma: no cover """Save the current project as a copy to a location. Parameters @@ -408,7 +524,7 @@ def save_copy(self, file_path: Union[str, Path]) -> None: pass @abstractmethod - def set_timeout(self, timeout: Union[float, None] = None) -> None: + def set_timeout(self, timeout: Union[float, None] = None) -> None: # pragma: no cover """Set timeout value for execution of commands. Parameters @@ -434,7 +550,7 @@ def set_timeout(self, timeout: Union[float, None] = None) -> None: pass @abstractmethod - def shutdown(self, force: bool = False) -> None: + def shutdown(self, force: bool = False) -> None: # pragma: no cover """Shutdown the optiSLang server. Stop listening for incoming connections, discard pending requests, and shut down @@ -463,7 +579,9 @@ def shutdown(self, force: bool = False) -> None: pass @abstractmethod - def start(self, wait_for_started: bool = True, wait_for_finished: bool = True) -> None: + def start( + self, wait_for_started: bool = True, wait_for_finished: bool = True + ) -> None: # pragma: no cover """Start project execution. Parameters @@ -487,7 +605,7 @@ def start(self, wait_for_started: bool = True, wait_for_finished: bool = True) - pass @abstractmethod - def stop(self, wait_for_finished: bool = True) -> None: + def stop(self, wait_for_finished: bool = True) -> None: # pragma: no cover """Stop project execution. Parameters @@ -507,10 +625,26 @@ def stop(self, wait_for_finished: bool = True) -> None: """ pass - # stop_gently method doesn't work properly in optiSLang 2023R1, therefore it was commented out + # to be fixed in 2023R2: + # close method doesn't work properly in optiSLang 2023R1, therefore it was commented out + # @abstractmethod + # def close(self) -> None: # pragma: no cover + # """Close the current project. + # 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. + # """ + # pass + + # stop_gently method doesn't work properly in optiSLang 2023R1, therefore it was commented out # @abstractmethod - # def stop_gently(self, wait_for_finished: bool = True) -> None: + # def stop_gently(self, wait_for_finished: bool = True) -> None: # pragma: no cover # """Stop project execution after the current design is finished. # Parameters diff --git a/src/ansys/optislang/core/project.py b/src/ansys/optislang/core/project.py new file mode 100644 index 000000000..de8095609 --- /dev/null +++ b/src/ansys/optislang/core/project.py @@ -0,0 +1,151 @@ +"""Contains class ProjectSystem.""" +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from ansys.optislang.core.nodes import RootSystem + +if TYPE_CHECKING: + from ansys.optislang.core.osl_server import OslServer + + +class Project: + """Class containing root_system and queries related to the loaded project.""" + + def __init__(self, osl_server: OslServer, uid: str) -> None: + """Initialize an instance of the Project class. + + Parameters + ---------- + osl_server: OslServer + Instance of `OslServer`. + uid: str + Unique id of the loaded project. + """ + self.__osl_server = osl_server + self.__uid = uid + self.__root_system = RootSystem( + uid=uid, + osl_server=self.__osl_server, + ) + + def __str__(self): + """Return formatted string.""" + return ( + f"Name: {self.get_name()}\n" + f"Description: {self.get_description()}\n" + f"Status: {self.get_status()}\n" + f"Location: {str(self.get_location())}" + ) + + def get_description(self) -> str: + """Get description of the optiSLang project. + + Returns + ------- + str + optiSLang project description. If no project is loaded in the optiSLang, + returns ``None``. + + 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. + """ + return self.__osl_server.get_project_description() + + def get_location(self) -> Path: + """Get path to the optiSLang project file. + + Returns + ------- + Path + Path to the optiSLang project file. If no project is loaded in the optiSLang, + returns ``None``. + + 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. + """ + return self.__osl_server.get_project_location() + + def get_name(self) -> str: + """Get name of the optiSLang project. + + Returns + ------- + str + Name of the optiSLang project. If no project is loaded in the optiSLang, + returns ``None``. + + 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. + """ + return self.__osl_server.get_project_name() + + def get_status(self) -> str: + """Get status of the optiSLang project. + + Returns + ------- + str + optiSLang project status. If no project is loaded in the optiSLang, + returns ``None``. + + 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. + """ + return self.__osl_server.get_project_status() + + @property + def root_system(self) -> RootSystem: + """Return instance of the ``RootSystem`` class. + + Returns + ------- + RootSystem + Loaded projects root system. + + 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. + """ + return self.__root_system + + @property + def uid(self) -> str: + """Return uid of the optiSLang project. + + Returns + ------- + str + Uid of the loaded project. + """ + return self.__uid diff --git a/src/ansys/optislang/core/project_parametric.py b/src/ansys/optislang/core/project_parametric.py new file mode 100644 index 000000000..d05711dc7 --- /dev/null +++ b/src/ansys/optislang/core/project_parametric.py @@ -0,0 +1,1999 @@ +"""Contains classes Parameter, ParameterManager and Design.""" +from __future__ import annotations + +import copy +from enum import Enum +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Mapping, Sequence, Tuple, Union +import uuid + +if TYPE_CHECKING: + from ansys.optislang.core.osl_server import OslServer + + +class ParameterType(Enum): + """Available parameter types.""" + + DETERMINISTIC = 0 + STOCHASTIC = 1 + MIXED = 2 + DEPENDENT = 3 + + @staticmethod + def from_str(label: str) -> ParameterType: + """Convert string to ``ParameterType``. + + Parameters + ---------- + label: str + String that shall be converted. + + Returns + ------- + ParameterType + Instance of the ``ParameterType`` class. + + Raises + ------ + TypeError + Raised when inappropriate type of ``label`` was given. + ValueError + Raised when inappropriate value of ``label`` was given. + """ + if not isinstance(label, str): + raise TypeError(f"String was expected, but `{type(label)}` was given.") + label = label.upper() + try: + return eval("ParameterType." + label) + except: + raise ValueError(f"Status `{label}` not available in ParameterType types.") + + +class ParameterResolution(Enum): + """Available parameter resolution kinds.""" + + # optimization (deterministic) + CONTINUOUS = 0 + ORDINALDISCRETE_VALUE = 1 + ORDINALDISCRETE_INDEX = 2 + NOMINALDISCRETE = 3 + # stochastic + DETERMINISTIC = 4 + MARGINALDISTRIBUTION = 5 + EMPIRICAL_DISCRETE = 6 + EMPIRICAL_CONTINUOUS = 7 + + @staticmethod + def from_str(label: str) -> ParameterResolution: + """Convert string to ``ParameterResolution``. + + Parameters + ---------- + label: str + String that shall be converted. + + Returns + ------- + ParameterResolution + Instance of the ``ParameterResolution`` class. + + Raises + ------ + TypeError + Raised when inappropriate type of ``label`` was given. + ValueError + Raised when inappropriate value of ``label`` was given. + """ + if not isinstance(label, str): + raise TypeError(f"String was expected, but `{type(label)}` was given.") + label = label.upper() + try: + return eval("ParameterResolution." + label) + except: + raise ValueError(f"Status `{label}` not available in ParameterResolution kinds.") + + +class ParameterValueType(Enum): + """Available parameter value types.""" + + UNINITIALIZED = 0 + BOOL = 1 + REAL = 2 + INTEGER = 3 + STRING = 4 + VARIANT = 5 + + @staticmethod + def from_str(label: str) -> ParameterValueType: + """Convert string to ParameterValueType. + + Parameters + ---------- + label: str + String that shall be converted. + + Returns + ------- + ParameterValueType + Instance of the ``ParameterValueType`` class. + + Raises + ------ + TypeError + Raised when inappropriate type of ``label`` was given. + ValueError + Raised when inappropriate value of ``label`` was given. + """ + if not isinstance(label, str): + raise TypeError(f"String was expected, but `{type(label)}` was given.") + label = label.upper() + try: + return eval("ParameterValueType." + label) + except: + raise ValueError(f"Status `{label}` not available in ParameterValueType types.") + + +class DistributionType(Enum): + """Available distribution types.""" + + EXTERNALCOHERENCE = 0 + UNTYPED = 1 + EXTERNAL = 2 + UNIFORM = 3 + NORMAL = 4 + TRUNCATEDNORMAL = 5 + LOGNORMAL = 6 + EXPONENTIAL = 7 + RAYLEIGH = 8 + SMALL_I = 9 + LARGE_I = 10 + SMALL_II = 11 + LARGE_II = 12 + SMALL_III = 13 + LARGE_III = 14 + TRIANGULAR = 15 + BETA = 16 + CHI_SQUARE = 17 + ERLANG = 18 + FISHER_F = 19 + GAMMA = 20 + PARETO = 21 + WEIBULL = 22 + EXTREME_VALUE = 23 + STUDENTS_F = 24 + INVERSE_NORMAL = 25 + LOG_GAMMA = 26 + LOG_NORMAL = 27 + LORENTZ = 28 + FISHER_TIPPETT = 29 + GUMBEL = 30 + FISHER_Z = 31 + LAPLACE = 32 + LEVY = 33 + LOGISTIC = 34 + ROSSI = 35 + FRECHET = 36 + MAX_TYPE = 37 + POLYMAP = 38 + KERNEL = 39 + BERNOULLI = 40 + LOG_UNIFORM = 41 + DISCRETE = 42 + MULTIUNIFORM = 43 + LAMBDA = 44 + POISSON = 45 + + @staticmethod + def from_str(label: str) -> DistributionType: + """Convert string to DistributionType. + + Parameters + ---------- + label: str + String that shall be converted. + + Returns + ------- + DistributionType + Instance of the ``DistributionType`` class. + + Raises + ------ + TypeError + Raised when inappropriate type of ``label`` was given. + ValueError + Raised when inappropriate value of ``label`` was given. + """ + if not isinstance(label, str): + raise TypeError(f"String was expected, but `{type(label)}` was given.") + label = label.upper() + try: + return eval("DistributionType." + label) + except: + raise ValueError(f"Status `{label}` not available in DistributionType types.") + + +class Parameter: + """This class stores parameters data.""" + + def __init__( + self, + name: str, + reference_value: Union[bool, float, str, None], + id: str, + const: bool, + type_: Union[ParameterType, str], + ) -> None: + """Create a new instance of ``Parameter``. + + Parameters + ---------- + name: str + Name of the parameter. + reference_value: Union[bool, float, str, None] + Parameter's reference value. + id: str + Parameter's unique id. + const: bool + Determines whether is parameter constant. + type: Union[ParameterType, str] + Parameter's type. + """ + self.name = name + self.reference_value = reference_value + self.id = id + self.const = const + if isinstance(type_, str): + type_ = ParameterType.from_str(type_) + if isinstance(type_, ParameterType): + self.__type = type_ + else: + raise TypeError( + "Type ``Union[ParameterType, str]`` was expected, but type: " + f"``{type(type_)}`` was given." + ) + + def __eq__(self, other: Parameter) -> bool: + """Compare properties of two instances of the ``Parameter`` class. + + Parameters + ---------- + other: Parameter + Parameter for comparison. + + Returns + ------- + bool + ``True`` if all properties match; ``False`` otherwise. + """ + if type(self) == type(other): + checks = {} + checks["name"] = self.name == other.name + checks["reference_value"] = self.reference_value == other.reference_value + checks["id"] = self.id == other.id + checks["const"] = self.const == other.const + checks["type"] = self.type == other.type + return False not in checks.values() + else: + return False + + def __deepcopy__(self, memo) -> Parameter: + """Return deep copy of given parameter.""" + return Parameter( + self.name, + self.reference_value, + self.id, + self.const, + self.type, + ) + + @property + def name(self) -> str: + """Get parameter's name.""" + return self.__name + + @name.setter + def name(self, name: str) -> None: + """Set parameter's name. + + Parameters + ---------- + name: str + Name of the ``Parameter``. + + Raises + ------ + TypeError + Raised when invalid type of ``name`` was given. + """ + if not isinstance(name, str): + raise TypeError( + f"Type of ``name`` must be ``str`` but type: ``{type(name)}`` was given." + ) + self.__name = name + + @property + def id(self) -> str: + """Get parameter's id.""" + return self.__id + + @id.setter + def id(self, id: str) -> None: + """Set parameter's id. + + Parameters + ---------- + id: str + Id of the ``Parameter``. + + Raises + ------ + TypeError + Raised when invalid type of ``id`` was given. + """ + if not isinstance(id, str): + raise TypeError(f"Type of ``id`` must be ``str`` but type: ``{type(id)}`` was given.") + self.__id = id + + @property + def const(self) -> bool: + """Get info whether ``Parameter`` is constant.""" + return self.__const + + @const.setter + def const(self, is_const: bool) -> None: + """Set whether ``Parameter`` is constant. + + Parameters + ---------- + is_const: bool + ``True`` or ``False``. + + Raises + ------ + TypeError + Raised when invalid type of ``is_const`` was given. + """ + if not isinstance(is_const, bool): + raise TypeError( + f"Type of ``is_const`` must be ``bool`` but type: ``{type(is_const)}`` was given." + ) + self.__const = is_const + + @property + def reference_value( + self, + ) -> Union[bool, float, str, None]: + """Get ``Parameter``'s reference value.""" + return self.__reference_value + + @reference_value.setter + def reference_value( + self, + reference_value: Union[bool, float, str, None], + ) -> None: + """Set ``Parameter``'s reference value. + + Parameters + ---------- + reference_value: Union[bool, float, str, None] + Reference value of the ``Parameter``. + + Raises + ------ + TypeError + Raised when invalid type of ``reference_value`` was given. + """ + self.__reference_value = reference_value + + @property + def type(self) -> ParameterType: + """Get type of the ``Parameter``.""" + return self.__type + + @staticmethod + def from_dict(par_dict: dict) -> Parameter: + """Create an instance of ``Parameter`` class from optiSLang output. + + Parameters + ---------- + par_dict : dict + Output from the optiSLang server. + + Returns + ------- + Parameter + Instance of the ``Parameter`` class. + + Raises + ------ + TypeError + Raised when undefined type of parameter is given. + """ + type = ParameterType.from_str(par_dict["type"]["value"]) + + if type == ParameterType.DEPENDENT: + return DependentParameter.from_dict(par_dict=par_dict) + elif type == ParameterType.DETERMINISTIC: + return OptimizationParameter.from_dict(par_dict=par_dict) + elif type == ParameterType.STOCHASTIC: + return StochasticParameter.from_dict(par_dict=par_dict) + elif type == ParameterType.MIXED: + return MixedParameter.from_dict(par_dict=par_dict) + else: + raise TypeError("Undefined type of parameter.") + + +class OptimizationParameter(Parameter): + """This class stores ``OptimizationParameter``'s data.""" + + def __init__( + self, + name: str, + reference_value: Union[bool, float, str, None] = 0, + reference_value_type: ParameterValueType = ParameterValueType.REAL, + id: str = str(uuid.uuid4()), + const: bool = False, + type: Union[ParameterType, str] = ParameterType.DETERMINISTIC, + deterministic_resolution: Union[ParameterResolution, str] = ParameterResolution.CONTINUOUS, + range: Union[Sequence[float, float], Sequence[Sequence[float]]] = (-1, 1), + ) -> None: + """Create a new instance of ``OptimizationParameter``. + + Parameters + ---------- + name: str + Name of the parameter. + reference_value: Union[bool, float, str, None], optional + Parameter's reference value. + reference_value_type: ParameterValueType, optional + Type of the reference value. + id: str, optional + Parameter's unique id. + const: bool, optional + Determines whether is parameter constant. + type: Union[ParameterType, str], optional + Parameter's type. + deterministic_resolution: Union[ParameterResolution, str], optional + Parameter's deterministic resolution. + range: Union[Sequence[float, float], Sequence[Sequence[float]]], optional + Either 2 values specifying range or list of discrete values. + """ + super().__init__( + name=name, + reference_value=reference_value, + id=id, + const=const, + type_=type, + ) + self.reference_value_type = reference_value_type + self.deterministic_resolution = deterministic_resolution + self.range = tuple(range) + + def __eq__(self, other: OptimizationParameter) -> bool: + r"""Compare properties of two instances of the ``OptimizationParameter`` class. + + Parameters + ---------- + other: OptimizationParameter + Parameter for comparison. + + Returns + ------- + bool + ``True`` if all properties match; ``False`` otherwise. + """ + if type(self) == type(other): + checks = {} + checks["name"] = self.name == other.name + checks["reference_value"] = self.reference_value == other.reference_value + checks["reference_value_type"] = self.reference_value_type == other.reference_value_type + checks["id"] = self.id == other.id + checks["const"] = self.const == other.const + checks["type"] = self.type == other.type + checks["deterministic_resolution"] = ( + self.deterministic_resolution == other.deterministic_resolution + ) + checks["range"] = self.range == other.range + return False not in checks.values() + else: + return False + + def __deepcopy__(self, memo) -> OptimizationParameter: + """Return deep copy of the optimization parameter.""" + return OptimizationParameter( + self.name, + self.reference_value, + self.id, + self.const, + self.type, + self.reference_value_type, + self.deterministic_resolution, + copy.deepcopy(self.range), + ) + + @property + def reference_value_type(self) -> ParameterValueType: + """Get ``reference_value_type``.""" + return self.__reference_value_type + + @reference_value_type.setter + def reference_value_type(self, type_: Union[ParameterValueType, str]) -> None: + """Set type of the ``reference_value``. + + Parameters + ---------- + type_ : Union[ParameterValueType, str] + Type of the ``reference_value``. + + Raises + ------ + TypeError + Raised when inappropriate type of ``type_`` was given. + """ + if isinstance(type_, str): + type_ = ParameterValueType.from_str(type_) + if isinstance(type_, ParameterValueType): + self.__reference_value_type = type_ + else: + raise TypeError( + "Type ``Union[ParameterValueType, str]`` was expected, but type: " + f"``{type(type_)}`` was given." + ) + + @property + def deterministic_resolution(self) -> ParameterResolution: + """Get kind of the ``deterministic_resolution``.""" + return self.__deterministic_resolution + + @deterministic_resolution.setter + def deterministic_resolution( + self, deterministic_resolution: Union[ParameterResolution, str] + ) -> None: + """Set type of the ``deterministic_resolution``. + + Parameters + ---------- + deterministic_resolution : Union[ParameterResolution, str] + Kind of the ``deterministic_resolution``. + + Raises + ------ + TypeError + Raised when inappropriate type of ``deterministic_resolution`` was given. + """ + if isinstance(deterministic_resolution, str): + deterministic_resolution = ParameterResolution.from_str(deterministic_resolution) + if isinstance(deterministic_resolution, ParameterResolution): + self.__deterministic_resolution = deterministic_resolution + else: + raise TypeError( + "Type Union[ParameterResolution, str] was expected, but type: " + f"``{type(deterministic_resolution)}`` was given." + ) + + @property + def range(self) -> Union[Tuple[float, float], Tuple[Tuple[float, ...]]]: + """Get ``OptimizationParameter``'s range.""" + return self.__range + + @range.setter + def range(self, range: Union[Sequence[float, float], Sequence[Sequence[float]]]) -> None: + """Set ``OptimizationParameter``'s range. + + Parameters + ---------- + range: Union[Sequence[float, float], Sequence[Sequence[float]]] + ``OptimizationParameter``'s range. + """ + self.__range = range + + @staticmethod + def from_dict(par_dict: dict) -> OptimizationParameter: + """Create an instance of the ``OptimizationParameter`` class from optiSLang output. + + Parameters + ---------- + par_dict : dict + Output from optiSLang server. + + Returns + ------- + OptimizationParameter + Instance of the ``OptimizationParameter`` class. + """ + name = par_dict["name"] + id = par_dict["id"] + const = par_dict["const"] + type = ParameterType.from_str(par_dict["type"]["value"]) + reference_value_type = ParameterValueType.from_str( + par_dict.get("deterministic_property", {}).get("domain_type", {}).get("value", None) + ) + reference_value = par_dict["reference_value"] + deterministic_resolution = ParameterResolution.from_str( + par_dict.get("deterministic_property", {}).get("kind", {}).get("value", None) + ) + # range for continuous parameters, stored as (val1, val2) + if deterministic_resolution == ParameterResolution.CONTINUOUS: + range = ( + par_dict.get("deterministic_property", {}).get("lower_bound", None), + par_dict.get("deterministic_property", {}).get("upper_bound", None), + ) + # discrete values otherwise, stored as ([val1, val2, val3 ..]) + else: + range = (par_dict.get("deterministic_property", {}).get("discrete_states", None),) + return OptimizationParameter( + name=name, + reference_value=reference_value, + reference_value_type=reference_value_type, + id=id, + const=const, + type=type, + deterministic_resolution=deterministic_resolution, + range=range, + ) + + def to_dict(self) -> dict: + """Convert an instance of the ``OptimizationParameter`` to dictionary. + + Returns + ------- + dict + Input dictionary for the optiSLang server. + """ + if len(self.range) == 1: + range_dict = {"discrete_states": self.range[0]} + else: + range_dict = { + "lower_bound": self.range[0], + "upper_bound": self.range[1], + } + output_dict = { + "active": True, + "const": self.const if self.const is not None else False, + "deterministic_property": { + "domain_type": {"value": self.reference_value_type.name.lower()}, + "kind": {"value": self.deterministic_resolution.name.lower()}, + }, + "id": self.id, + "modifiable": False, + "name": self.name, + "reference_value": self.reference_value, + "removable": True, + "type": {"value": "deterministic"}, + "unit": "", + } + output_dict["deterministic_property"].update(range_dict) + return output_dict + + +class StochasticParameter(Parameter): + """This class stores ``StochasticParameter``'s data.""" + + def __init__( + self, + name: str, + reference_value: Union[bool, float, str, None] = 0, + reference_value_type: Union[ParameterValueType, str] = ParameterValueType.REAL, + id: str = str(uuid.uuid4()), + const: bool = False, + type: Union[ParameterType, str] = ParameterType.STOCHASTIC, + stochastic_resolution: Union[ + ParameterResolution, str + ] = ParameterResolution.MARGINALDISTRIBUTION, + distribution_type: Union[DistributionType, str] = DistributionType.NORMAL, + distribution_parameters: Sequence[float] = (0, 1), + ) -> None: + """Create a new instance of the ``StochasticParameter`` class. + + Parameters + ---------- + name: str + Name of the parameter. + reference_value: Union[bool, float, str, None, Tuple[Any, ParameterValueType]], optional + Parameter's reference value. + reference_value_type: ParameterValueType, optional + Type of the reference value. + id: str, optional + Parameter's unique id. + const: bool, optional + Determines whether is parameter constant. + type: Union[ParameterType, str], optional + Parameter's type. + stochastic_resolution: Union[ParameterResolution, str], optional + Parameter's stochastic resolution. + distribution_type: Union[DistributionType, str], optional + Parameter's distribution type. + distribution_parameters: Sequence[float], optional + Distribution's parameters. + """ + super().__init__( + name=name, + reference_value=reference_value, + id=id, + const=const, + type_=type, + ) + self.reference_value_type = reference_value_type + self.stochastic_resolution = stochastic_resolution + self.distribution_type = distribution_type + self.distribution_parameters = tuple(distribution_parameters) + + def __eq__(self, other: StochasticParameter) -> bool: + r"""Compare properties of two instances of the ``StochasticParameter`` class. + + Parameters + ---------- + other: StochasticParameter + Parameter for comparison. + + Returns + ------- + bool + ``True`` if all properties match; ``False`` otherwise. + """ + if type(self) == type(other): + checks = {} + checks["name"] = self.name == other.name + checks["reference_value"] = self.reference_value == other.reference_value + checks["reference_value_type"] = self.reference_value_type == other.reference_value_type + checks["id"] = self.id == other.id + checks["const"] = self.const == other.const + checks["type"] = self.type == other.type + checks["stochastic_resolution"] = ( + self.stochastic_resolution == other.stochastic_resolution + ) + checks["distribution_type"] = self.distribution_type == other.distribution_type + checks["distribution_parameters"] = ( + self.distribution_parameters == other.distribution_parameters + ) + return False not in checks.values() + else: + return False + + def __deepcopy__(self, memo) -> StochasticParameter: + """Return deep copy of the stochastic parameter.""" + return StochasticParameter( + self.name, + self.reference_value, + self.id, + self.const, + self.type, + self.reference_value_type, + self.stochastic_resolution, + self.distribution_type, + self.distribution_parameters, + ) + + @property + def reference_value_type(self) -> ParameterValueType: + """Get type of the ``reference_value``.""" + return self.__reference_value_type + + @reference_value_type.setter + def reference_value_type(self, type_: Union[ParameterValueType, str]) -> None: + """Set type of the ``reference_value``. + + Parameters + ---------- + type_ : Union[ParameterValueType, str] + Type of the ``reference_value`` + + Raises + ------ + TypeError + Raised when inappropriate type of ``type_`` was given. + """ + if isinstance(type_, str): + type_ = ParameterValueType.from_str(type_) + if isinstance(type_, ParameterValueType): + self.__reference_value_type = type_ + else: + raise TypeError( + "Type ``Union[ParameterValueType, str]`` was expected, but type: " + f"``{type(type_)}`` was given." + ) + + @property + def stochastic_resolution(self) -> ParameterResolution: + """Get kind of the ``stochastic_resolution``.""" + return self.__stochastic_resolution + + @stochastic_resolution.setter + def stochastic_resolution(self, stochastic_resolution: Union[ParameterResolution, str]) -> None: + """Set kind of the ``stochastic_resolution``. + + Parameters + ---------- + stochastic_resolution : Union[ParameterResolution, str] + Kind of the ``stochastic_resolution``. + + Raises + ------ + TypeError + Raised when inappropriate type of ``stochastic_resolution`` was given. + """ + if isinstance(stochastic_resolution, str): + stochastic_resolution = ParameterResolution.from_str(stochastic_resolution) + if isinstance(stochastic_resolution, ParameterResolution): + self.__stochastic_resolution = stochastic_resolution + else: + raise TypeError( + "Type Union[ParameterResolution, str] was expected, but type: " + f"``{type(stochastic_resolution)}`` was given." + ) + + @property + def distribution_type(self) -> DistributionType: + """Get ``distribution_type``.""" + return self.__distribution_type + + @distribution_type.setter + def distribution_type(self, distribution_type: Union[DistributionType, str]) -> None: + """Set ``distribution_type``. + + Parameters + ---------- + distribution_type : Union[DistributionType, str] + Type of the distribution. + + Raises + ------ + TypeError + Raised when inappropriate type of ``distribution_type`` was given. + """ + if isinstance(distribution_type, str): + distribution_type = DistributionType.from_str(distribution_type) + if isinstance(distribution_type, DistributionType): + self.__distribution_type = distribution_type + else: + raise TypeError( + "Type Union[DistributionType, str] was expected, but type: " + f"``{type(distribution_type)}`` was given." + ) + + @property + def distribution_parameters(self) -> Sequence[float]: + """Get ``distribution_parameters``.""" + return self.__distribution_parameters + + @distribution_parameters.setter + def distribution_parameters(self, parameters: Sequence[float]) -> None: + """Set ``distribution_parameters``. + + Parameters + ---------- + parameters : Sequence[float] + Distribution's parameters. + """ + self.__distribution_parameters = parameters + + @staticmethod + def from_dict(par_dict: dict) -> StochasticParameter: + """Create an instance of the ``StochasticParameter`` class from the optiSLang server output. + + Parameters + ---------- + par_dict : dict + Output from the optiSLang server. + + Returns + ------- + StochasticParameter + Instance of the ``StochasticParameter`` class. + """ + name = par_dict["name"] + reference_value = par_dict["reference_value"] + id = par_dict["id"] + const = par_dict["const"] + type = ParameterType.from_str(par_dict["type"]["value"]) + stochastic_resolution = ParameterResolution.from_str( + par_dict.get("stochastic_property", {}).get("kind", {}).get("value", None) + ) + distribution_type = DistributionType.from_str( + par_dict.get("stochastic_property", {}).get("type", {}).get("value", None) + ) + distribution_parameters = tuple( + par_dict.get("stochastic_property", {}).get("statistical_moments", None) + ) + return StochasticParameter( + name=name, + reference_value=reference_value, + reference_value_type=ParameterValueType.REAL, + id=id, + const=const, + type=type, + stochastic_resolution=stochastic_resolution, + distribution_type=distribution_type, + distribution_parameters=distribution_parameters, + ) + + def to_dict(self) -> dict: + """Convert an instance of the ``StochasticParameter`` to dictionary. + + Returns + ------- + dict + Input dictionary for the optiSLang server. + """ + return { + "active": True, + "const": self.const if self.const is not None else False, + "id": self.id, + "modifiable": False, + "name": self.name, + "reference_value": self.reference_value, + "removable": True, + "stochastic_property": { + "kind": {"value": self.stochastic_resolution.name.lower()}, + "statistical_moments": self.distribution_parameters, + "type": {"value": self.distribution_type.name.lower()}, + }, + "type": {"value": "stochastic"}, + "unit": "", + } + + +class MixedParameter(Parameter): + """This class stores ``MixedParameter``'s data.""" + + def __init__( + self, + name: str, + reference_value: Union[bool, float, str, None, Tuple[Any, ParameterValueType]] = 0, + reference_value_type: Union[ParameterValueType, str] = ParameterValueType.REAL, + id: str = str(uuid.uuid4()), + const: bool = False, + type: Union[ParameterType, str] = ParameterType.MIXED, + deterministic_resolution: Union[ParameterResolution, str] = ParameterResolution.CONTINUOUS, + range: Union[Sequence[float, float], Sequence[Sequence[float]]] = (-1, 1), + stochastic_resolution: Union[ + ParameterResolution, str + ] = ParameterResolution.MARGINALDISTRIBUTION, + distribution_type: Union[DistributionType, str] = DistributionType.NORMAL, + distribution_parameters: Sequence[float] = (0, 1), + ) -> None: + """Create a new instance of the ``MixedParameter`` class. + + Parameters + ---------- + name: str + Name of the parameter. + reference_value: Union[bool, float, str, None, Tuple[Any, ParameterValueType]], optional + Parameter's reference value. + reference_value_type: ParameterValueType, optional + Type of the reference value. + id: str, optional + Parameter's unique id. + const: bool, optional + Determines whether is parameter constant. + type: Union[ParameterType, str], optional + Parameter's type. + deterministic_resolution: Union[ParameterResolution, str], optional + Parameter's deterministic resolution. + range: Union[Sequence[float, float], Sequence[Sequence[float]]], optional + Either 2 values specifying range or list of discrete values. + stochastic_resolution: Union[ParameterResolution, str], optional + Parameter's stochastic resolution. + distribution_type: Union[DistributionType, str], optional + Parameter's distribution type. + distribution_parameters: Sequence[float, ...], optional + Distribution's parameters. + """ + super().__init__( + name=name, + reference_value=reference_value, + id=id, + const=const, + type_=type, + ) + self.reference_value_type = reference_value_type + self.deterministic_resolution = deterministic_resolution + self.range = tuple(range) + self.stochastic_resolution = stochastic_resolution + self.distribution_type = distribution_type + self.distribution_parameters = tuple(distribution_parameters) + + def __eq__(self, other: MixedParameter) -> bool: + """Compare properties of two instances of the ``MixedParameter`` class. + + Parameters + ---------- + other: MixedParameter + Parameter for comparison. + + Returns + ------- + bool + ``True`` if all properties match; ``False`` otherwise. + """ + if type(self) == type(other): + checks = {} + checks["name"] = self.name == other.name + checks["reference_value"] = self.reference_value == other.reference_value + checks["reference_value_type"] = self.reference_value_type == other.reference_value_type + checks["id"] = self.id == other.id + checks["const"] = self.const == other.const + checks["type"] = self.type == other.type + checks["deterministic_resolution"] = ( + self.deterministic_resolution == other.deterministic_resolution + ) + checks["range"] = self.range == other.range + checks["stochastic_resolution"] = ( + self.stochastic_resolution == other.stochastic_resolution + ) + checks["distribution_type"] = self.distribution_type == other.distribution_type + checks["distribution_parameters"] = ( + self.distribution_parameters == other.distribution_parameters + ) + return False not in checks.values() + else: + return False + + def __deepcopy__(self, memo) -> MixedParameter: + """Return deep copy of the instance of ``MixedParameter`` class.""" + return MixedParameter( + self.name, + self.reference_value, + self.id, + self.const, + self.type, + self.reference_value_type, + self.deterministic_resolution, + copy.deepcopy(self.range), + self.stochastic_resolution, + self.distribution_type, + copy.deepcopy(self.distribution_parameters), + ) + + @property + def reference_value_type(self) -> ParameterValueType: + """Get type of the ``reference_value``.""" + return self.__reference_value_type + + @reference_value_type.setter + def reference_value_type(self, type_: Union[ParameterValueType, str]) -> None: + """Set type of the ``reference_value``.""" + if isinstance(type_, str): + type_ = ParameterValueType.from_str(type_) + if isinstance(type_, ParameterValueType): + self.__reference_value_type = type_ + else: + raise TypeError( + "Type ``Union[ParameterValueType, str]`` was expected, but type: " + f"``{type(type_)}`` was given." + ) + + @property + def deterministic_resolution(self) -> ParameterResolution: + """Get kind of the ``deterministic_resolution``.""" + return self.__deterministic_resolution + + @deterministic_resolution.setter + def deterministic_resolution( + self, deterministic_resolution: Union[ParameterResolution, str] + ) -> None: + """Set kind of the ``deterministic_resolution``. + + Parameters + ---------- + deterministic_resolution : Union[ParameterResolution, str] + Kind of the ``deterministic_resolution``. + + Raises + ------ + TypeError + Raised when inappropriate type of ``deterministic_resolution`` was given. + """ + if isinstance(deterministic_resolution, str): + deterministic_resolution = ParameterResolution.from_str(deterministic_resolution) + if isinstance(deterministic_resolution, ParameterResolution): + self.__deterministic_resolution = deterministic_resolution + else: + raise TypeError( + "Type Union[ParameterResolution, str] was expected, but type: " + f"``{type(deterministic_resolution)}`` was given." + ) + + @property + def range(self) -> Union[Tuple[float, float], Tuple[Tuple[float, ...]]]: + """Get parameter's ``range``.""" + return self.__range + + @range.setter + def range(self, range: Union[Sequence[float, float], Sequence[Sequence[float]]]) -> None: + """Set parameter's ``range``. + + Parameters + ---------- + range : Union[Sequence[float, float], Sequence[Sequence[float]]] + ``MixedParameter``'s range. + """ + self.__range = range + + @property + def stochastic_resolution(self) -> ParameterResolution: + """Get kind of the ``stochastic_resolution``.""" + return self.__stochastic_resolution + + @stochastic_resolution.setter + def stochastic_resolution(self, stochastic_resolution: Union[ParameterResolution, str]) -> None: + """Set type of the ``stochastic_resolution``. + + Parameters + ---------- + stochastic_resolution : Union[ParameterResolution, str] + Kind of the ``stochastic_resolution``. + + Raises + ------ + TypeError + Raised when inappropriate type of ``stochastic_resolution`` was given. + """ + if isinstance(stochastic_resolution, str): + stochastic_resolution = ParameterResolution.from_str(stochastic_resolution) + if isinstance(stochastic_resolution, ParameterResolution): + self.__stochastic_resolution = stochastic_resolution + else: + raise TypeError( + "Type Union[ParameterResolution, str] was expected, but type: " + f"``{type(stochastic_resolution)}`` was given." + ) + + @property + def distribution_type(self) -> DistributionType: + """Get ``distribution_type``.""" + return self.__distribution_type + + @distribution_type.setter + def distribution_type(self, distribution_type: Union[DistributionType, str]) -> None: + """Set ``distribution_type``. + + Parameters + ---------- + distribution_type : Union[DistributionType, str] + Distribution's type. + + Raises + ------ + TypeError + Raised when inappropriate type of ``distribution_type`` was given. + """ + if isinstance(distribution_type, str): + distribution_type = DistributionType.from_str(distribution_type) + if isinstance(distribution_type, DistributionType): + self.__distribution_type = distribution_type + else: + raise TypeError( + "Type Union[DistributionType, str] was expected, but type: " + f"``{type(distribution_type)}`` was given." + ) + + @property + def distribution_parameters(self) -> Sequence[float]: + """Get ``distribution_parameters``.""" + return self.__distribution_parameters + + @distribution_parameters.setter + def distribution_parameters(self, parameters: Sequence[float]): + """Set ``distribution_parameters``. + + Parameters + ---------- + parameters : Sequence[float] + Distribution's parameters. + """ + self.__distribution_parameters = parameters + + @staticmethod + def from_dict(par_dict: dict) -> MixedParameter: + """Create and instance of the ``MixedParameter`` class from optiSLang output. + + Parameters + ---------- + par_dict : dict + Output from the optiSLang server. + + Returns + ------- + MixedParameter + Instance of the ``MixedParameter`` class. + """ + # create `Parameter` instance with mandatory parameters + name = par_dict["name"] + reference_value = par_dict["reference_value"] + id = par_dict["id"] + const = par_dict["const"] + type = ParameterType.from_str(par_dict["type"]["value"]) + reference_value_type = ParameterValueType.from_str( + par_dict.get("deterministic_property", {}).get("domain_type", {}).get("value", None) + ) + deterministic_resolution = ParameterResolution.from_str( + par_dict.get("deterministic_property", {}).get("kind", {}).get("value", None) + ) + stochastic_resolution = ParameterResolution.from_str( + par_dict.get("stochastic_property", {}).get("kind", {}).get("value", None) + ) + + # range for continuous parameters, stored as (val1, val2) + if deterministic_resolution == ParameterResolution.CONTINUOUS: + range = ( + par_dict.get("deterministic_property", {}).get("lower_bound", None), + par_dict.get("deterministic_property", {}).get("upper_bound", None), + ) + # discrete values otherwise, stored as ([val1, val2, val3 ..]) + else: + range = (par_dict.get("deterministic_property", {}).get("discrete_states", None),) + + distribution_type = DistributionType.from_str( + par_dict.get("stochastic_property", {}).get("type", {}).get("value", None) + ) + distribution_parameters = tuple( + par_dict.get("stochastic_property", {}).get("statistical_moments", None) + ) + return MixedParameter( + name=name, + reference_value=reference_value, + reference_value_type=reference_value_type, + id=id, + const=const, + type=type, + deterministic_resolution=deterministic_resolution, + stochastic_resolution=stochastic_resolution, + range=range, + distribution_type=distribution_type, + distribution_parameters=distribution_parameters, + ) + + def to_dict(self) -> dict: + """Convert an instance of the ``MixedParameter`` to dictionary. + + Returns + ------- + dict + Input dictionary for the optiSLang server. + """ + if len(self.range) == 1: + range_dict = {"discrete_states": self.range[0]} + else: + range_dict = { + "lower_bound": self.range[0], + "upper_bound": self.range[1], + } + output_dict = { + "active": True, + "const": self.const if self.const is not None else False, + "deterministic_property": { + "domain_type": {"value": self.reference_value_type.name.lower()}, + "kind": {"value": self.deterministic_resolution.name.lower()}, + }, + "id": self.id, + "modifiable": False, + "name": self.name, + "reference_value": self.reference_value if self.reference_value else 0, + "removable": True, + "stochastic_property": { + "kind": {"value": self.stochastic_resolution.name.lower()}, + "statistical_moments": self.distribution_parameters, + "type": {"value": self.distribution_type.name.lower()}, + }, + "type": {"value": "mixed"}, + "unit": "", + } + + output_dict["deterministic_property"].update(range_dict) + return output_dict + + +class DependentParameter(Parameter): + """This class stores ``DependentParameter``'s data.""" + + def __init__( + self, + name: str, + reference_value: Union[bool, float, str, None, Tuple[Any, ParameterValueType]] = 0, + id: str = str(uuid.uuid4()), + const: bool = False, + type: Union[ParameterType, str] = ParameterType.DEPENDENT, + operation: str = "0", + ) -> None: + """Create a new instance of the ``DependentParameter`` class. + + Parameters + ---------- + name: str + Name of the parameter. + reference_value: Union[bool, float, str, None, Tuple[Any, ParameterValueType]], optional + Parameter's reference value. + id: str, optional + Parameter's unique id. + const: bool, optional + Determines whether is parameter constant. + type: Union[ParameterType, str], optional + Parameter's type. + operation: str, optional + Mathematic expression to be evaluated. + """ + super().__init__( + name=name, + reference_value=reference_value, + id=id, + const=const, + type_=type, + ) + self.operation = operation + + def __eq__(self, other: DependentParameter) -> bool: + r"""Compare properties of two instances of the ``DependentParameter`` class. + + Parameters + ---------- + other: DependentParameter + Parameter for comparison. + + Returns + ------- + bool + ``True`` if all properties match; ``False`` otherwise. + """ + if type(self) == type(other): + checks = {} + checks["name"] = self.name == other.name + checks["reference_value"] = self.reference_value == other.reference_value + checks["id"] = self.id == other.id + checks["const"] = self.const == other.const + checks["type"] = self.type == other.type + checks["operation"] = self.operation == other.operation + return False not in checks.values() + else: + return False + + def __deepcopy__(self, memo) -> DependentParameter: + """Return deep copy of an instance of the ``DependentParameter`` class.""" + return DependentParameter( + self.name, + self.reference_value, + self.reference_value_type, + self.id, + self.const, + self.type, + self.operation, + ) + + @staticmethod + def from_dict(par_dict: dict) -> DependentParameter: + """Create an instance of the ``DependentParameter`` class from optiSLang output. + + Parameters + ---------- + par_dict : dict + Output from the optiSLang server. + + Returns + ------- + Parameter + Instance of the ``DependentParameter`` class. + """ + name = par_dict["name"] + reference_value = par_dict["reference_value"] + id = par_dict["id"] + const = par_dict["const"] + type = ParameterType.from_str(par_dict["type"]["value"]) + operation = par_dict.get("dependency_expression") + + return DependentParameter( + name=name, + reference_value=reference_value, + id=id, + const=const, + type=type, + operation=operation, + ) + + def to_dict(self) -> dict: + """Convert an instance of the ``DependentParameter`` class to dictionary. + + Returns + ------- + dict + Input dictionary for the optiSLang server. + + Raises + ------ + TypeError + Raised when parameter is modified to unknown type. + """ + return { + "active": True, + "const": self.const if self.const is not None else False, + "dependency_expression": self.operation if self.operation is not None else 0, + "id": self.id, + "modifiable": False, + "name": self.name, + "reference_value": self.reference_value if self.reference_value is not None else None, + "removable": True, + "type": {"value": "dependent"}, + "unit": "", + } + + +class ParameterManager: + """This class contains methods to obtain parameters.""" + + def __init__(self, uid: str, osl_server: OslServer) -> None: + """Initialize a new instance of the ``ParameterManager`` class. + + Parameters + ---------- + uid: str + Specific unique ID. + osl_server: OslServer + Object providing access to optiSLang server. + """ + self.__uid = uid + self.__osl_server = osl_server + + def __str__(self) -> str: + """Return ``ParameterManager``'s ``uid``.""" + return f"ParameterManager uid: {self.__uid}" + + def get_parameters(self) -> Tuple[Parameter, ...]: + """Get tuple of the system's parameters. + + Returns + ------- + Tuple[Parameter, ...] + Tuple of defined parameters. + + 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. + """ + props = self.__osl_server.get_actor_properties(uid=self.__uid) + container = props["properties"].get("ParameterManager", {}).get("parameter_container", []) + parameters = [] + for par_dict in container: + parameters.append(Parameter.from_dict(par_dict)) + return tuple(parameters) + + def get_parameters_names(self) -> Tuple[str, ...]: + """Get names of the system's parameters. + + Returns + ------- + Tuple[str, ...] + Tuple of defined parameters names. + + 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. + """ + props = self.__osl_server.get_actor_properties(uid=self.__uid) + container = props["properties"].get("ParameterManager", {}).get("parameter_container", []) + parameters_list = [] + for par in container: + parameters_list.append(par["name"]) + return tuple(parameters_list) + + +class DesignVariable: + """Class which stores information about design variable.""" + + def __init__( + self, + name: str, + value: Union[bool, float, complex, list, dict, None], + ) -> None: + """Initialize a new instance of the ``DesignVariable`` class. + + Parameters + ---------- + name: str + Variable's name. + value: Union[bool, float, complex, list, dict, None] + Variable's value. + """ + self.name = name + self.value = value + + def __deepcopy__(self, memo) -> DesignVariable: + """Return deep copy of an instance of the ``DesignVariable`` class.""" + return DesignVariable( + self.name, + copy.deepcopy(self.value), + ) + + def __eq__(self, other: DesignVariable) -> bool: + """Compare properties of two instances of the ``DesignVariable`` class. + + Parameters + ---------- + other: DesignVariable + Variable for comparison. + + Returns + ------- + bool + ``True`` if all properties match; ``False`` otherwise. + """ + if type(self) == type(other): + checks = {} + checks["name"] = self.name == other.name + checks["value"] = self.value == other.value + return False not in checks.values() + else: + return False + + @property + def name(self) -> str: + """Get variable's ``name``.""" + return self.__name + + @name.setter + def name(self, name: str) -> None: + """Set variable's ``name``. + + Parameters + ---------- + name : str + ``DesignVariable``'s name. + + Raises + ------ + TypeError + Raised when inappropriate type of ``name`` was given. + """ + if not isinstance(name, str): + raise TypeError(f"String was expected but type: ``{type(name)}`` was given.") + else: + self.__name = name + + @property + def value(self) -> Union[bool, float, complex, list, dict, None]: + """Get variable's ``value``.""" + return self.__value + + @value.setter + def value(self, value: Union[bool, float, complex, list, dict, None]) -> None: + """Set variable's ``value``. + + Parameters + ---------- + value : Union[bool, float, complex, list, dict, None] + ``DesignVariable``'s value. + """ + self.__value = value + + +class DesignStatus(Enum): + """Available design states.""" + + IDLE = 0 + PENDING = 1 + SUCCEEDED = 2 + NOT_SUCCEEDED = 3 + FAILED = 4 + + @staticmethod + def from_str(label: str) -> DesignStatus: + """Convert string to an instance of the ``DesignStatus`` class. + + Parameters + ---------- + label: str + String that shall be converted. + + Returns + ------- + DesignStatus + Instance of the ``DesignStatus`` class. + + Raises + ------ + TypeError + Raised when inappropriate type of ``label`` was given. + ValueError + Raised when inappropriate value of ``label`` was given. + """ + if not isinstance(label, str): + raise TypeError(f"String was expected, but `{type(label)}` was given.") + label = label.upper().replace(" ", "_") + try: + return eval("DesignStatus." + label) + except: + raise ValueError(f"Status `{label}` not available in DesignStatus states.") + + +class Design: + """Class which stores information about design point, exclusive for ``RootSystem``. + + Parameters + ---------- + parameters: Union[Dict[str, float], Iterable[Union[Parameter, DesignVariable]]], optional + Dictionary of parameters and it's values {'parname': value, ...} + or iterable of DesignVariables or Parameters. + + Examples + -------- + Get reference design: + + >>> from ansys.optislang.core import Optislang + >>> osl = Optislang() + >>> root_system = osl.project.root_system + >>> design = root_system.get_reference_design() + >>> design.set_parameter_by_name(parameter = 'a', value = 2) + >>> print(design) + >>> osl.dispose() + """ + + def __init__( + self, + parameters: Union[ + Mapping[str, Union[bool, str, float, None]], + Iterable[Union[Parameter, DesignVariable]], + ], + ) -> None: + """Initialize a new instance of the ``Design`` class.""" + self.__constraints: List[DesignVariable] = [] + self.__feasibility: Union[bool, None] = None + self.__id: int = None + self.__limit_states: List[DesignVariable] = [] + self.__objectives: List[DesignVariable] = [] + self.__parameters: List[DesignVariable] = [] + self.__responses: List[DesignVariable] = [] + self.__status: DesignStatus = DesignStatus.IDLE + self.__variables: List[DesignVariable] = [] + + if isinstance(parameters, dict): + for name, value in parameters.items(): + self.__parameters.append(DesignVariable(name=name, value=value)) + else: + for parameter in parameters: + if isinstance(parameter, Parameter): + value = parameter.reference_value + elif isinstance(parameter, DesignVariable): + value = parameter.value + else: + raise TypeError(f"Parameters type: ``{type(parameter)}`` is not supported.") + self.__parameters.append( + DesignVariable( + name=parameter.name, + value=value, + ) + ) + + def __str__(self) -> str: + """Return info about design.""" + return ( + f"ID: {self.id}\n" + f"Status: {self.__status.name}\n" + f"Feasibility: {self.__feasibility}\n" + f"Criteria:\n" + f" constraints: {self.constraints_names}\n" + f" objectives: {self.objectives_names}\n" + f" limit_states: {self.limit_states_names}\n" + f"Parameters: {self.parameters_names}\n" + f"Responses: {self.responses_names}\n" + f"Variables: {self.variables_names}\n" + ) + + @property + def feasibility(self) -> Union[bool, None]: + """Get design's ``feasibility``, ``None`` if not evaluated.""" + return self.__feasibility + + @property + def id(self) -> Union[int, None]: + """Get design's ``id``, ``None`` if not assigned.""" + return self.__id + + @property + def constraints(self) -> Tuple[DesignVariable, ...]: + """Get all design's ``constraints``.""" + return tuple(self.__constraints) + + @property + def constraints_names(self) -> Tuple[str, ...]: + """Get all ``constraint``'s names.""" + names = [] + for constraint in self.__constraints: + names.append(constraint.name) + return tuple(names) + + @property + def limit_states(self) -> Tuple[DesignVariable, ...]: + """Get all defined ``limit_states``.""" + return tuple(self.__limit_states) + + @property + def limit_states_names(self) -> Tuple[str, ...]: + """Get all ``constraint``'s names.""" + names = [] + for limit_state in self.__limit_states: + names.append(limit_state.name) + return tuple(names) + + @property + def objectives(self) -> Tuple[DesignVariable, ...]: + """Get all defined ``objectives``.""" + return tuple(self.__objectives) + + @property + def objectives_names(self) -> Tuple[str, ...]: + """Get all ``objective``'s names.""" + names = [] + for objective in self.__objectives: + names.append(objective.name) + return tuple(names) + + @property + def parameters(self) -> Tuple[DesignVariable, ...]: + """Get all ``parameters``.""" + return tuple(self.__parameters) + + @property + def parameters_names(self) -> Tuple[str, ...]: + """Get all ``parameter``'s names.""" + names = [] + for parameter in self.__parameters: + names.append(parameter.name) + return tuple(names) + + @property + def responses(self) -> Tuple[DesignVariable, ...]: + """Get all ``responses``.""" + return tuple(self.__responses) + + @property + def responses_names(self) -> Tuple[str, ...]: + """Get all ``response``'s names.""" + names = [] + for response in self.__responses: + names.append(response.name) + return tuple(names) + + @property + def status(self) -> DesignStatus: + """Get ``status`` of an instance of the ``Design`` class.""" + return self.__status + + @property + def variables(self) -> Tuple[DesignVariable, ...]: + """Get all ``variables``.""" + return tuple(self.__variables) + + @property + def variables_names(self) -> Tuple[str, ...]: + """Get all ``variable``'s names.""" + names = [] + for variable in self.__variables: + names.append(variable.name) + return tuple(names) + + def copy_unevaluated_design(self) -> Design: + """Create deep copy of unevaluated ``Design``. + + Returns + ------- + Design + Deep copy of unevaluated design. + """ + return Design(copy.deepcopy(self.parameters)) + + def remove_parameter(self, name: str) -> None: + """Remove parameter defined by name from ``Design``. + + Parameters + ---------- + name: str + Name of the parameter. + """ + index = self.__find_name_index(name, type_="parameter") + if index is not None: + self.__parameters.pop(index) + + def clear_parameters(self) -> None: + """Remove all defined ``parameters`` from design.""" + self.__parameters.clear() + + def __reset(self) -> None: + """Reset ``status``, ``feasibility`` and delete output values.""" + self.__status = DesignStatus.IDLE + self.__feasibility = None + self.__constraints.clear() + self.__limit_states.clear() + self.__objectives.clear() + self.__responses.clear() + self.__variables.clear() + + def set_parameter( + self, + parameter: Union[Parameter, DesignVariable], + reset_output: bool = True, + ) -> None: + """Set value of parameter, add new one if not specified. + + Parameters + ---------- + parameter: Union[Parameter, DesignVariable] + Instance of ``Parameter`` or ``DesignVariable`` class. + reset_output: bool, optional + Reset ``status``, ``feasibility`` and delete output values, defaults to ``True``. + + 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. + TypeError + Raised when invalid type of parameter is passed. + """ + if reset_output: + self.__reset() + + if isinstance(parameter, Parameter): + value = parameter.reference_value + elif isinstance(parameter, DesignVariable): + value = parameter.value + else: + raise TypeError(f"Invalid type of parameter: `{type(parameter)}`.") + + index = self.__find_name_index(name=parameter.name, type_="parameter") + if index is not None: + self.__parameters[index].value = value + else: + self.__parameters.append(DesignVariable(name=parameter.name, value=value)) + + def set_parameter_by_name( + self, + name: str, + value: Union[str, float, bool, None] = None, + reset_output: bool = True, + ) -> None: + """Set value of parameter, add new one if not specified. + + Parameters + ---------- + name: str + Name of the parameter. + value: float + Value of the parameter. + reset_output: bool, optional + Reset ``status``, ``feasibility`` and delete output values, defaults to ``True``. + + 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. + TypeError + Raised when invalid type of parameter is passed. + """ + if reset_output: + self.__reset() + + if isinstance(name, str): + index = self.__find_name_index(name=name, type_="parameter") + else: + raise TypeError(f"Invalid type of name: `{type(name)}`.") + + if index is not None: + self.__parameters[index].value = value + else: + self.__parameters.append(DesignVariable(name=name, value=value)) + + def _receive_results(self, results: Dict) -> None: + """Store received results. + + Parameters + ---------- + results: Dict + Output from ``evaluate_design`` server command. + """ + self.__reset() + self.__id = results["result_design"]["hid"] + self.__feasibility = results["result_design"]["feasible"] + self.__status = DesignStatus.from_str(results["result_design"]["status"]) + + # constraint + for position, constraint in enumerate(results["result_design"]["constraint_names"]): + self.__constraints.append( + DesignVariable( + name=constraint, + value=results["result_design"]["constraint_values"][position], + ) + ) + # limit state + for position, limit_state in enumerate(results["result_design"]["limit_state_names"]): + self.__limit_states.append( + DesignVariable( + name=limit_state, + value=results["result_design"]["limit_state_values"][position], + ) + ) + # objective + for position, objective in enumerate(results["result_design"]["objective_names"]): + self.__objectives.append( + DesignVariable( + name=objective, + value=results["result_design"]["objective_values"][position], + ) + ) + # responses + for position, response in enumerate(results["result_design"]["response_names"]): + self.__responses.append( + DesignVariable( + name=response, + value=results["result_design"]["response_values"][position], + ) + ) + # variables + for position, variable in enumerate(results["result_design"]["variable_names"]): + self.__variables.append( + DesignVariable( + name=variable, + value=results["result_design"]["variable_values"][position], + ) + ) + + def __find_name_index(self, name: str, type_: str) -> Union[int, None]: + """Find index of the criterion, parameter, response or variable with given name. + + Parameters + ---------- + name: str + Name of the criterion, parameter, response or variable. + type_: str + Union['constraint', 'limit_state', 'objective', 'parameter', 'response', 'variable'] + + Returns + ------- + Union[int, None] + Position in list, ``None`` if parameter wasn't found. + + Raises + ------ + TypeError + Raised when unknown type of ``type_`` was given. + RuntimeError + Raised when multiple instances of ``DesignVariable`` with the same name were found. + """ + indices = [] + if type_ == "constraint": + search_in = self.__constraints + elif type_ == "limit_state": + search_in = self.__limit_states + elif type_ == "objective": + search_in = self.__objectives + elif type_ == "parameter": + search_in = self.__parameters + elif type_ == "response": + search_in = self.__responses + elif type_ == "variable": + search_in = self.__variables + else: + raise TypeError(f"Unknown type_: ``{type(type_)}``.") + + for index, parameter in enumerate(search_in): + if parameter.name == name: + indices.append(index) + if len(indices) > 1: + raise RuntimeError(f"Name `{name}` of `{type_}` is not unique.") + elif len(indices) == 0: + return None + return indices[0] diff --git a/src/ansys/optislang/core/server_commands.py b/src/ansys/optislang/core/server_commands.py index a5777a1b9..440145065 100644 --- a/src/ansys/optislang/core/server_commands.py +++ b/src/ansys/optislang/core/server_commands.py @@ -220,20 +220,10 @@ def create_node( args = {} args["type"] = type_ args["name"] = name - # TODO: and/or The request must contain the type entry as well as the args entry, - # containing the name, parent_uid, and/or - # design_flow with optional arguments [ RECEIVE, SEND, RECEIVE_SEND ]. if parent_uid is not None: args["parent_uid"] = parent_uid - if design_flow is not None: - supported_values = ["RECEIVE", "SEND", "RECEIVE_SEND"] - if design_flow in supported_values: - args["design_flow"] = design_flow - else: - raise TypeError( - f"Unsuppored value of ``design_flow``: {design_flow}," - "supported values are: {supported_values}" - ) + if design_flow not in [None, "NONE"]: + args["design_flow"] = design_flow return _to_json(_gen_server_command(command=_CREATE_NODE, args=args, password=password)) diff --git a/src/ansys/optislang/core/tcp_osl_server.py b/src/ansys/optislang/core/tcp_osl_server.py index bfcdc14e1..0b720f50b 100644 --- a/src/ansys/optislang/core/tcp_osl_server.py +++ b/src/ansys/optislang/core/tcp_osl_server.py @@ -968,7 +968,7 @@ def _get_server_info(self) -> Dict: TimeoutError Raised when the timeout float value expires. """ - return self._send_command(queries.server_info()) + return self._send_command(queries.server_info(self.__password)) def _get_basic_project_info(self) -> Dict: """Get basic project info, like name, location, global settings and status. @@ -987,21 +987,7 @@ def _get_basic_project_info(self) -> Dict: TimeoutError Raised when the timeout float value expires. """ - return self._send_command(queries.basic_project_info()) - - # def close(self) -> None: - # """Close the current project. - - # 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. - # """ - # self._send_command(commands.close(password=self.__password)) + return self._send_command(queries.basic_project_info(self.__password)) def dispose(self) -> None: """Terminate all local threads and unregister listeners. @@ -1023,6 +1009,101 @@ def dispose(self) -> None: self.__dispose_all_listeners() self.__disposed = True + def evaluate_design(self, evaluate_dict: Dict[str, float]) -> List[dict]: + """Evaluate requested design. + + Parameters + ---------- + evaluate_dict: Dict[str, float] + {'parName': value, ...} + + Returns + ------- + List[dict] + Output from optislang server. + + 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. + """ + return self._send_command( + commands.evaluate_design(evaluate_dict, self.__password), + ) + + def get_actor_info(self, uid: str) -> Dict: + """Get info about actor defined by uid. + + Parameters + ---------- + uid : str + Actor uid. + + Returns + ------- + Dict + Info about actor defined by uid. + + 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. + """ + return self._send_command(queries.actor_info(uid=uid, password=self.__password)) + + def get_actor_properties(self, uid: str) -> Dict: + """Get properties of actor defined by uid. + + Parameters + ---------- + uid : str + Actor uid. + + Returns + ------- + Dict + Properties of actor defined by uid. + + 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. + """ + return self._send_command(queries.actor_properties(uid=uid, password=self.__password)) + + def get_full_project_tree_with_properties(self) -> Dict: + """Get full project tree with properties. + + Returns + ------- + Dict + Properties of actor defined by uid. + + 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. + """ + return self._send_command( + queries.full_project_tree_with_properties(password=self.__password) + ) + def get_osl_version_string(self) -> str: """Get version of used optiSLang. @@ -1186,6 +1267,27 @@ def get_project_status(self) -> str: return None return project_info.get("projects", [{}])[0].get("state", None) + def get_project_uid(self) -> str: + """Get project uid. + + Returns + ------- + str + Project uid. If no project is loaded in the optiSLang, returns `None`. + + 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. + """ + project_tree = self.get_full_project_tree_with_properties() + project_uid = project_tree.get("projects", [{}])[0].get("system", {}).get("uid", None) + return project_uid + def get_timeout(self) -> Union[float, None]: """Get current timeout value for execution of commands. @@ -1714,75 +1816,6 @@ def stop(self, wait_for_finished: bool = True) -> None: raise TimeoutError("Waiting for finished timed out.") self._logger.info(f"Successfully_finished: {successfully_finished}.") - # stop_gently method doesn't work properly in optiSLang 2023R1, therefore it was commented out - - # def stop_gently(self, wait_for_finished: bool = True) -> None: - # """Stop project execution after the current design is finished. - - # Parameters - # ---------- - # wait_for_finished : bool, optional - # Determines whether this function call should wait on the optiSlang to finish - # the command execution. I.e. don't continue on next line of python script after command - # was successfully sent to optiSLang but wait for execution of command inside optiSLang. - # Defaults to ``True``. - - # 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. - # """ - # if wait_for_finished: - # exec_finished_listener = self.__create_exec_finished_listener() - # exec_finished_listener.cleanup_notifications() - # wait_for_finished_queue = Queue() - # exec_finished_listener.add_callback( - # self.__class__.__terminate_listener_thread, - # ( - # [ - # ServerNotification.EXECUTION_FINISHED.name, - # ServerNotification.NOTHING_PROCESSED.name, - # ], - # wait_for_finished_queue, - # self._logger, - # ), - # ) - # exec_finished_listener.start_listening() - # self._logger.debug("Wait for finished thread was created.") - - # status = self.get_project_status() - - # # do not send stop_gently request if project is already stopped or request - # # with higher or equal priority was already sent - # if status in self._STOPPED_STATES: - # self._logger.debug(f"Do not send STOP request, project status is: {status}") - # if wait_for_finished: - # exec_finished_listener.stop_listening() - # exec_finished_listener.clear_callbacks() - # self.__delete_exec_finished_listener() - # return - # elif status in self._STOP_REQUESTS_PRIORITIES: - # stop_request_priority = self._STOP_REQUESTS_PRIORITIES["STOP_GENTLY"] - # current_status_priority = self._STOP_REQUESTED_STATES_PRIORITIES[status] - # if stop_request_priority > current_status_priority: - # self._send_command(commands.stop(self.__password)) - # else: - # self._logger.debug(f"Do not send STOP request, project status is: {status}") - # else: - # self._send_command(commands.stop(self.__password)) - - # if wait_for_finished: - # self._logger.info(f"Waiting for finished") - # successfully_finished = wait_for_finished_queue.get() - # self.__delete_exec_finished_listener() - # if successfully_finished == "Terminate": - # raise TimeoutError("Waiting for finished timed out.") - # self._logger.info(f"Successfully_finished: {successfully_finished}.") - def _unregister_listener(self, listener: TcpOslListener) -> None: """Unregister a listener. @@ -2070,7 +2103,7 @@ def __register_listener( ) return msg[0]["uid"] - def __refresh_listeners_registration(self) -> None: + def __refresh_listeners_registration(self) -> None: # pragma: no cover """Refresh listeners registration. Raises @@ -2193,6 +2226,90 @@ def _send_command(self, command: str) -> Dict: return response + # To be fixed in 2023R2: + # close method doesn't work properly in optiSLang 2023R1, therefore it was commented out + # def close(self) -> None: + # """Close the current project. + + # 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. + # """ + # self._send_command(commands.close(password=self.__password)) + + # stop_gently method doesn't work properly in optiSLang 2023R1, therefore it was commented out + # def stop_gently(self, wait_for_finished: bool = True) -> None: + # """Stop project execution after the current design is finished. + + # Parameters + # ---------- + # wait_for_finished : bool, optional + # Determines whether this function call should wait on the optiSlang to finish + # the command execution. I.e. don't continue on next line of python script after command + # was successfully sent to optiSLang but wait for execution of command inside optiSLang. + # Defaults to ``True``. + + # 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. + # """ + # if wait_for_finished: + # exec_finished_listener = self.__create_exec_finished_listener() + # exec_finished_listener.cleanup_notifications() + # wait_for_finished_queue = Queue() + # exec_finished_listener.add_callback( + # self.__class__.__terminate_listener_thread, + # ( + # [ + # ServerNotification.EXECUTION_FINISHED.name, + # ServerNotification.NOTHING_PROCESSED.name, + # ], + # wait_for_finished_queue, + # self._logger, + # ), + # ) + # exec_finished_listener.start_listening() + # self._logger.debug("Wait for finished thread was created.") + + # status = self.get_project_status() + + # # do not send stop_gently request if project is already stopped or request + # # with higher or equal priority was already sent + # if status in self._STOPPED_STATES: + # self._logger.debug(f"Do not send STOP request, project status is: {status}") + # if wait_for_finished: + # exec_finished_listener.stop_listening() + # exec_finished_listener.clear_callbacks() + # self.__delete_exec_finished_listener() + # return + # elif status in self._STOP_REQUESTS_PRIORITIES: + # stop_request_priority = self._STOP_REQUESTS_PRIORITIES["STOP_GENTLY"] + # current_status_priority = self._STOP_REQUESTED_STATES_PRIORITIES[status] + # if stop_request_priority > current_status_priority: + # self._send_command(commands.stop(self.__password)) + # else: + # self._logger.debug(f"Do not send STOP request, project status is: {status}") + # else: + # self._send_command(commands.stop(self.__password)) + + # if wait_for_finished: + # self._logger.info(f"Waiting for finished") + # successfully_finished = wait_for_finished_queue.get() + # self.__delete_exec_finished_listener() + # if successfully_finished == "Terminate": + # raise TimeoutError("Waiting for finished timed out.") + # self._logger.info(f"Successfully_finished: {successfully_finished}.") + @staticmethod def __check_command_response(response: Dict) -> None: """Check whether the server response for a sent command contains any failure information. diff --git a/tests/test_examples.py b/tests/test_examples.py index 62d1d00fa..1039de5b5 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -8,23 +8,23 @@ pytestmark = pytest.mark.local_osl pytest_path = __file__ -examples_dir = os.path.join(pathlib.Path(__file__).parents[1], "examples") -example_files_paths = [] -for file in os.listdir(examples_dir): - if file.endswith(".py"): - example_files_paths.append(os.path.join(examples_dir, file)) - -def setup_function(function): - """Get name of test (example) being executed.""" - global name - name = function.__name__[5:] +run_python_script_examples_dir = os.path.join( + pathlib.Path(__file__).parents[1], "examples", "run_python_script" +) +run_python_script_example_files_paths = [] +for file in os.listdir(run_python_script_examples_dir): + if file.endswith(".py"): + run_python_script_example_files_paths.append( + os.path.join(run_python_script_examples_dir, file) + ) def test_01_ten_bar_truss(): """Test 01_1_ten_bar_truss.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "01_ten_bar_truss" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -33,7 +33,8 @@ def test_01_ten_bar_truss(): def test_02_1_oscillator_robustness(): """Test 02_1_oscillator_robustness.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "02_1_oscillator_robustness" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -42,7 +43,8 @@ def test_02_1_oscillator_robustness(): def test_02_2_oscillator_python_system(): """Test 02_2_oscillator_python_system.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "02_2_oscillator_python_system" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -51,7 +53,8 @@ def test_02_2_oscillator_python_system(): def test_02_3_oscillator_optimization_on_EA(): """Test 02_3_oscillator_optimization_on_EA.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "02_3_oscillator_optimization_on_EA" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -60,7 +63,8 @@ def test_02_3_oscillator_optimization_on_EA(): def test_02_4_oscillator_MOP_sensitivity_and_optimization(): """Test 02_4_oscillator_MOP_sensitivity_and_optimization.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "02_4_oscillator_MOP_sensitivity_and_optimization" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -69,7 +73,8 @@ def test_02_4_oscillator_MOP_sensitivity_and_optimization(): def test_02_5_oscillator_calibration_systems(): """Test 02_5_oscillator_calibration_systems.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "02_5_oscillator_calibration_systems" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -78,7 +83,8 @@ def test_02_5_oscillator_calibration_systems(): def test_03_etk_abaqus(): """Test 03_etk_abaqus.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "03_etk_abaqus" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -87,7 +93,8 @@ def test_03_etk_abaqus(): def test_04_python_node_and_help(): """Test 04_python_node_and_help.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "04_python_node_and_help" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -96,7 +103,8 @@ def test_04_python_node_and_help(): def test_05_optimizer_settings(): """Test 05_optimizer_settings.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "05_optimizer_settings" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -105,7 +113,8 @@ def test_05_optimizer_settings(): def test_06_sensitivity_settings(): """Test 06_sensitivity_settings.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "06_sensitivity_settings" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None @@ -114,7 +123,27 @@ def test_06_sensitivity_settings(): def test_07_simple_calculator(): """Test 07_simple_calculator.py.""" with does_not_raise() as dnr: - file = list(filter(lambda path: name in path, example_files_paths))[0] + name = "07_simple_calculator" + file = list(filter(lambda path: name in path, run_python_script_example_files_paths))[0] + exec(open(file).read()) + time.sleep(5) + assert dnr is None + + +evaluate_design_examples_dir = os.path.join( + pathlib.Path(__file__).parents[1], "examples", "evaluate_design" +) +evaluate_design_example_files_paths = [] +for file in os.listdir(evaluate_design_examples_dir): + if file.endswith(".py"): + evaluate_design_example_files_paths.append(os.path.join(evaluate_design_examples_dir, file)) + + +def test_01_ten_bar_truss_evaluate_design(): + """Test 01_ten_bar_truss.py.""" + with does_not_raise() as dnr: + name = "01_ten_bar_truss" + file = list(filter(lambda path: name in path, evaluate_design_example_files_paths))[0] exec(open(file).read()) time.sleep(5) assert dnr is None diff --git a/tests/test_logging.py b/tests/test_logging.py index 819941f6b..9d96a9399 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,5 +1,5 @@ import logging as deflogging # Default logging -import os +from pathlib import Path from ansys.optislang.core import LOG # Global logger from ansys.optislang.core import logging @@ -18,12 +18,12 @@ def test_initialization(): assert log1.logger.level == deflogging.ERROR -def test_create_log_file(tmp_path): +def test_create_log_file(tmp_path: Path): "This tests creation of logfile with ``logfile_name`` when initialized" - logfile_path = os.path.join(tmp_path, "testlog.log") - assert not os.path.isfile(logfile_path) + logfile_path = tmp_path / "testlog.log" + assert not logfile_path.is_file() log = logging.OslLogger(log_to_file=True, logfile_name=logfile_path) - assert os.path.isfile(logfile_path) + assert logfile_path.is_file() def test_set_log_level(): @@ -41,9 +41,9 @@ def test_set_log_level(): assert log.logger.level == deflogging.DEBUG -def test_add_file_handler(): +def test_add_file_handler(tmp_path: Path): log = logging.OslLogger() - log.add_file_handler(logfile_name="testlog.log") + log.add_file_handler(logfile_name=tmp_path / "testlog.log") assert log.file_handler != None assert log.file_handler.level == LOG_LEVELS[logging.LOG_LEVEL] log.set_log_level(loglevel="ERROR") @@ -81,11 +81,11 @@ def test_global_logger_stdout(caplog): assert caplog.record_tuples[-1] == ("pyoptislang_global", each_log_number, msg) -def test_global_logger_log_to_file(): +def test_global_logger_log_to_file(tmp_path: Path): LOG.logger.setLevel("DEBUG") - LOG.add_file_handler(logfile_name="testlog.log", loglevel="DEBUG") + LOG.add_file_handler(logfile_name=tmp_path / "testlog.log", loglevel="DEBUG") msg = "Random debug message" LOG.logger.debug(msg) - with open("testlog.log", "r") as fid: + with open(tmp_path / "testlog.log", "r") as fid: text = "".join(fid.readlines()) assert msg in text diff --git a/tests/test_nodes.py b/tests/test_nodes.py new file mode 100644 index 000000000..d65d0ddb4 --- /dev/null +++ b/tests/test_nodes.py @@ -0,0 +1,197 @@ +from contextlib import nullcontext as does_not_raise + +import pytest + +from ansys.optislang.core import Optislang, examples +from ansys.optislang.core.nodes import Node, ParametricSystem, RootSystem, System +from ansys.optislang.core.project_parametric import ParameterManager + +pytestmark = pytest.mark.local_osl +single_node = examples.get_files("calculator_with_params")[1][0] +nested_systems = examples.get_files("nested_systems")[1][0] +connect_nodes = examples.get_files("nodes_connection")[1][0] + + +@pytest.fixture() +def optislang(scope="function", autouse=False) -> Optislang: + """Create instance of Optislang class. + + Returns + ------- + Optislang: + Connects to the optiSLang application and provides an API to control it. + """ + osl = Optislang() + osl.set_timeout(20) + return osl + + +# TEST NODE +def test_node_initialization(optislang: Optislang): + """Test `Node` initialization.""" + optislang.open(file_path=single_node) + project = optislang.project + root_system = project.root_system + node = root_system.get_nodes()[0] + assert isinstance(node, Node) + optislang.dispose() + + +def test_node_properties(optislang: Optislang): + """Test properties of the instance of `Node` class.""" + optislang.open(file_path=single_node) + project = optislang.project + root_system = project.root_system + node = root_system.get_nodes()[0] + assert isinstance(node, Node) + assert isinstance(node.uid, str) + optislang.dispose() + + +def test_node_queries(optislang: Optislang): + """Test get methods of the instance of `Node` class.""" + optislang.open(file_path=single_node) + project = optislang.project + root_system = project.root_system + node = root_system.get_nodes()[0] + + name = node.get_name() + assert isinstance(name, str) + assert name == "Calculator" + + parent_name = node.get_parent_name() + assert isinstance(name, str) + + parent = node.get_parent() + assert isinstance(parent, RootSystem) + assert parent_name == parent.get_name() + + properties = node.get_properties() + assert isinstance(properties, dict) + + status = node.get_status() + assert isinstance(status, str) + + type = node.get_type() + assert isinstance(type, str) + assert type == "CalculatorSet" + + with does_not_raise() as dnr: + print(node) + assert dnr is None + + optislang.dispose() + + +# TEST SYSTEM +def test_find_node_by_uid(optislang: Optislang): + """Test `find_node_by_uid`.""" + optislang.open(file_path=nested_systems) + project = optislang.project + root_system = project.root_system + + # level 1 + level0_node = root_system.find_node_by_uid( + uid="8854b2c4-fd8a-4e07-839e-1f36553e2d40", search_depth=1 + ) + assert isinstance(level0_node, Node) + with does_not_raise() as dnr: + print(level0_node) + assert dnr is None + + level0_system = root_system.find_node_by_uid( + uid="a8375c1f-0e39-4901-aa29-56d88f693b54", search_depth=1 + ) + assert isinstance(level0_system, System) + with does_not_raise() as dnr: + print(level0_system) + assert dnr is None + + higher_level = root_system.find_node_by_uid( + uid="051ba887-cd72-4fe1-a676-2d75a8a843e9", search_depth=1 + ) + assert higher_level is None + + # level 2 + level1_node = root_system.find_node_by_uid( + uid="051ba887-cd72-4fe1-a676-2d75a8a843e9", search_depth=2 + ) + assert isinstance(level1_node, Node) + + higher_level = root_system.find_node_by_uid( + uid="ab7d50e2-b031-4c75-b701-fd9fcadf779c", search_depth=2 + ) + assert higher_level is None + + # level 3 + level2_node = root_system.find_node_by_uid( + uid="ab7d50e2-b031-4c75-b701-fd9fcadf779c", search_depth=3 + ) + assert isinstance(level2_node, Node) + + higher_level = root_system.find_node_by_uid( + uid="161992fc-d1ee-487f-8bab-0b9897e641e8", search_depth=3 + ) + assert higher_level is None + + # level 4 + level3_node = root_system.find_node_by_uid( + uid="161992fc-d1ee-487f-8bab-0b9897e641e8", search_depth=4 + ) + assert isinstance(level3_node, Node) + + optislang.dispose() + + +def test_find_node_by_name(optislang: Optislang): + """Test `find_node_by_name`.""" + optislang.open(file_path=nested_systems) + project = optislang.project + root_system = project.root_system + + # level 1 + level0_node = root_system.find_nodes_by_name(name="Calculator", search_depth=1)[0] + assert isinstance(level0_node, Node) + + level0_system = root_system.find_nodes_by_name(name="System", search_depth=1)[0] + assert isinstance(level0_system, System) + + higher_level = root_system.find_nodes_by_name(name="Calculator_inSystem", search_depth=1) + assert isinstance(higher_level, tuple) + assert len(higher_level) == 0 + + # level 2 + level1_node = root_system.find_nodes_by_name(name="Calculator_inSensitivity", search_depth=2)[0] + assert isinstance(level1_node, Node) + + higher_level = root_system.find_nodes_by_name( + name="System_inSystem_inSensitivity", search_depth=2 + ) + assert isinstance(higher_level, tuple) + assert len(higher_level) == 0 + + # level 3 + level2_nodes = root_system.find_nodes_by_name(name="Calculator_inSystem", search_depth=3) + assert len(level2_nodes) == 2 + + # level 4 + level3_nodes = root_system.find_nodes_by_name(name="Calculator_inSystem", search_depth=4) + assert len(level3_nodes) == 3 + + optislang.dispose() + + +# TEST PARAMETRIC SYSTEM +def test_get_parameter_manager(optislang: Optislang): + """Test initialization and __str__ methods of both `ParametricSystem` and `ParameterManager`.""" + optislang.open(file_path=nested_systems) + project = optislang.project + root_system = project.root_system + parametric_system: ParametricSystem = root_system.find_nodes_by_name("Parametric System")[0] + with does_not_raise() as dnr: + print(parametric_system) + parameter_manager = parametric_system.parameter_manager + print(parameter_manager) + assert isinstance(parameter_manager, ParameterManager) + optislang.dispose() + assert dnr is None diff --git a/tests/test_optislang.py b/tests/test_optislang.py index 3470e60d0..dedbe14ae 100644 --- a/tests/test_optislang.py +++ b/tests/test_optislang.py @@ -6,12 +6,14 @@ import pytest from ansys.optislang.core import Optislang, examples +from ansys.optislang.core.project import Project pytestmark = pytest.mark.local_osl +parametric_project = examples.get_files("calculator_with_params")[1][0] -@pytest.fixture -def optislang(scope="function", autouse=True) -> Optislang: +@pytest.fixture() +def optislang(scope="function", autouse=False) -> Optislang: """Create Optislang class. Returns @@ -24,7 +26,7 @@ def optislang(scope="function", autouse=True) -> Optislang: return osl -# def test_close(optislang: Optislang): +# # def test_close(optislang: Optislang): # "Test ``close`` (close opened and create new project)." # with does_not_raise() as dnr: # optislang.close() @@ -52,7 +54,7 @@ def test_has_active_project(optislang: Optislang): def test_get_osl_version_string(optislang: Optislang): - "Test ``get_osl_version_string``." + """Test ``get_osl_version_string``.""" version = optislang.get_osl_version_string() assert isinstance(version, str) with does_not_raise() as dnr: @@ -73,44 +75,13 @@ def test_get_osl_version(optislang: Optislang): assert dnr is None -def test_get_project_description(optislang: Optislang): - "Test ``get_project_description``." - description = optislang.get_project_description() - assert isinstance(description, str) +def test_get_project(optislang: Optislang): + """Test `get_project`.""" with does_not_raise() as dnr: - optislang.dispose() - time.sleep(3) - assert dnr is None - - -def test_get_project_location(optislang: Optislang): - "Test ``get_project_location``." - location = optislang.get_project_location() - assert isinstance(location, Path) - with does_not_raise() as dnr: - optislang.dispose() - time.sleep(3) - assert dnr is None - - -def test_get_project_name(optislang: Optislang): - "Test ``get_project_name``." - name = optislang.get_project_name() - assert isinstance(name, str) - with does_not_raise() as dnr: - optislang.dispose() - time.sleep(3) - assert dnr is None - - -def test_get_project_status(optislang: Optislang): - "Test ``get_project_status``." - status = optislang.get_project_status() - assert isinstance(status, str) - with does_not_raise() as dnr: - optislang.dispose() - time.sleep(3) + project = optislang.project + optislang.dispose() assert dnr is None + assert isinstance(project, Project) def test_get_set_timeout(optislang: Optislang): @@ -136,11 +107,12 @@ def test_get_working_dir(optislang: Optislang): assert dnr is None -def test_new(optislang: Optislang): +def test_new(optislang: Optislang, tmp_path: Path): "Test ``new``." optislang.new() - assert optislang.get_project_name() == "Unnamed project" + assert optislang.project.get_name() == "Unnamed project" with does_not_raise() as dnr: + optislang.save_as(file_path=tmp_path / "newProject.opf") optislang.dispose() time.sleep(3) assert dnr is None @@ -154,7 +126,7 @@ def test_open(optislang: Optislang, path_type): project = str(project) optislang.open(file_path=project) - project_name = optislang.get_project_name() + project_name = optislang.project.get_name() assert project_name == "calculator" with does_not_raise() as dnr: optislang.dispose() @@ -212,7 +184,8 @@ def test_run_python_file(optislang: Optislang, tmp_path: Path): def test_save(optislang: Optislang): "Test save." - file_path = optislang.get_project_location() + project = optislang.project + file_path = project.get_location() assert file_path.is_file() mod_time = os.path.getmtime(str(file_path)) optislang.save() @@ -311,3 +284,11 @@ def test_stop(optislang: Optislang): # optislang.dispose() # time.sleep(3) # assert dnr is None + + +def test_dispose(optislang): + "Test ``dispose``." + with does_not_raise() as dnr: + optislang.dispose() + time.sleep(3) + assert dnr is None diff --git a/tests/test_parameter_manager.py b/tests/test_parameter_manager.py new file mode 100644 index 000000000..7609cafcc --- /dev/null +++ b/tests/test_parameter_manager.py @@ -0,0 +1,46 @@ +import pytest + +from ansys.optislang.core import Optislang, examples +from ansys.optislang.core.project_parametric import MixedParameter + +pytestmark = pytest.mark.local_osl +parametric_project = examples.get_files("calculator_with_params")[1][0] + + +@pytest.fixture() +def optislang(scope="function", autouse=False) -> Optislang: + """Create Optislang class. + + Returns + ------- + Optislang: + Connects to the optiSLang application and provides an API to control it. + """ + osl = Optislang(project_path=parametric_project) + osl.set_timeout(20) + return osl + + +def test_get_parameters(optislang: Optislang): + """Test ``get_parameters_names``.""" + project = optislang.project + root_system = project.root_system + parameter_manager = root_system.parameter_manager + parameters = parameter_manager.get_parameters() + optislang.dispose() + assert isinstance(parameters, tuple) + assert len(parameters) > 0 + for parameter in parameters: + assert isinstance(parameter, MixedParameter) + + +def test_get_parameters_names(optislang: Optislang): + """Test ``get_parameters_names``.""" + project = optislang.project + root_system = project.root_system + parameter_manager = root_system.parameter_manager + parameters_names = parameter_manager.get_parameters_names() + optislang.dispose() + assert isinstance(parameters_names, tuple) + assert len(parameters_names) > 0 + assert set(["a", "b"]) == set(parameters_names) diff --git a/tests/test_parameters.py b/tests/test_parameters.py new file mode 100644 index 000000000..f2f0b9b4e --- /dev/null +++ b/tests/test_parameters.py @@ -0,0 +1,600 @@ +import pytest + +from ansys.optislang.core.project_parametric import ( + DependentParameter, + Design, + DesignStatus, + DesignVariable, + DistributionType, + MixedParameter, + OptimizationParameter, + Parameter, + ParameterResolution, + ParameterType, + ParameterValueType, + StochasticParameter, +) + +DEPENDENT_PARAMETER = DependentParameter( + name="dependent", id="aaba5b78-4b8c-4cc3-a308-4e122e2af665" +) +DEPENDENT_PARAMETER_DICT = { + "active": True, + "const": False, + "dependency_expression": "0", + "id": "aaba5b78-4b8c-4cc3-a308-4e122e2af665", + "modifiable": False, + "name": "dependent", + "reference_value": 0.0, + "removable": True, + "type": {"value": "dependent"}, + "unit": "", +} +OPTIMIZATION_PARAMETER = OptimizationParameter( + name="optimization", id="c06ea75b-7c19-4905-b727-a0f89c540c4a" +) +OPTIMIZATION_PARAMETER_DICT = { + "active": True, + "const": False, + "deterministic_property": { + "domain_type": {"value": "real"}, + "kind": {"value": "continuous"}, + "lower_bound": -1.0, + "upper_bound": 1.0, + }, + "id": "c06ea75b-7c19-4905-b727-a0f89c540c4a", + "modifiable": False, + "name": "optimization", + "reference_value": 0.0, + "removable": True, + "type": {"value": "deterministic"}, + "unit": "", +} +STOCHASTIC_PARAMETER = StochasticParameter( + name="stochastic", id="9dbcac71-5538-485c-9e65-39255903ea78" +) +STOCHASTIC_PARAMETER_DICT = { + "active": True, + "const": False, + "id": "9dbcac71-5538-485c-9e65-39255903ea78", + "modifiable": False, + "name": "stochastic", + "reference_value": 0.0, + "removable": True, + "stochastic_property": { + "kind": {"value": "marginaldistribution"}, + "statistical_moments": [0.0, 1.0], + "type": {"value": "normal"}, + }, + "type": {"value": "stochastic"}, + "unit": "", +} +MIXED_PARAMETER = MixedParameter(name="mixed", id="ddc173ae-9784-4387-ae66-21fff6e199e5") +MIXED_PARAMETER_DICT = { + "active": True, + "const": False, + "deterministic_property": { + "domain_type": {"value": "real"}, + "kind": {"value": "continuous"}, + "lower_bound": -1.0, + "upper_bound": 1.0, + }, + "id": "ddc173ae-9784-4387-ae66-21fff6e199e5", + "modifiable": False, + "name": "mixed", + "reference_value": 0.0, + "removable": True, + "stochastic_property": { + "kind": {"value": "marginaldistribution"}, + "statistical_moments": [0.0, 1.0], + "type": {"value": "normal"}, + }, + "type": {"value": "mixed"}, + "unit": "", +} + +# TEST ENUMERATION METHODS: +def test_design_status_from_str(): + """Test `DesignStatus.from_str()`.""" + assert DesignStatus.from_str("IdLe") == DesignStatus.IDLE + assert DesignStatus.from_str("pending") == DesignStatus.PENDING + assert DesignStatus.from_str("succeeded") == DesignStatus.SUCCEEDED + assert DesignStatus.from_str("failed") == DesignStatus.FAILED + with pytest.raises(ValueError): + DesignStatus.from_str("nonexistingstatus") + + +def test_parameter_type(): + """Test `ParameterType`.""" + deterministic = ParameterType.DETERMINISTIC + deterministic_name = deterministic.name + deterministic_from_str = ParameterType.from_str("DeTeRmInIsTiC") + assert isinstance(deterministic, ParameterType) + assert isinstance(deterministic_from_str, ParameterType) + assert isinstance(deterministic_name, str) + assert deterministic_name == "DETERMINISTIC" + assert deterministic_name == deterministic_from_str.name + + stochastic = ParameterType.STOCHASTIC + stochastic_name = stochastic.name + stochastic_from_str = ParameterType.from_str("stochastic") + assert isinstance(stochastic, ParameterType) + assert isinstance(stochastic_from_str, ParameterType) + assert isinstance(stochastic_name, str) + assert stochastic_name == "STOCHASTIC" + assert stochastic_name == stochastic_from_str.name + + mixed = ParameterType.MIXED + mixed_name = mixed.name + mixed_from_str = ParameterType.from_str("mixed") + assert isinstance(mixed, ParameterType) + assert isinstance(mixed_from_str, ParameterType) + assert isinstance(mixed_name, str) + assert mixed_name == "MIXED" + assert mixed_name == mixed_from_str.name + + dependent = ParameterType.DEPENDENT + dependent_name = dependent.name + dependent_from_str = ParameterType.from_str("dependent") + assert isinstance(dependent, ParameterType) + assert isinstance(dependent_from_str, ParameterType) + assert isinstance(dependent_name, str) + assert dependent_name == "DEPENDENT" + assert dependent_name == dependent_from_str.name + + with pytest.raises(TypeError): + ParameterType.from_str(1) + + with pytest.raises(ValueError): + ParameterType.from_str("nonexisting") + + +def test_parameter_resolution(): + """Test `ParameterResolution`.""" + continuous_from_str = ParameterResolution.from_str("CoNtInUoUs") + assert isinstance(continuous_from_str, ParameterResolution) + assert continuous_from_str.name == "CONTINUOUS" + + ordinaldiscrete_value_from_str = ParameterResolution.from_str("ordinaldiscrete_value") + assert isinstance(ordinaldiscrete_value_from_str, ParameterResolution) + assert ordinaldiscrete_value_from_str.name == "ORDINALDISCRETE_VALUE" + + ordinaldiscrete_index_from_str = ParameterResolution.from_str("ordinaldiscrete_index") + assert isinstance(ordinaldiscrete_index_from_str, ParameterResolution) + assert ordinaldiscrete_index_from_str.name == "ORDINALDISCRETE_INDEX" + + nominaldiscrete_from_str = ParameterResolution.from_str("nominaldiscrete") + assert isinstance(nominaldiscrete_from_str, ParameterResolution) + assert nominaldiscrete_from_str.name == "NOMINALDISCRETE" + + deterministic_from_str = ParameterResolution.from_str("deterministic") + assert isinstance(deterministic_from_str, ParameterResolution) + assert deterministic_from_str.name == "DETERMINISTIC" + + marginaldistribution_from_str = ParameterResolution.from_str("marginaldistribution") + assert isinstance(marginaldistribution_from_str, ParameterResolution) + assert marginaldistribution_from_str.name == "MARGINALDISTRIBUTION" + + empirical_discrete_from_str = ParameterResolution.from_str("empirical_discrete") + assert isinstance(empirical_discrete_from_str, ParameterResolution) + assert empirical_discrete_from_str.name == "EMPIRICAL_DISCRETE" + + empirical_continuous_from_str = ParameterResolution.from_str("empirical_continuous") + assert isinstance(empirical_continuous_from_str, ParameterResolution) + assert empirical_continuous_from_str.name == "EMPIRICAL_CONTINUOUS" + + with pytest.raises(TypeError): + ParameterResolution.from_str(1) + + with pytest.raises(ValueError): + ParameterResolution.from_str("nonexisting") + + +def test_parameter_value_type(): + """Test `ParameterValueType`.""" + uninitialized_from_str = ParameterValueType.from_str("UnInItIaLiZeD") + assert isinstance(uninitialized_from_str, ParameterValueType) + assert uninitialized_from_str.name == "UNINITIALIZED" + + bool_from_str = ParameterValueType.from_str("bool") + assert isinstance(bool_from_str, ParameterValueType) + assert bool_from_str.name == "BOOL" + + real_from_str = ParameterValueType.from_str("real") + assert isinstance(real_from_str, ParameterValueType) + assert real_from_str.name == "REAL" + + integer_from_str = ParameterValueType.from_str("integer") + assert isinstance(integer_from_str, ParameterValueType) + assert integer_from_str.name == "INTEGER" + + string_from_str = ParameterValueType.from_str("string") + assert isinstance(string_from_str, ParameterValueType) + assert string_from_str.name == "STRING" + + variant_from_str = ParameterValueType.from_str("variant") + assert isinstance(variant_from_str, ParameterValueType) + assert variant_from_str.name == "VARIANT" + + with pytest.raises(TypeError): + ParameterValueType.from_str(1) + + with pytest.raises(ValueError): + ParameterValueType.from_str("nonexisting") + + +def test_distribution_type(): + """Test `DistributionType`.""" + externalcoherence_from_str = DistributionType.from_str("ExTeRnAlCoHeReNcE") + assert isinstance(externalcoherence_from_str, DistributionType) + assert externalcoherence_from_str.name == "EXTERNALCOHERENCE" + + untyped_from_str = DistributionType.from_str("untyped") + assert isinstance(untyped_from_str, DistributionType) + assert untyped_from_str.name == "UNTYPED" + + external_from_str = DistributionType.from_str("external") + assert isinstance(external_from_str, DistributionType) + assert external_from_str.name == "EXTERNAL" + + uniform_from_str = DistributionType.from_str("uniform") + assert isinstance(uniform_from_str, DistributionType) + assert uniform_from_str.name == "UNIFORM" + + normal_from_str = DistributionType.from_str("normal") + assert isinstance(normal_from_str, DistributionType) + assert normal_from_str.name == "NORMAL" + + truncatednormal_from_str = DistributionType.from_str("truncatednormal") + assert isinstance(truncatednormal_from_str, DistributionType) + assert truncatednormal_from_str.name == "TRUNCATEDNORMAL" + + lognormal_from_str = DistributionType.from_str("lognormal") + assert isinstance(lognormal_from_str, DistributionType) + assert lognormal_from_str.name == "LOGNORMAL" + + exponential_from_str = DistributionType.from_str("exponential") + assert isinstance(exponential_from_str, DistributionType) + assert exponential_from_str.name == "EXPONENTIAL" + + rayleigh_from_str = DistributionType.from_str("rayleigh") + assert isinstance(rayleigh_from_str, DistributionType) + assert rayleigh_from_str.name == "RAYLEIGH" + + small_i_from_str = DistributionType.from_str("small_i") + assert isinstance(small_i_from_str, DistributionType) + assert small_i_from_str.name == "SMALL_I" + + large_i_from_str = DistributionType.from_str("large_i") + assert isinstance(large_i_from_str, DistributionType) + assert large_i_from_str.name == "LARGE_I" + + small_ii_from_str = DistributionType.from_str("small_ii") + assert isinstance(small_ii_from_str, DistributionType) + assert small_ii_from_str.name == "SMALL_II" + + large_ii_from_str = DistributionType.from_str("large_ii") + assert isinstance(large_ii_from_str, DistributionType) + assert large_ii_from_str.name == "LARGE_II" + + small_iii_from_str = DistributionType.from_str("small_iii") + assert isinstance(small_iii_from_str, DistributionType) + assert small_iii_from_str.name == "SMALL_III" + + large_iii_from_str = DistributionType.from_str("large_iii") + assert isinstance(large_iii_from_str, DistributionType) + assert large_iii_from_str.name == "LARGE_III" + + triangular_from_str = DistributionType.from_str("triangular") + assert isinstance(triangular_from_str, DistributionType) + assert triangular_from_str.name == "TRIANGULAR" + + beta_from_str = DistributionType.from_str("beta") + assert isinstance(beta_from_str, DistributionType) + assert beta_from_str.name == "BETA" + + chi_square_from_str = DistributionType.from_str("chi_square") + assert isinstance(chi_square_from_str, DistributionType) + assert chi_square_from_str.name == "CHI_SQUARE" + + erlang_from_str = DistributionType.from_str("erlang") + assert isinstance(erlang_from_str, DistributionType) + assert erlang_from_str.name == "ERLANG" + + fisher_f_from_str = DistributionType.from_str("fisher_f") + assert isinstance(fisher_f_from_str, DistributionType) + assert fisher_f_from_str.name == "FISHER_F" + + gamma_from_str = DistributionType.from_str("gamma") + assert isinstance(gamma_from_str, DistributionType) + assert gamma_from_str.name == "GAMMA" + + pareto_from_str = DistributionType.from_str("pareto") + assert isinstance(pareto_from_str, DistributionType) + assert pareto_from_str.name == "PARETO" + + weibull_from_str = DistributionType.from_str("weibull") + assert isinstance(weibull_from_str, DistributionType) + assert weibull_from_str.name == "WEIBULL" + + extreme_value_from_str = DistributionType.from_str("extreme_value") + assert isinstance(extreme_value_from_str, DistributionType) + assert extreme_value_from_str.name == "EXTREME_VALUE" + + students_f_from_str = DistributionType.from_str("students_f") + assert isinstance(students_f_from_str, DistributionType) + assert students_f_from_str.name == "STUDENTS_F" + + inverse_normal_from_str = DistributionType.from_str("inverse_normal") + assert isinstance(inverse_normal_from_str, DistributionType) + assert inverse_normal_from_str.name == "INVERSE_NORMAL" + + log_gamma_from_str = DistributionType.from_str("log_gamma") + assert isinstance(log_gamma_from_str, DistributionType) + assert log_gamma_from_str.name == "LOG_GAMMA" + + log_normal_from_str = DistributionType.from_str("log_normal") + assert isinstance(log_normal_from_str, DistributionType) + assert log_normal_from_str.name == "LOG_NORMAL" + + lorentz_from_str = DistributionType.from_str("lorentz") + assert isinstance(lorentz_from_str, DistributionType) + assert lorentz_from_str.name == "LORENTZ" + + fisher_tippett_from_str = DistributionType.from_str("fisher_tippett") + assert isinstance(fisher_tippett_from_str, DistributionType) + assert fisher_tippett_from_str.name == "FISHER_TIPPETT" + + gumbel_from_str = DistributionType.from_str("gumbel") + assert isinstance(gumbel_from_str, DistributionType) + assert gumbel_from_str.name == "GUMBEL" + + fisher_z_from_str = DistributionType.from_str("fisher_z") + assert isinstance(fisher_z_from_str, DistributionType) + assert fisher_z_from_str.name == "FISHER_Z" + + laplace_from_str = DistributionType.from_str("laplace") + assert isinstance(laplace_from_str, DistributionType) + assert laplace_from_str.name == "LAPLACE" + + levy_from_str = DistributionType.from_str("levy") + assert isinstance(levy_from_str, DistributionType) + assert levy_from_str.name == "LEVY" + + logistic_from_str = DistributionType.from_str("logistic") + assert isinstance(logistic_from_str, DistributionType) + assert logistic_from_str.name == "LOGISTIC" + + rossi_from_str = DistributionType.from_str("rossi") + assert isinstance(rossi_from_str, DistributionType) + assert rossi_from_str.name == "ROSSI" + + frechet_from_str = DistributionType.from_str("frechet") + assert isinstance(frechet_from_str, DistributionType) + assert frechet_from_str.name == "FRECHET" + + max_type_from_str = DistributionType.from_str("max_type") + assert isinstance(max_type_from_str, DistributionType) + assert max_type_from_str.name == "MAX_TYPE" + + polymap_from_str = DistributionType.from_str("polymap") + assert isinstance(polymap_from_str, DistributionType) + assert polymap_from_str.name == "POLYMAP" + + kernel_from_str = DistributionType.from_str("kernel") + assert isinstance(kernel_from_str, DistributionType) + assert kernel_from_str.name == "KERNEL" + + bernoulli_from_str = DistributionType.from_str("bernoulli") + assert isinstance(bernoulli_from_str, DistributionType) + assert bernoulli_from_str.name == "BERNOULLI" + + log_uniform_from_str = DistributionType.from_str("log_uniform") + assert isinstance(log_uniform_from_str, DistributionType) + assert log_uniform_from_str.name == "LOG_UNIFORM" + + discrete_from_str = DistributionType.from_str("discrete") + assert isinstance(discrete_from_str, DistributionType) + assert discrete_from_str.name == "DISCRETE" + + multiuniform_from_str = DistributionType.from_str("multiuniform") + assert isinstance(multiuniform_from_str, DistributionType) + assert multiuniform_from_str.name == "MULTIUNIFORM" + + lambda_from_str = DistributionType.from_str("lambda") + assert isinstance(lambda_from_str, DistributionType) + assert lambda_from_str.name == "LAMBDA" + + poisson_from_str = DistributionType.from_str("poisson") + assert isinstance(poisson_from_str, DistributionType) + assert poisson_from_str.name == "POISSON" + + with pytest.raises(TypeError): + DistributionType.from_str(1) + + with pytest.raises(ValueError): + DistributionType.from_str("nonexisting") + + +# TEST PARAMETERS: +def test_design_variable(): + """Test `DesignVariable`.""" + design_variable = DesignVariable(name="par1", value=12) + assert isinstance(design_variable, DesignVariable) + assert isinstance(design_variable.name, str) + assert isinstance(design_variable.value, (int, float)) + + design_variable_eq = DesignVariable(name="par1", value=12.0) + assert isinstance(design_variable, DesignVariable) + assert isinstance(design_variable.name, str) + assert isinstance(design_variable.value, (int, float)) + + design_variable_neq = DesignVariable(name="par2", value=12) + assert design_variable == design_variable_eq + assert not design_variable == design_variable_neq + + +def test_parameter(): + """Test `Parameter`.""" + parameter1 = Parameter( + name="par", reference_value=13.0, id="xxx-12-58", const=True, type_=ParameterType.MIXED + ) + assert isinstance(parameter1, Parameter) + assert isinstance(parameter1.name, str) + assert isinstance(parameter1.reference_value, (int, float)) + assert isinstance(parameter1.id, str) + assert isinstance(parameter1.const, bool) + assert isinstance(parameter1.type, ParameterType) + + parameter1.name = "par1" + parameter1.reference_value = 12 + parameter1.id = "xxx-12-55" + parameter1.const = False + with pytest.raises(AttributeError): + parameter1.type = ParameterType.DEPENDENT + + parameter1_eq = Parameter( + name="par1", reference_value=12.0, id="xxx-12-55", const=False, type_=ParameterType.MIXED + ) + parameter1_neq = Parameter( + name="par1", reference_value=12.0, id="xxx-12-56", const=False, type_=ParameterType.MIXED + ) + assert parameter1 == parameter1_eq + assert not parameter1 == parameter1_neq + + dependent_parameter_from_dict = Parameter.from_dict(DEPENDENT_PARAMETER_DICT) + assert isinstance(dependent_parameter_from_dict, DependentParameter) + optimization_parameter_from_dict = Parameter.from_dict(OPTIMIZATION_PARAMETER_DICT) + assert isinstance(optimization_parameter_from_dict, OptimizationParameter) + stochastic_parameter_from_dict = Parameter.from_dict(STOCHASTIC_PARAMETER_DICT) + assert isinstance(stochastic_parameter_from_dict, StochasticParameter) + mixed_parameter_from_dict = Parameter.from_dict(MIXED_PARAMETER_DICT) + assert isinstance(mixed_parameter_from_dict, MixedParameter) + with pytest.raises(ValueError): + Parameter.from_dict({"type": {"value": "nonexisting"}}) + + +def test_optimization_parameter(): + """Test `OptimizationParameter`.""" + optimization_parameter_dict = OPTIMIZATION_PARAMETER.to_dict() + assert isinstance(optimization_parameter_dict, dict) + for key in optimization_parameter_dict.keys(): + assert key in OPTIMIZATION_PARAMETER_DICT + + optimization_parameter_from_dict = OptimizationParameter.from_dict(OPTIMIZATION_PARAMETER_DICT) + assert OPTIMIZATION_PARAMETER == optimization_parameter_from_dict + + +def test_stochastic_parameter(): + """Test `StochasticParameter`.""" + stochastic_parameter_dict = STOCHASTIC_PARAMETER.to_dict() + assert isinstance(stochastic_parameter_dict, dict) + for key in stochastic_parameter_dict.keys(): + assert key in STOCHASTIC_PARAMETER_DICT + + stochastic_parameter_from_dict = StochasticParameter.from_dict(STOCHASTIC_PARAMETER_DICT) + assert STOCHASTIC_PARAMETER == stochastic_parameter_from_dict + + +def test_mixed_parameter(): + """Test `OptimizationParameter`.""" + mixed_parameter_dict = MIXED_PARAMETER.to_dict() + assert isinstance(mixed_parameter_dict, dict) + for key in mixed_parameter_dict.keys(): + assert key in MIXED_PARAMETER_DICT + + mixed_parameter_from_dict = MixedParameter.from_dict(MIXED_PARAMETER_DICT) + assert MIXED_PARAMETER == mixed_parameter_from_dict + + +def test_dependent_parameter(): + """Test `OptimizationParameter`.""" + dependent_parameter_dict = DEPENDENT_PARAMETER.to_dict() + assert isinstance(dependent_parameter_dict, dict) + for key in dependent_parameter_dict.keys(): + assert key in DEPENDENT_PARAMETER_DICT + + dependent_parameter_from_dict = DependentParameter.from_dict(DEPENDENT_PARAMETER_DICT) + assert DEPENDENT_PARAMETER == dependent_parameter_from_dict + + +# TEST DESIGN: +@pytest.fixture() +def design(scope="function", autouse=False) -> Design: + """Create an instance of Design class.""" + return Design( + [ + DEPENDENT_PARAMETER, + MIXED_PARAMETER, + OPTIMIZATION_PARAMETER, + STOCHASTIC_PARAMETER, + ] + ) + + +def test_design_properties(design: Design): + """Test properties of instance of Design class.""" + assert design.id == None + assert design.feasibility == None + assert design.status == DesignStatus.IDLE + assert isinstance(design.constraints, tuple) + assert isinstance(design.constraints_names, tuple) + assert isinstance(design.limit_states, tuple) + assert isinstance(design.limit_states_names, tuple) + assert isinstance(design.objectives, tuple) + assert isinstance(design.objectives_names, tuple) + assert isinstance(design.parameters, tuple) + assert isinstance(design.parameters[0], DesignVariable) + assert isinstance(design.parameters_names, tuple) + assert isinstance(design.parameters_names[0], str) + assert isinstance(design.responses, tuple) + assert isinstance(design.responses_names, tuple) + assert isinstance(design.variables, tuple) + assert isinstance(design.variables_names, tuple) + for parameter in design.parameters: + assert isinstance(parameter, DesignVariable) + + +def test_clear_parameters(design: Design): + """Test `clear_parameters`.""" + design.clear_parameters() + assert len(design.parameters) == 0 + + +def test_remove_parameter(design: Design): + """Test `remove_parameters`.""" + for name in ["mixed", "dependent"]: + design.remove_parameter(name=name) + assert len(design.parameters) > 0 + for parameter in design.parameters: + assert parameter.name in ["optimization", "stochastic"] + assert parameter.name not in ["mixed", "dependent"] + + +def test_set_parameter_value(design: Design): + """Test `set_parameter` and `set_parameter_by_name`.""" + design.clear_parameters() + design.set_parameter_by_name(name="par1", value=5) + design.set_parameter(parameter=DesignVariable("par2", 10)) + for parameter in design.parameters: + assert isinstance(parameter, DesignVariable) + assert parameter.name in ["par1", "par2"] + assert parameter.value in [5, 10] + design.set_parameter_by_name(name="par1", value=15) + design.set_parameter( + parameter=Parameter( + name="par2", reference_value=20, id="xxx", const=False, type_=ParameterType.MIXED + ) + ) + for parameter in design.parameters: + assert isinstance(parameter, DesignVariable) + assert parameter.name in ["par1", "par2"] + assert parameter.value in [15, 20] + + +def test_copy_unevaluated_design(design: Design): + """Test deep copy of Design (and parameters inside).""" + design_copy = design.copy_unevaluated_design() + assert design_copy.parameters == design.parameters + design_copy.remove_parameter("dependent") + assert not design_copy.parameters == design.parameters diff --git a/tests/test_project.py b/tests/test_project.py new file mode 100644 index 000000000..1fd684056 --- /dev/null +++ b/tests/test_project.py @@ -0,0 +1,82 @@ +from contextlib import nullcontext as does_not_raise +from pathlib import Path +import time + +import pytest + +from ansys.optislang.core import Optislang +from ansys.optislang.core.nodes import RootSystem + +pytestmark = pytest.mark.local_osl + + +@pytest.fixture() +def optislang(scope="function", autouse=True) -> Optislang: + """Create Optislang class. + + Returns + ------- + Optislang: + Connects to the optiSLang application and provides an API to control it. + """ + osl = Optislang() + osl.set_timeout(20) + return osl + + +def test_get_description(optislang: Optislang): + """Test `get_description`.""" + project = optislang.project + description = project.get_description() + assert isinstance(description, str) + with does_not_raise() as dnr: + optislang.dispose() + time.sleep(3) + assert dnr is None + + +def test_get_location(optislang: Optislang): + """Test ``get_location``.""" + project = optislang.project + location = project.get_location() + assert isinstance(location, Path) + with does_not_raise() as dnr: + optislang.dispose() + time.sleep(3) + assert dnr is None + + +def test_get_name(optislang: Optislang): + """Test ``get_name``.""" + project = optislang.project + name = project.get_name() + assert isinstance(name, str) + with does_not_raise() as dnr: + optislang.dispose() + time.sleep(3) + assert dnr is None + + +def test_get_status(optislang: Optislang): + """Test ``get_status``.""" + project = optislang.project + status = project.get_status() + assert isinstance(status, str) + with does_not_raise() as dnr: + optislang.dispose() + time.sleep(3) + assert dnr is None + + +def test_project_properties(optislang: Optislang): + """Test `root_system`, `uid` and `__str__` method.""" + project = optislang.project + uid = project.uid + assert isinstance(uid, str) + root_system = project.root_system + assert isinstance(root_system, RootSystem) + with does_not_raise() as dnr: + print(project) + optislang.dispose() + time.sleep(3) + assert dnr is None diff --git a/tests/test_rootsystem.py b/tests/test_rootsystem.py new file mode 100644 index 000000000..eb6e296ab --- /dev/null +++ b/tests/test_rootsystem.py @@ -0,0 +1,96 @@ +from pathlib import Path + +import pytest + +from ansys.optislang.core import Optislang, examples +from ansys.optislang.core.project_parametric import Design, DesignStatus, DesignVariable + +pytestmark = pytest.mark.local_osl +parametric_project = examples.get_files("calculator_with_params")[1][0] +parameters_dict = {"a": 5, "b": 10} +parameters = [DesignVariable("a", 5), DesignVariable("b", 10)] + + +@pytest.fixture() +def optislang(scope="function", autouse=False) -> Optislang: + """Create Optislang class. + + Returns + ------- + Optislang: + Connects to the optiSLang application and provides an API to control it. + """ + osl = Optislang(project_path=parametric_project) + osl.set_timeout(20) + return osl + + +def test_get_refence_design(optislang: Optislang): + """Test ``get_refence_design``.""" + project = optislang.project + root_system = project.root_system + design = root_system.get_reference_design() + optislang.dispose() + assert isinstance(design, Design) + assert isinstance(design.parameters, tuple) + assert isinstance(design.parameters[0], DesignVariable) + + +def test_evaluate_design(optislang: Optislang, tmp_path: Path): + """Test ``evaluate_design``.""" + optislang.save_copy(file_path=tmp_path / "test_modify_parameter.opf") + optislang.reset() + project = optislang.project + root_system = project.root_system + design = Design(parameters=parameters) + assert design.status == DesignStatus.IDLE + assert design.id == None + assert design.feasibility == None + assert isinstance(design.variables, tuple) + result = root_system.evaluate_design(design) + optislang.dispose() + assert isinstance(result, Design) + assert result.status == DesignStatus.SUCCEEDED + assert design.status == DesignStatus.SUCCEEDED + assert isinstance(result.responses, tuple) + assert isinstance(design.responses, tuple) + assert isinstance(result.responses[0], DesignVariable) + assert isinstance(design.responses[0], DesignVariable) + assert result.responses[0].value == 15 + assert design.responses[0].value == 15 + assert isinstance(result.constraints, tuple) + assert isinstance(design.constraints, tuple) + assert isinstance(result.limit_states, tuple) + assert isinstance(design.limit_states, tuple) + assert isinstance(result.objectives, tuple) + assert isinstance(design.objectives, tuple) + assert isinstance(result.variables, tuple) + assert isinstance(design.variables, tuple) + assert result.feasibility + assert design.feasibility + + +def test_design_structure(optislang: Optislang): + """Test ``get_missing&unused_parameters_names``.""" + project = optislang.project + root_system = project.root_system + designs = [ + Design(parameters=parameters), + Design(parameters={"a": 3, "b": 4}), + Design(parameters={"a": 5, "par1": 6}), + Design(parameters={"par1": 7, "par2": 8}), + ] + expected_outputs = ( + (tuple([]), tuple([])), + (tuple([]), tuple([])), + (tuple(["b"]), tuple(["par1"])), + (tuple(["a", "b"]), tuple(["par1", "par2"])), + ) + for index, design in enumerate(designs): + missing = root_system.get_missing_parameters_names(design) + undefined = root_system.get_undefined_parameters_names(design) + assert isinstance(missing, tuple) + assert isinstance(undefined, tuple) + assert missing == expected_outputs[index][0] + assert undefined == expected_outputs[index][1] + optislang.dispose() diff --git a/tests/test_server_commands.py b/tests/test_server_commands.py index 36c40efb0..3dc6b062a 100644 --- a/tests/test_server_commands.py +++ b/tests/test_server_commands.py @@ -195,8 +195,6 @@ def test_create_node(): json_string = sc.create_node(type_="Sensitivity", name="Sensi-System", password=my_password) dictionary = json.loads(json_string) dictionary["Password"] = my_password - with pytest.raises(TypeError): - sc.create_node(type_="Sensitivity", name="Sensi-System", design_flow="RANDOM") with pytest.raises(TypeError): sc.create_node(type_="Sensitivity") with pytest.raises(TypeError): diff --git a/tests/test_tcp_osl_server.py b/tests/test_tcp_osl_server.py index eb6baa9d2..e363a5032 100644 --- a/tests/test_tcp_osl_server.py +++ b/tests/test_tcp_osl_server.py @@ -8,7 +8,7 @@ import pytest -from ansys.optislang.core import OslServerProcess, examples +from ansys.optislang.core import OslServerProcess, errors, examples import ansys.optislang.core.tcp_osl_server as tos _host = socket.gethostbyname(socket.gethostname()) @@ -16,11 +16,12 @@ _msg = '{ "What": "SYSTEMS_STATUS_INFO" }' +parametric_project = examples.get_files("calculator_with_params")[1][0] pytestmark = pytest.mark.local_osl -@pytest.fixture(scope="function", autouse=True) +@pytest.fixture(scope="function", autouse=False) def osl_server_process(): time.sleep(2) # Will be executed before each test @@ -102,6 +103,21 @@ def test_connect_and_disconnect(osl_server_process: OslServerProcess, tcp_client assert dnr is None +def test_tcpclient_properties(osl_server_process: OslServerProcess, tcp_client: tos.TcpClient): + "Test clients properties." + tcp_client.connect(host=_host, port=_port) + remote_address = tcp_client.remote_address + assert isinstance(remote_address, tuple) + assert isinstance(remote_address[0], str) + assert isinstance(remote_address[1], int) + local_address = tcp_client.local_address + assert isinstance(local_address, tuple) + assert isinstance(local_address[0], str) + assert isinstance(local_address[1], int) + tcp_client.disconnect() + osl_server_process.terminate() + + def test_send_msg(osl_server_process: OslServerProcess, tcp_client: tos.TcpClient): "Test ``send_msg`" with does_not_raise() as dnr: @@ -236,6 +252,7 @@ def test_start_stop_listening( # TcpOslServer + # def test_close(tcp_osl_server: tos.TcpOslServer): # with does_not_raise() as dnr: # tcp_osl_server.close() @@ -244,15 +261,87 @@ def test_start_stop_listening( # assert dnr is None -def test_dispose(tcp_osl_server: tos.TcpOslServer): - """Test `dispose`.""" - with does_not_raise() as dnr: - tcp_osl_server.shutdown() - tcp_osl_server.dispose() - assert dnr is None +def test_evaluate_design(tmp_path: Path): + "Test ``evaluate_design``." + time.sleep(2) + osl_server_process = OslServerProcess( + shutdown_on_finished=False, project_path=parametric_project + ) + osl_server_process.start() + time.sleep(5) + tcp_osl_server = tos.TcpOslServer(host=_host, port=_port) + tcp_osl_server.save_copy(file_path=tmp_path / "test_evaluate_design.opf") + tcp_osl_server.reset() + result = tcp_osl_server.evaluate_design({"a": 5, "b": 10}) + tcp_osl_server.shutdown() + tcp_osl_server.dispose() + assert isinstance(result, list) + assert isinstance(result[0], dict) + + +@pytest.mark.parametrize( + "uid, expected", + [ + ("3577cb69-15b9-4ad1-a53c-ac8af8aaea82", dict), + ("3577cb69-15b9-4ad1-a53c-ac8af8aaea83", errors.OslCommandError), + ], +) +def test_get_actor_info(uid, expected): + """Test ``get_actor_info``.""" + time.sleep(2) + osl_server_process = OslServerProcess( + shutdown_on_finished=False, project_path=parametric_project + ) + osl_server_process.start() + time.sleep(5) + tcp_osl_server = tos.TcpOslServer(host=_host, port=_port) + if expected == errors.OslCommandError: + with pytest.raises(expected): + info = tcp_osl_server.get_actor_info(uid) + else: + info = tcp_osl_server.get_actor_info(uid) + assert isinstance(info, expected) + tcp_osl_server.shutdown() + tcp_osl_server.dispose() -def test_get_server_info(tcp_osl_server: tos.TcpOslServer): +@pytest.mark.parametrize( + "uid, expected", + [ + ("3577cb69-15b9-4ad1-a53c-ac8af8aaea82", dict), + ("3577cb69-15b9-4ad1-a53c-ac8af8aaea83", errors.OslCommandError), + ], +) +def test_get_actor_properties(uid, expected): + """Test ``get_actor_properties``.""" + time.sleep(2) + osl_server_process = OslServerProcess( + shutdown_on_finished=False, project_path=parametric_project + ) + osl_server_process.start() + time.sleep(5) + tcp_osl_server = tos.TcpOslServer(host=_host, port=_port) + if expected == errors.OslCommandError: + with pytest.raises(expected): + properties = tcp_osl_server.get_actor_properties(uid) + else: + properties = tcp_osl_server.get_actor_properties(uid) + assert isinstance(properties, expected) + tcp_osl_server.shutdown() + tcp_osl_server.dispose() + + +def test_get_full_project_tree_with_properties( + osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer +): + """Test `get_full_project_tree_with_properties`.""" + project_tree = tcp_osl_server.get_full_project_tree_with_properties() + assert isinstance(project_tree, dict) + tcp_osl_server.shutdown() + tcp_osl_server.dispose() + + +def test_get_server_info(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``_get_server_info``.""" server_info = tcp_osl_server._get_server_info() tcp_osl_server.shutdown() @@ -261,7 +350,9 @@ def test_get_server_info(tcp_osl_server: tos.TcpOslServer): assert bool(server_info) -def test_get_basic_project_info(tcp_osl_server: tos.TcpOslServer): +def test_get_basic_project_info( + osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer +): """Test ``_get_basic_project_info``.""" basic_project_info = tcp_osl_server._get_basic_project_info() tcp_osl_server.shutdown() @@ -270,7 +361,9 @@ def test_get_basic_project_info(tcp_osl_server: tos.TcpOslServer): assert bool(basic_project_info) -def test_get_osl_version_string(osl_server_process, tcp_osl_server): +def test_get_osl_version_string( + osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer +): """Test ``get_osl_version_string``.""" version = tcp_osl_server.get_osl_version_string() tcp_osl_server.shutdown() @@ -279,7 +372,7 @@ def test_get_osl_version_string(osl_server_process, tcp_osl_server): assert bool(version) -def test_get_osl_version(tcp_osl_server): +def test_get_osl_version(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``get_osl_version``.""" major_version, minor_version, maintenance_version, revision = tcp_osl_server.get_osl_version() tcp_osl_server.shutdown() @@ -290,7 +383,9 @@ def test_get_osl_version(tcp_osl_server): assert isinstance(revision, int) or revision == None -def test_get_project_description(osl_server_process, tcp_osl_server): +def test_get_project_description( + osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer +): """Test ``get_project_description``.""" project_description = tcp_osl_server.get_project_description() tcp_osl_server.shutdown() @@ -299,7 +394,9 @@ def test_get_project_description(osl_server_process, tcp_osl_server): assert not bool(project_description) -def test_get_project_location(tcp_osl_server: tos.TcpOslServer): +def test_get_project_location( + osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer +): """Test ``get_project_location``.""" project_location = tcp_osl_server.get_project_location() tcp_osl_server.shutdown() @@ -308,7 +405,7 @@ def test_get_project_location(tcp_osl_server: tos.TcpOslServer): assert bool(project_location) -def test_get_project_name(tcp_osl_server: tos.TcpOslServer): +def test_get_project_name(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``get_project_name``.""" project_name = tcp_osl_server.get_project_name() tcp_osl_server.shutdown() @@ -317,7 +414,7 @@ def test_get_project_name(tcp_osl_server: tos.TcpOslServer): assert bool(project_name) -def test_get_project_status(tcp_osl_server: tos.TcpOslServer): +def test_get_project_status(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``get_get_project_status``.""" project_status = tcp_osl_server.get_project_status() tcp_osl_server.shutdown() @@ -326,7 +423,7 @@ def test_get_project_status(tcp_osl_server: tos.TcpOslServer): assert bool(project_status) -def test_get_set_timeout(tcp_osl_server: tos.TcpOslServer): +def test_get_set_timeout(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``get_get_timeout``.""" timeout = tcp_osl_server.get_timeout() assert isinstance(timeout, (int, float)) @@ -339,7 +436,7 @@ def test_get_set_timeout(tcp_osl_server: tos.TcpOslServer): tcp_osl_server.dispose() -def test_get_working_dir(tcp_osl_server: tos.TcpOslServer): +def test_get_working_dir(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``get_working_dir``.""" working_dir = tcp_osl_server.get_working_dir() tcp_osl_server.shutdown() @@ -348,16 +445,19 @@ def test_get_working_dir(tcp_osl_server: tos.TcpOslServer): assert bool(working_dir) -def test_new(tcp_osl_server: tos.TcpOslServer): +def test_new( + osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer, tmp_path: Path +): """Test ``new``.""" tcp_osl_server.new() assert tcp_osl_server.get_project_name() == "Unnamed project" + tcp_osl_server.save_as(file_path=tmp_path / "newProject.opf") tcp_osl_server.shutdown() tcp_osl_server.dispose() @pytest.mark.parametrize("path_type", [str, Path]) -def test_open(tcp_osl_server: tos.TcpOslServer, path_type): +def test_open(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer, path_type): """Test ``open``.""" project = examples.get_files("simple_calculator")[1][0] assert project.is_file() @@ -369,7 +469,7 @@ def test_open(tcp_osl_server: tos.TcpOslServer, path_type): tcp_osl_server.dispose() -def test_reset(tcp_osl_server: tos.TcpOslServer): +def test_reset(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``reset``.""" with does_not_raise() as dnr: tcp_osl_server.reset() @@ -379,7 +479,12 @@ def test_reset(tcp_osl_server: tos.TcpOslServer): @pytest.mark.parametrize("path_type", [str, Path]) -def test_run_python_file(tcp_osl_server: tos.TcpOslServer, tmp_path: Path, path_type): +def test_run_python_file( + osl_server_process: OslServerProcess, + tcp_osl_server: tos.TcpOslServer, + tmp_path: Path, + path_type, +): """Test ``run_python_file``.""" cmd = """ a = 5 @@ -401,7 +506,7 @@ def test_run_python_file(tcp_osl_server: tos.TcpOslServer, tmp_path: Path, path_ assert isinstance(run_file, tuple) -def test_run_python_script(tcp_osl_server: tos.TcpOslServer): +def test_run_python_script(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``run_python_script``.""" cmd = """ a = 5 @@ -415,7 +520,7 @@ def test_run_python_script(tcp_osl_server: tos.TcpOslServer): assert isinstance(run_script, tuple) -def test_save(tcp_osl_server: tos.TcpOslServer): +def test_save(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``save``.""" file_path = tcp_osl_server.get_project_location() assert file_path.is_file() @@ -428,7 +533,12 @@ def test_save(tcp_osl_server: tos.TcpOslServer): @pytest.mark.parametrize("path_type", [str, Path]) -def test_save_as(tcp_osl_server: tos.TcpOslServer, tmp_path: Path, path_type): +def test_save_as( + osl_server_process: OslServerProcess, + tcp_osl_server: tos.TcpOslServer, + tmp_path: Path, + path_type, +): """Test ``save_as``.""" file_path = tmp_path / "test_save.opf" if path_type == str: @@ -443,7 +553,12 @@ def test_save_as(tcp_osl_server: tos.TcpOslServer, tmp_path: Path, path_type): @pytest.mark.parametrize("path_type", [str, Path]) -def test_save_copy(tmp_path: Path, tcp_osl_server: tos.TcpOslServer, path_type): +def test_save_copy( + osl_server_process: OslServerProcess, + tmp_path: Path, + tcp_osl_server: tos.TcpOslServer, + path_type, +): """Test ``save_copy``.""" copy_path = tmp_path / "test_save_copy.opf" if path_type == str: @@ -457,7 +572,18 @@ def test_save_copy(tmp_path: Path, tcp_osl_server: tos.TcpOslServer, path_type): assert copy_path.is_file() -def test_start(tcp_osl_server: tos.TcpOslServer): +def test_set_timeout(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): + """Test ``set_timeout``.""" + assert tcp_osl_server.get_timeout() == 10 + with pytest.raises(ValueError): + tcp_osl_server.set_timeout(-5) + with pytest.raises(TypeError): + tcp_osl_server.set_timeout("5") + tcp_osl_server.shutdown() + tcp_osl_server.dispose() + + +def test_start(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``start``.""" with does_not_raise() as dnr: tcp_osl_server.start() @@ -466,7 +592,7 @@ def test_start(tcp_osl_server: tos.TcpOslServer): assert dnr is None -def test_stop(tcp_osl_server: tos.TcpOslServer): +def test_stop(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``stop``.""" with does_not_raise() as dnr: tcp_osl_server.stop() @@ -475,7 +601,7 @@ def test_stop(tcp_osl_server: tos.TcpOslServer): assert dnr is None -# def test_stop_gently(tcp_osl_server: tos.TcpOslServer): +# def test_stop_gently(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): # """Test ``stop_gently``.""" # with does_not_raise() as dnr: # tcp_osl_server.stop_gently() @@ -483,9 +609,26 @@ def test_stop(tcp_osl_server: tos.TcpOslServer): # assert dnr is None -def test_shutdown(tcp_osl_server: tos.TcpOslServer): +def test_shutdown(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): """Test ``shutdown``.""" with does_not_raise() as dnr: tcp_osl_server.shutdown() tcp_osl_server.dispose() assert dnr is None + + +def test_force_shutdown_local_process(): + """Test ``_force_shutdown_local_process``.""" + with does_not_raise() as dnr: + tcp_osl_server = tos.TcpOslServer() + tcp_osl_server._force_shutdown_local_process() + tcp_osl_server.dispose() + assert dnr is None + + +def test_get_project_uid(osl_server_process: OslServerProcess, tcp_osl_server: tos.TcpOslServer): + """Test `project_uid`.""" + uid = tcp_osl_server.get_project_uid() + assert isinstance(uid, str) + tcp_osl_server.shutdown() + tcp_osl_server.dispose()