From bc651fa847c6a37668a9bdf1e55a0e725e2dac60 Mon Sep 17 00:00:00 2001 From: MichaelNale <122869149+MichaelNale@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:37:17 +0200 Subject: [PATCH] Exposition of the mesh_info_provider in Python and output as derived class (#1007) * data sources namespace * fix * add test and docstring * add generic data container api * operator gdc connect & getoutput * exposed get-property * any new_from & cast; gdc set_property * add documentation * add get-property-[names/types] * add missing docstring * fix type reflection * update any ansys release version * fix coding style * fix coding style * remove datasources changes * add server version decorator * fix coding style * fix retro compatibility errors * add server version check in op connect * fix deleter crash * fix get_output * fix deleter crash * fix typo * fix operator gdc test * revert all changes * remove gdc * add back any & gdc * add back operator connect test * revert operators test changes * add back any tests * Exposition of the mesh_info in Python: MeshInfo class and tests * Exposition of the mesh_info in Python * Revert "revert all changes" This reverts commit 2f42b6e5c412240817ac95b2113831cf2685a439. # Conflicts: # src/ansys/dpf/core/__init__.py # src/ansys/dpf/core/common.py # src/ansys/dpf/core/dpf_operator.py * Revert commits "revert all changes" from feat/grpc-api-propagation * Reformat code with pre-commit * Complete the tests and locally correct some issues concerning the GDC (fix in another PR) * mesh_info_provider test with fluent file * Complete mesh_info tests with mesh_info_provider in CFF * Reformat and rename mesh_info * Exposition of the mesh_info_provider Changes in the formating of the operator generation * Fix some issues: naming and argument order, conditionnal derive_class argument. Cleaning the code. * Fix test_mesh_info issues * Pre-commit reformat * Pre commit reformat * Pre commit reformat * Update the PR : expose the output as the derived class. Implementation of the mesh_info in models. * Reformat code * Update the PR : reformat and implement setter/getter for mesh_info * Update the Pr : Reformat code * Update the PR : avoid circular import of the MeshInfo and reformat code * Update the PR : filter the CFF tests for Linux * Update the PR : consistency with DPF side with the use of lower case for derived class name * Update the PR : still skipped Linux for CFF tests in tests_mesh_info * Update mesh_info test * Update the PR : fix retro compatibility issues * Update PR : reformat * Update the PR : retro compatibility * Update the PR : retro compatibility issues * Update Pr : retro compatibility issues * Update the PR : retro compatibility issues * Update the PR : retro compatibility issues * Update PR : Linux issues * Update the PR : call the new API for pins with derived class * Update the PR : reformat code * Update the PR : fix Linux issues * Update the PR : reformat code * test Linux issues : catch if the CFF plugin is loaded * Update the PR : remove Linux test issue * Update the PR : complete mesh_info tests * Update the PR : test fix Linux issues * Revert last commit * Update the PR : fix test_mesh_info issue * Update the PR : test Linux fix * Update the PR : fix Linux issue * Update the PR : complete mesh_info tests with loading cff operator test * Update PR : complete mesh_info tests by adding test concerning the load of CFF operators and model * Update the PR : correct typo * Update the PR : run Linux pipeline testing result_info with fluent model in Grpc * Revert previous test commit * Update the PR : fix conftests issues for CFF fixture fluent_axial_comp * Update the PR : reformat code * Update the PR : add in Process tests in Linux * Update the PR : reformat code * Update the PR : removed Linux skip and update fluent fixture * Update the PR : reformat code * Update the PR : un-unskip Linux test for result_info * Update test_resultinfo.py * Update the PR : reformat code * Update the PR : fix Linux tests for result_info using CFF model * Update the PR : fix issue for Linux test in result_info * Update the PR : un unskip Linux test for result_info * Update the PR : reviews * Update the PR : fix issues concerning deep_copy of the MeshInfo class * Update the PR : satisfy Codacy Static Code Analysis * Update the PR : server consistency * Update the PR : fix server issue --------- Co-authored-by: cbellot Co-authored-by: Jose A. Henriquez Roa Co-authored-by: Paul Profizi <100710998+PProfizi@users.noreply.github.com> --- src/ansys/dpf/core/__init__.py | 3 +- src/ansys/dpf/core/common.py | 3 + src/ansys/dpf/core/dpf_operator.py | 5 + src/ansys/dpf/core/generic_data_container.py | 1 + src/ansys/dpf/core/mesh_info.py | 198 ++++++++ src/ansys/dpf/core/model.py | 37 ++ src/ansys/dpf/core/operator_specification.py | 108 +++- src/ansys/dpf/core/operators/build.py | 5 + .../dpf/core/operators/operator.mustache | 11 + src/ansys/dpf/core/outputs.py | 13 +- tests/conftest.py | 17 +- tests/test_faces.py | 5 +- tests/test_mesh_info.py | 462 ++++++++++++++++++ tests/test_python_plugins.py | 61 +++ tests/test_resultinfo.py | 34 +- 15 files changed, 898 insertions(+), 65 deletions(-) create mode 100644 src/ansys/dpf/core/mesh_info.py create mode 100644 tests/test_mesh_info.py diff --git a/src/ansys/dpf/core/__init__.py b/src/ansys/dpf/core/__init__.py index ea94b6d5ae..e6932c12ed 100644 --- a/src/ansys/dpf/core/__init__.py +++ b/src/ansys/dpf/core/__init__.py @@ -87,8 +87,9 @@ LicenseContextManager ) from ansys.dpf.core.unit_system import UnitSystem, unit_systems -from ansys.dpf.core.any import Any +from ansys.dpf.core.mesh_info import MeshInfo from ansys.dpf.core.generic_data_container import GenericDataContainer +from ansys.dpf.core.any import Any # for matplotlib # solves "QApplication: invalid style override passed, ignoring it." diff --git a/src/ansys/dpf/core/common.py b/src/ansys/dpf/core/common.py index 7b06e7cab2..36a6d1448e 100644 --- a/src/ansys/dpf/core/common.py +++ b/src/ansys/dpf/core/common.py @@ -93,6 +93,7 @@ class types(Enum): string_field = 22 custom_type_field = 23 generic_data_container = 24 + mesh_info = 25 # Types not from grpc proto, added in Python fields_container = -1 scopings_container = -2 @@ -121,6 +122,7 @@ def types_enum_to_types(): workflow, streams_container, generic_data_container, + mesh_info, ) from ansys.dpf.gate import dpf_vector @@ -150,6 +152,7 @@ def types_enum_to_types(): types.custom_type_field: custom_type_field.CustomTypeField, types.streams_container: streams_container.StreamsContainer, types.generic_data_container: generic_data_container.GenericDataContainer, + types.mesh_info: mesh_info.MeshInfo, } diff --git a/src/ansys/dpf/core/dpf_operator.py b/src/ansys/dpf/core/dpf_operator.py index ad2376eed0..99e4dd8826 100644 --- a/src/ansys/dpf/core/dpf_operator.py +++ b/src/ansys/dpf/core/dpf_operator.py @@ -300,6 +300,7 @@ def _type_to_output_method(self): collection, streams_container, generic_data_container, + mesh_info, ) out = [ @@ -369,6 +370,10 @@ def _type_to_output_method(self): self._api.operator_getoutput_time_freq_support, "time_freq_support", ), + ( + mesh_info.MeshInfo, + "mesh_info", + ), (workflow.Workflow, self._api.operator_getoutput_workflow, "workflow"), (data_tree.DataTree, self._api.operator_getoutput_data_tree, "data_tree"), (Operator, self._api.operator_getoutput_operator, "operator"), diff --git a/src/ansys/dpf/core/generic_data_container.py b/src/ansys/dpf/core/generic_data_container.py index c349d3fc12..bac09723dd 100644 --- a/src/ansys/dpf/core/generic_data_container.py +++ b/src/ansys/dpf/core/generic_data_container.py @@ -39,6 +39,7 @@ def __init__(self, generic_data_container=None, server=None): # step 2: if object exists, take the instance, else create it self._api_instance = None + self._api.init_generic_data_container_environment(self) # creates stub when gRPC self._api.init_generic_data_container_environment(self) diff --git a/src/ansys/dpf/core/mesh_info.py b/src/ansys/dpf/core/mesh_info.py new file mode 100644 index 0000000000..335b7d147a --- /dev/null +++ b/src/ansys/dpf/core/mesh_info.py @@ -0,0 +1,198 @@ +""" +MeshInfo +========== +""" +from ansys.dpf.core import server as server_module +from ansys.dpf.core.generic_data_container import GenericDataContainer +from ansys.dpf.core.scoping import Scoping +from ansys.dpf.core.string_field import StringField + + +class MeshInfo: + """Hold the information relative to a mesh region. + + This class describes the available mesh information. + + Parameters + ---------- + server : ansys.dpf.core.server, optional + Server with the channel connected to the remote or local instance. + The default is ``None``, in which case an attempt is made to use the + global server. + generic_data_container : ansys.dpf.core.generic_data_container, optional + Generic data container that is wrapped into the mesh info. + mesh_info : optional + Hold the information of the mesh region into a generic data container. + + Examples + -------- + Explore the mesh info from the model + + >>> from ansys.dpf import core as dpf + >>> from ansys.dpf.core import examples + >>> fluent = examples.fluid_axial_model() + >>> model = dpf.Model(fluent) + >>> mesh_info = model.metadata.mesh_info + + """ + + def __init__( + self, + generic_data_container=None, + mesh_info=None, + server=None, + ): + """Initialize with a MeshInfo message""" + # ############################ + # step 1: get server + + if generic_data_container is None and mesh_info is None: + self._server = server_module.get_or_create_server(server) + self._generic_data_container = GenericDataContainer(server=self._server) + elif generic_data_container is not None and mesh_info is None: + self._server = generic_data_container._server + self._generic_data_container = generic_data_container + elif generic_data_container is None and MeshInfo is not None: + self._server = mesh_info.generic_data_container._server + self._generic_data_container = mesh_info.generic_data_container + else: + raise ValueError( + "Arguments generic_data_container and mesh_info are mutually exclusive." + ) + + @property + def generic_data_container(self) -> GenericDataContainer: + """GenericDataContainer wrapped into the MeshInfo + that contains all the relative information of the derived class. + + Returns + ------- + :class:`ansys.dpf.core.generic_data_container.GenericDataContainer` + + """ + + return self._generic_data_container + + @generic_data_container.setter + def generic_data_container(self, value: GenericDataContainer): + """GenericDataContainer wrapped into the MeshInfo + that contains all the relative information of the derived class. + """ + + if not isinstance(value, GenericDataContainer): + raise ValueError("Input value must be a GenericDataContainer.") + self._generic_data_container = value + self._server = self._generic_data_container._server + + def deep_copy(self, server=None): + """Create a deep copy of the mesh_info's data on a given server. + + This method is useful for passing data from one server instance to another. + + Parameters + ---------- + server : ansys.dpf.core.server, optional + Server with the channel connected to the remote or local instance. + The default is ``None``, in which case an attempt is made to use the + global server. + + Returns + ------- + mesh_info_copy : MeshInfo + """ + mesh_info = MeshInfo(server=server) + mesh_info.generic_data_container = self._generic_data_container.deep_copy(server) + return mesh_info + + def get_property(self, property_name, output_type): + """Get property with given name. + + Parameters + ---------- + property_name : str + Property name. + output_type : :class:`ansys.dpf.core.common.types` + + Returns + ------- + type + Property object instance. + """ + return self.generic_data_container.get_property(property_name, output_type) + + def set_property(self, property_name, prop): + """Register given property with the given name. + + Parameters + ---------- + property_name : str + Property name. + prop : Int, String, Float, Field, StringField, GenericDataContainer, Scoping + object instance. + """ + + return self.generic_data_container.set_property(property_name, prop) + + @property + def get_number_nodes(self): + """ + Returns + ------- + number_nodes : int + Number of nodes of the mesh. + """ + + return self.generic_data_container.get_property("num_nodes", int) + + @property + def get_number_elements(self): + """ + Returns + ------- + number_elements : int + Number of elements of the mesh. + """ + + return self.generic_data_container.get_property("num_elements", int) + + @property + def get_splittable_by(self): + """ + Returns + ------- + splittable by which entity : StringField + Name of the properties according to which the mesh can be split by. + """ + + return self.generic_data_container.get_property("splittable_by", StringField) + + @property + def get_available_elem_types(self): + """ + Returns + ------- + available element types : Scoping + element type available for the mesh. + """ + + return self.generic_data_container.get_property("avalaible_elem_type", Scoping) + + def set_number_nodes(self, number_of_nodes): + """Set the number of nodes in the mesh""" + + return self.generic_data_container.set_property("num_nodes", number_of_nodes) + + def set_number_elements(self, number_of_elements): + """Set the number of elements in the mesh""" + + return self.generic_data_container.set_property("num_elements", number_of_elements) + + def set_splittable_by(self, split): + """Set name of the properties according to which the mesh can be split by""" + + return self.generic_data_container.set_property("splittable_by", split) + + def set_available_elem_types(self, available_elem_types): + """Set the available element types""" + + return self.generic_data_container.set_property("avalaible_elem_type", available_elem_types) diff --git a/src/ansys/dpf/core/model.py b/src/ansys/dpf/core/model.py index e0bc7640b3..f1bb34e21d 100644 --- a/src/ansys/dpf/core/model.py +++ b/src/ansys/dpf/core/model.py @@ -65,6 +65,7 @@ def metadata(self): - ``time_freq_support`` - ``result_info`` - ``mesh_provider`` + - ``mesh_info`` Returns ------- @@ -268,6 +269,7 @@ def __init__(self, data_sources, server): self._meshed_region = None self._meshes_container = None self._result_info = None + self._mesh_info = None self._stream_provider = None self._time_freq_support = None self._mesh_selection_manager = None @@ -279,6 +281,11 @@ def _cache_result_info(self): if not self._result_info: self._result_info = self._load_result_info() + def _cache_mesh_info(self): + """Store mesh information.""" + if not self._mesh_info: + self._mesh_info = self._load_mesh_info() + def _cache_streams_provider(self): """Create a stream provider and cache it.""" from ansys.dpf.core import operators @@ -424,6 +431,22 @@ def _load_result_info(self): return None return result_info + def _load_mesh_info(self): + """Returns a mesh info object""" + op = Operator("mesh_info_provider", server=self._server) + op.inputs.connect(self._stream_provider.outputs) + try: + mesh_info = op.outputs.mesh_info() + except Exception as e: + # give the user a more helpful error + if "results file is not defined in the Data sources" in e.args: + raise RuntimeError("Unable to open result file") from None + else: + raise e + except: + return None + return mesh_info + @property @protect_source_op_not_found def meshed_region(self): @@ -482,6 +505,20 @@ def result_info(self): return self._result_info + @property + @version_requires("7.0") + @protect_source_op_not_found + def mesh_info(self): + """Mesh Info instance. + + Returns + ------- + mesh_info : :class:`ansys.dpf.core.mesh_info.MeshInfo` + """ + self._cache_mesh_info() + + return self._mesh_info + @property @version_requires("4.0") def meshes_container(self): diff --git a/src/ansys/dpf/core/operator_specification.py b/src/ansys/dpf/core/operator_specification.py index 35c82b6cd6..2f67d482a0 100644 --- a/src/ansys/dpf/core/operator_specification.py +++ b/src/ansys/dpf/core/operator_specification.py @@ -15,6 +15,7 @@ ) from ansys.dpf.core import mapping_types, common from ansys.dpf.core.check_version import version_requires +from ansys.dpf.core.check_version import server_meet_version class PinSpecification: @@ -54,13 +55,17 @@ class PinSpecification: document: str optional: bool ellipsis: bool + name_derived_class = str - def __init__(self, name, type_names, document="", optional=False, ellipsis=False): + def __init__( + self, name, type_names, document="", optional=False, ellipsis=False, name_derived_class="" + ): self.name = name self.type_names = type_names self.optional = optional self.document = document self.ellipsis = ellipsis + self.name_derived_class = name_derived_class @property def type_names(self): @@ -100,7 +105,12 @@ def type_names(self, val): @staticmethod def _get_copy(other, changed_types): return PinSpecification( - other.name, changed_types, other.document, other.optional, other.ellipsis + other.name, + changed_types, + other.document, + other.optional, + other.ellipsis, + other.name_derived_class, ) def __repr__(self): @@ -109,6 +119,7 @@ def __repr__(self): params=", ".join( "{param}={value}".format(param=k, value=f"'{v}'" if isinstance(v, str) else v) for k, v in vars(self).items() + if not ("{param}" == "name_derived_class" and "name_derived_class" != "") ), ) @@ -322,7 +333,8 @@ def inputs(self) -> dict: >>> 4 in operator.specification.inputs.keys() True >>> operator.specification.inputs[4] - PinSpecification(name='data_sources', _type_names=['data_sources'], ...set', ellipsis=False) + PinSpecification(name='data_sources', _type_names=['data_sources'], ...set', ellipsis=False, + name_derived_class='') """ if self._map_input_pin_spec is None: self._map_input_pin_spec = {} @@ -342,7 +354,8 @@ def outputs(self) -> dict: >>> from ansys.dpf import core as dpf >>> operator = dpf.operators.mesh.mesh_provider() >>> operator.specification.outputs - {0: PinSpecification(name='mesh', _type_names=['abstract_meshed_region'], ...=False)} + {0: PinSpecification(name='mesh', _type_names=['abstract_meshed_region'], ...=False, + name_derived_class='')} """ if self._map_output_pin_spec is None: self._map_output_pin_spec = {} @@ -369,9 +382,22 @@ def _fill_pins(self, binput, to_fill): for i_type in range(n_types) ] + pin_derived_class_type_name = "" + if server_meet_version("7.0", self._server): + pin_derived_class_type_name = ( + self._api.operator_specification_get_pin_derived_class_type_name( + self, binput, i_pin + ) + ) + pin_ell = self._api.operator_specification_is_pin_ellipsis(self, binput, i_pin) to_fill[i_pin] = PinSpecification( - pin_name, pin_type_names, pin_doc, pin_opt, pin_ell + pin_name, + pin_type_names, + pin_doc, + pin_opt, + pin_ell, + pin_derived_class_type_name, ) @property @@ -588,17 +614,31 @@ def inputs(self) -> dict: def inputs(self, val: dict): for key, value in val.items(): list_types = integral_types.MutableListString(value.type_names) - self._api.operator_specification_set_pin( - self, - True, - key, - value.name, - value.document, - len(value.type_names), - list_types, - value.optional, - value.ellipsis, - ) + if server_meet_version("7.0", self._server): + self._api.operator_specification_set_pin_derived_class( + self, + True, + key, + value.name, + value.document, + len(value.type_names), + list_types, + value.optional, + value.ellipsis, + value.name_derived_class, + ) + else: + self._api.operator_specification_set_pin( + self, + True, + key, + value.name, + value.document, + len(value.type_names), + list_types, + value.optional, + value.ellipsis, + ) @property @version_requires("4.0") @@ -615,17 +655,31 @@ def outputs(self) -> dict: def outputs(self, val: dict): for key, value in val.items(): list_types = integral_types.MutableListString(value.type_names) - self._api.operator_specification_set_pin( - self, - False, - key, - value.name, - value.document, - len(value.type_names), - list_types, - value.optional, - value.ellipsis, - ) + if server_meet_version("7.0", self._server): + self._api.operator_specification_set_pin_derived_class( + self, + False, + key, + value.name, + value.document, + len(value.type_names), + list_types, + value.optional, + value.ellipsis, + value.name_derived_class, + ) + else: + self._api.operator_specification_set_pin( + self, + False, + key, + value.name, + value.document, + len(value.type_names), + list_types, + value.optional, + value.ellipsis, + ) @property @version_requires("4.0") diff --git a/src/ansys/dpf/core/operators/build.py b/src/ansys/dpf/core/operators/build.py index 7f81267e5e..6cd42d2d97 100644 --- a/src/ansys/dpf/core/operators/build.py +++ b/src/ansys/dpf/core/operators/build.py @@ -59,6 +59,9 @@ def build_pin_data(pins, output=False): specification = pins[id] type_names = specification.type_names + + derived_class_type_name = specification.name_derived_class + if specification.ellipsis: type_names = update_type_names_for_ellipsis(type_names) docstring_types = map_types(type_names) @@ -85,9 +88,11 @@ def build_pin_data(pins, output=False): "name": pin_name, "pin_name": pin_name, # Base pin name, without numbers for when pin is ellipsis "has_types": len(type_names) >= 1, + "has_derived_class": len(derived_class_type_name) >= 1, "multiple_types": multiple_types, "printable_type_names": printable_type_names, "types": type_names, + "derived_type_name": derived_class_type_name, "types_for_docstring": parameter_types, "main_type": main_type, "built_in_main_type": main_type in built_in_types, diff --git a/src/ansys/dpf/core/operators/operator.mustache b/src/ansys/dpf/core/operators/operator.mustache index a69a0a893e..0e86c561e2 100644 --- a/src/ansys/dpf/core/operators/operator.mustache +++ b/src/ansys/dpf/core/operators/operator.mustache @@ -89,6 +89,9 @@ class {{class_name}}(Operator): {{/has_types}} optional={{optional}}, document="""{{{document}}}""", + {{#has_derived_class}} + name_derived_class=["{{{derived_type_name}}}"], + {{/has_derived_class}} ), {{/input_pins}} }, @@ -101,6 +104,9 @@ class {{class_name}}(Operator): {{/has_types}} optional={{optional}}, document="""{{{document}}}""", + {{#has_derived_class}} + name_derived_class=["{{{derived_type_name}}}"], + {{/has_derived_class}} ), {{/output_pins}} }, @@ -224,7 +230,12 @@ class Outputs{{capital_class_name}}(_Outputs): Returns ---------- + {{#derived_type_name}} + my_{{name}} : {{derived_type_name}} + {{/derived_type_name}} + {{^derived_type_name}} my_{{name}} :{{#types_for_docstring}} {{types_for_docstring}}{{/types_for_docstring}} + {{/derived_type_name}} Examples -------- diff --git a/src/ansys/dpf/core/outputs.py b/src/ansys/dpf/core/outputs.py index 8184de914a..610f7bd67a 100644 --- a/src/ansys/dpf/core/outputs.py +++ b/src/ansys/dpf/core/outputs.py @@ -39,6 +39,7 @@ def __init__(self, spec, pin, operator): def get_data(self): """Retrieves the output of the operator.""" type_output = self._spec.type_names[0] + if type_output == "abstract_meshed_region": type_output = types.meshed_region elif type_output == "abstract_data_tree": @@ -56,7 +57,17 @@ def get_data(self): elif type_output == "vector": type_output = types.vec_int - return self._operator.get_output(self._pin, type_output) + type_output_derive_class = self._spec.name_derived_class + + if type_output_derive_class != "": + out_type = [ + type_tuple + for type_tuple in self._operator._type_to_output_method + if type_output_derive_class in type_tuple + ] + return out_type[0][0](self._operator.get_output(self._pin, type_output)) + else: + return self._operator.get_output(self._pin, type_output) def __call__(self): return self.get_data() diff --git a/tests/conftest.py b/tests/conftest.py index 632c1fc78a..e6e03a677a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -217,12 +217,17 @@ def cyclic_multistage(): @pytest.fixture() def fluent_axial_comp(): - """Create a data sources with a cas and a dat file of fluent axial compressor case.""" - ds = core.DataSources() - files = examples.download_fluent_axial_comp() - ds.set_result_file_path(files["cas"][0], "cas") - ds.add_file_path(files["dat"][0], "dat") - return ds + """Return a function which creates a data sources + with a cas and a dat file of fluent axial compressor case.""" + + def return_ds(server=None): + ds = core.DataSources(server=server) + files = examples.download_fluent_axial_comp(server=server) + ds.set_result_file_path(files["cas"][0], "cas") + ds.add_file_path(files["dat"][0], "dat") + return ds + + return return_ds @pytest.fixture() diff --git a/tests/test_faces.py b/tests/test_faces.py index 0400f47733..3f15c441f6 100644 --- a/tests/test_faces.py +++ b/tests/test_faces.py @@ -1,6 +1,5 @@ import pytest import conftest -import platform from ansys.dpf import core as dpf from ansys.dpf.core.elements import element_types from ansys.dpf.core import mesh_scoping_factory @@ -8,12 +7,11 @@ @pytest.fixture() def model_faces(fluent_axial_comp): - model = dpf.Model(fluent_axial_comp) + model = dpf.Model(fluent_axial_comp()) faces = model.metadata.meshed_region.faces return faces -@pytest.mark.skipif(platform.system() == "Linux", reason="CFF not available for Linux InProcess.") @pytest.mark.skipif( not conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="mesh faces were not supported before 7.0", @@ -39,7 +37,6 @@ def test_faces(model_faces): assert mask[1] == True -@pytest.mark.skipif(platform.system() == "Linux", reason="CFF not available for Linux InProcess.") @pytest.mark.skipif( not conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="mesh faces were not supported before 7.0", diff --git a/tests/test_mesh_info.py b/tests/test_mesh_info.py new file mode 100644 index 0000000000..a12df5bed2 --- /dev/null +++ b/tests/test_mesh_info.py @@ -0,0 +1,462 @@ +import ansys.dpf.core.generic_data_container +from ansys.dpf import core as dpf +from conftest import ( + SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, +) +import pytest +from ansys.dpf.core import examples + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_load_cff_mesh_info_operator(server_type): + mesh_info = dpf.Operator(name="cff::cas::mesh_info_provider", server=server_type) + assert mesh_info is not None + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_load_cff_model(fluent_multi_species, server_type): + model = dpf.Model(fluent_multi_species(server=server_type), server=server_type) + mesh_provider = model.metadata.mesh_provider + mesh_info = model.metadata.mesh_info + assert mesh_info and mesh_provider is not None + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_create_mesh_info(server_type): + mesh_info = dpf.MeshInfo(server=server_type) + assert mesh_info is not None + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_mesh_info_generic_data_container_getter_model(fluent_multi_species, server_type): + model = dpf.Model(fluent_multi_species(server_type), server=server_type) + mesh_info = model.metadata.mesh_info + gdc = mesh_info.generic_data_container + assert isinstance(gdc, ansys.dpf.core.generic_data_container.GenericDataContainer) + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_mesh_info_generic_data_container_setter(fluent_multi_species, server_type): + model = dpf.Model(fluent_multi_species(server_type), server=server_type) + mesh_info = model.metadata.mesh_info + gdc = mesh_info.generic_data_container + gdc.set_property("property_name_00", 0) + mesh_info.generic_data_container = gdc + assert mesh_info.generic_data_container == gdc + with pytest.raises(ValueError) as e: + mesh_info.generic_data_container = "Wrong type" + assert "Input value must be a GenericDataContainer." in e + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_mesh_info_generic_data_container_setter_grpc( + fluent_multi_species, server_type_remote_process +): + model = dpf.Model( + fluent_multi_species(server_type_remote_process), server=server_type_remote_process + ) + mesh_info = model.metadata.mesh_info + gdc = mesh_info.generic_data_container + gdc.set_property("property_name_00", 0) + mesh_info.generic_data_container = gdc + assert mesh_info.generic_data_container == gdc + with pytest.raises(ValueError) as e: + mesh_info.generic_data_container = "Wrong type" + assert "Input value must be a GenericDataContainer." in e + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_set_get_num_of(server_type): + mesh_info = dpf.MeshInfo(server=server_type) + # """Number of nodes""" + num_nodes = 189 + mesh_info.set_number_nodes(189) + assert mesh_info.get_number_nodes == num_nodes + # """ Number of elements """ + num_elements = 2 + mesh_info.set_number_elements(2) + assert mesh_info.get_number_elements == num_elements + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_set_get_property_mesh_info(server_type): + mesh_info = dpf.MeshInfo(server=server_type) + + # """Scoping""" + scoping = dpf.Scoping(server=server_type) + expected_ids = [1, 2, 3] + scoping._set_ids(expected_ids) + mesh_info.set_property("my-property00", scoping) + result_scoping = mesh_info.get_property("my-property00", dpf.Scoping) + for x in range(len(expected_ids)): + assert result_scoping.id(x) == scoping.id(x) + + # """ Field """ + field = dpf.Field(server=server_type) + mesh_info.set_property("my-property01", field) + result_field = mesh_info.get_property("my-property01", dpf.Field) + assert result_field.component_count == field.component_count + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_set_get_splittable_by_mesh_info(server_type): + mesh_info = dpf.MeshInfo(server=server_type) + splittable = dpf.StringField(server=server_type) + expected_splittable = ["split_01", "split_02", "split_03"] + splittable.append(expected_splittable, 1) + mesh_info.set_splittable_by(splittable) + result_splittable = mesh_info.get_splittable_by + assert result_splittable.data[0] == expected_splittable[0] + assert result_splittable.data[1] == expected_splittable[1] + assert result_splittable.data[2] == expected_splittable[2] + assert len(result_splittable.scoping.ids) == 1 + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_set_get_available_elem_types_mesh_info(server_type): + mesh_info = dpf.MeshInfo(server=server_type) + available_results_ids = [1, 2, 3] + available_results = dpf.Scoping(server=server_type) + available_results._set_ids(available_results_ids) + mesh_info.set_available_elem_types(available_results) + result_available = mesh_info.get_available_elem_types + for x in range(len(available_results)): + assert result_available.id(x) == available_results.id(x) + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_output_mesh_info_provider_fluent(server_clayer): + ds = dpf.DataSources(server=server_clayer) + files = examples.download_fluent_multi_species() + ds.set_result_file_path(files["cas"], "cas") + + mesh_info = dpf.operators.metadata.mesh_info_provider(server=server_clayer) + mesh_info.connect(4, ds) + mesh_info_out = mesh_info.outputs.mesh_info() + + assert isinstance(mesh_info_out, dpf.mesh_info.MeshInfo) + + # """************************ NUMBER OF CELLS/FACES/ZONES ************************""" + num_cells = mesh_info_out.get_property("num_cells", int) + num_faces = mesh_info_out.get_property("num_faces", int) + num_nodes = mesh_info_out.get_property("num_nodes", int) + + assert num_cells == 1344 + assert num_faces == 2773 + assert num_nodes == 1430 + + # """************************ BODIES ************************""" + + # """************ Name ************""" + body_names = mesh_info_out.get_property("body_name", dpf.StringField) + + body_names_value = body_names._get_data() + + assert len(body_names_value) == 1 + assert body_names_value[0] == "fluid-1" + + # """************ Scoping ************""" + body_scoping = mesh_info_out.get_property("body_scoping", dpf.Scoping) + + assert body_scoping.size == 1 + assert body_scoping[0] == 1 + + # """************ Topology ************""" + body_cell_topology = mesh_info_out.get_property("body_cell_topology", dpf.PropertyField) + body_face_topology = mesh_info_out.get_property("body_face_topology", dpf.PropertyField) + + body_cell_topology_scoping = body_cell_topology._get_scoping() + body_face_topology_scoping = body_face_topology._get_scoping() + body_cell_topology_value = body_cell_topology._get_data() + body_face_topology_value = body_face_topology._get_data() + + assert body_cell_topology_scoping.size == 1 + assert body_face_topology_scoping.size == 5 + assert body_cell_topology_scoping[0] == 1 + assert body_face_topology_scoping[0] == 1 + assert body_cell_topology_value[0] == 1 + assert body_face_topology_value[0] == 3 + + # """************************ ZONES ************************""" + + # """************ Name ************""" + zone_names = mesh_info_out.get_property("zone_name", dpf.StringField) + + zone_names_value = zone_names._get_data() + + assert zone_names_value.size == 6 + assert zone_names_value[0] == "fluid-1" + assert zone_names_value[1] == "interior-3" + assert zone_names_value[2] == "symmetry-4" + assert zone_names_value[3] == "pressure-outlet-5" + assert zone_names_value[5] == "velocity-inlet-7" + + # """************ Scoping ************""" + zone_scoping = mesh_info_out.get_property("zone_scoping", dpf.Scoping) + + assert zone_scoping.size == 6 + assert zone_scoping[0] == 1 + assert zone_scoping[1] == 3 + assert zone_scoping[2] == 4 + assert zone_scoping[3] == 5 + assert zone_scoping[5] == 7 + + # """************ Element ************""" + zone_elements = mesh_info_out.get_property("num_elem_zone", dpf.PropertyField) + + number_of_element_in_zone_value = zone_elements._get_data() + + assert number_of_element_in_zone_value.size == 6 + assert number_of_element_in_zone_value[0] == 1344 + assert number_of_element_in_zone_value[1] == 2603 + assert number_of_element_in_zone_value[2] == 64 + assert number_of_element_in_zone_value[3] == 21 + assert number_of_element_in_zone_value[5] == 15 + + # """************ CELL ZONES ************""" + + # """************ Name ************""" + cell_zone_name = mesh_info_out.get_property("cell_zone_names", dpf.StringField) + + cell_zone_name_value = cell_zone_name._get_data() + + assert cell_zone_name_value.size == 1 + assert cell_zone_name_value[0] == "fluid-1" + + # """************ Scoping ************""" + cell_zone_scoping = mesh_info_out.get_property("cell_zone_scoping", dpf.Scoping) + + assert cell_zone_scoping.size == 1 + assert cell_zone_scoping[0] == 1 + + # """************ Element ************""" + cell_zone_elements = mesh_info_out.get_property("cell_zone_elements", dpf.PropertyField) + + cell_zone_elements_value = cell_zone_elements._get_data() + + assert cell_zone_elements_value.size == 1 + assert cell_zone_elements_value[0] == 1344 + + # """************ FACE ZONES ************""" + + # """************ Name ************""" + face_zone_names = mesh_info_out.get_property("face_zone_names", dpf.StringField) + + face_zone_names_value = face_zone_names._get_data() + + assert face_zone_names_value.size == 5 + assert face_zone_names_value[0] == "interior-3" + assert face_zone_names_value[1] == "symmetry-4" + assert face_zone_names_value[2] == "pressure-outlet-5" + assert face_zone_names_value[3] == "wall-6" + assert face_zone_names_value[4] == "velocity-inlet-7" + + # """************ Scoping ************""" + face_zone_scoping = mesh_info_out.get_property("face_zone_scoping", dpf.Scoping) + + assert face_zone_scoping.size == 5 + assert face_zone_scoping[0] == 3 + assert face_zone_scoping[1] == 4 + assert face_zone_scoping[2] == 5 + assert face_zone_scoping[3] == 6 + assert face_zone_scoping[4] == 7 + + # """************ Element ************""" + face_zone_elements = mesh_info_out.get_property("face_zone_elements", dpf.PropertyField) + + face_zone_elements_value = face_zone_elements._get_data() + + assert face_zone_elements_value.size == 5 + assert face_zone_elements_value[0] == 2603 + assert face_zone_elements_value[1] == 64 + assert face_zone_elements_value[2] == 21 + assert face_zone_elements_value[3] == 70 + assert face_zone_elements_value[4] == 15 + + +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_output_mesh_info_provider_flprj(fluent_axial_comp, server_clayer): + model = dpf.Model(fluent_axial_comp(server_clayer), server=server_clayer) + res = model.metadata.mesh_info + + # """************************ NUMBER OF CELLS/FACES/ZONES ************************""" + + num_cells = res.get_property("num_cells", int) + num_faces = res.get_property("num_faces", int) + num_nodes = res.get_property("num_nodes", int) + + assert num_cells == 13856 + assert num_faces == 45391 + assert num_nodes == 16660 + + # """************************ BODIES ************************""" + + # """************ Name ************""" + body_names = res.get_property("body_name", dpf.StringField) + + body_names_value = body_names._get_data() + + assert len(body_names_value) == 2 + assert body_names_value[0] == "fluid-rotor" + assert body_names_value[1] == "fluid-stator" + + # """************ Scoping ************""" + body_scoping = res.get_property("body_scoping", dpf.Scoping) + + assert body_scoping.size == 2 + assert body_scoping[0] == 13 + assert body_scoping[1] == 28 + + # """************ Topology ************""" + body_cell_topology = res.get_property("body_cell_topology", dpf.PropertyField) + body_face_topology = res.get_property("body_face_topology", dpf.PropertyField) + + body_cell_topology_scoping = body_cell_topology._get_scoping() + body_face_topology_scoping = body_face_topology._get_scoping() + body_cell_topology_value = body_cell_topology._get_data() + body_face_topology_value = body_face_topology._get_data() + + assert body_cell_topology_scoping.size == 2 + assert body_face_topology_scoping.size == 24 + assert body_cell_topology_scoping[0] == 13 + assert body_face_topology_scoping[0] == 13 + assert body_cell_topology_value[0] == 13 + assert body_face_topology_value[0] == 2 + + # """************************ ZONES ************************""" + + # """************ Name ************""" + zone_names = res.get_property("zone_name", dpf.StringField) + + zone_names_value = zone_names._get_data() + + assert zone_names_value.size == 26 + assert zone_names_value[0] == "fluid-rotor" + assert zone_names_value[4] == "rotor-inlet" + assert zone_names_value[8] == "rotor-per-1-shadow" + assert zone_names_value[12] == "fluid-stator" + assert zone_names_value[15] == "stator-shroud" + assert zone_names_value[18] == "stator-blade-1" + assert zone_names_value[22] == "stator-per-2" + assert zone_names_value[25] == "stator-per-1-shadow" + + # """************ Scoping ************""" + zone_scoping = res.get_property("zone_scoping", dpf.Scoping) + + assert zone_scoping.size == 26 + assert zone_scoping[0] == 13 + assert zone_scoping[4] == 5 + assert zone_scoping[8] == 9 + assert zone_scoping[12] == 28 + assert zone_scoping[15] == 17 + assert zone_scoping[18] == 20 + assert zone_scoping[22] == 24 + assert zone_scoping[25] == 27 + + # """************ Element ************""" + zone_elements = res.get_property("num_elem_zone", dpf.PropertyField) + + number_of_element_in_zone_value = zone_elements._get_data() + + assert number_of_element_in_zone_value.size == 26 + assert number_of_element_in_zone_value[0] == 6080 + assert number_of_element_in_zone_value[4] == 160 + assert number_of_element_in_zone_value[8] == 176 + assert number_of_element_in_zone_value[12] == 7776 + assert number_of_element_in_zone_value[15] == 486 + assert number_of_element_in_zone_value[18] == 320 + assert number_of_element_in_zone_value[22] == 48 + assert number_of_element_in_zone_value[25] == 64 + + # """************ CELL ZONES ************""" + + # """************ Name ************""" + cell_zone_name = res.get_property("cell_zone_names", dpf.StringField) + + cell_zone_name_value = cell_zone_name._get_data() + + assert cell_zone_name_value.size == 2 + assert cell_zone_name_value[0] == "fluid-rotor" + assert cell_zone_name_value[1] == "fluid-stator" + + # """************ Scoping ************""" + cell_zone_scoping = res.get_property("cell_zone_scoping", dpf.Scoping) + + assert cell_zone_scoping.size == 2 + assert cell_zone_scoping[0] == 13 + assert cell_zone_scoping[1] == 28 + + # """************ Element ************""" + cell_zone_elements = res.get_property("cell_zone_elements", dpf.PropertyField) + + cell_zone_elements_value = cell_zone_elements._get_data() + + assert cell_zone_elements_value.size == 2 + assert cell_zone_elements_value[0] == 6080 + assert cell_zone_elements_value[1] == 7776 + + # """************ FACE ZONES ************""" + + # """************ Name ************""" + face_zone_names = res.get_property("face_zone_names", dpf.StringField) + + face_zone_names_value = face_zone_names._get_data() + + assert face_zone_names_value.size == 24 + assert face_zone_names_value[0] == "default-interior:0" + assert face_zone_names_value[1] == "rotor-hub" + assert face_zone_names_value[5] == "rotor-blade-1" + assert face_zone_names_value[10] == "rotor-per-2" + assert face_zone_names_value[15] == "stator-outlet" + assert face_zone_names_value[20] == "stator-per-2" + assert face_zone_names_value[23] == "stator-per-1-shadow" + + # """************ Scoping ************""" + face_zone_scoping = res.get_property("face_zone_scoping", dpf.Scoping) + + assert face_zone_scoping.size == 24 + assert face_zone_scoping[0] == 2 + assert face_zone_scoping[1] == 3 + assert face_zone_scoping[5] == 7 + assert face_zone_scoping[10] == 12 + assert face_zone_scoping[15] == 19 + assert face_zone_scoping[20] == 24 + assert face_zone_scoping[23] == 27 + + # """************ Element ************""" + face_zone_elements = res.get_property("face_zone_elements", dpf.PropertyField) + + face_zone_elements_value = face_zone_elements._get_data() + + assert face_zone_elements_value.size == 24 + assert face_zone_elements_value[0] == 17092 + assert face_zone_elements_value[1] == 380 + assert face_zone_elements_value[5] == 384 + assert face_zone_elements_value[10] == 48 + assert face_zone_elements_value[15] == 288 + assert face_zone_elements_value[20] == 48 + assert face_zone_elements_value[23] == 64 diff --git a/tests/test_python_plugins.py b/tests/test_python_plugins.py index 8a4d8783f1..602ac75cbe 100644 --- a/tests/test_python_plugins.py +++ b/tests/test_python_plugins.py @@ -12,6 +12,9 @@ CustomConfigOptionSpec, PinSpecification, ) +from conftest import ( + SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, +) if not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_4_0: pytest.skip("Requires server version higher than 4.0", allow_module_level=True) @@ -243,6 +246,64 @@ def test_create_op_specification(server_in_process): assert spec.config_specification["work_by_index"].default_value_str == "false" +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Available for servers >=7.0" +) +def test_create_op_specification_with_derived_class(server_in_process): + spec = CustomSpecification(server=server_in_process) + spec.description = "Add derived class in op specification" + spec.inputs = { + 0: PinSpecification( + name="time_scoping", + type_names=["int32"], + optional=True, + document="""Optional time/frequency set id of the mesh.""", + ), + 3: PinSpecification( + name="streams_container", + type_names=["streams_container"], + optional=True, + document="""Streams (mesh file container) (optional)""", + ), + 4: PinSpecification( + name="data_sources", + type_names=["data_sources"], + optional=False, + document="""If the stream is null, retrieves the file + path from the data sources.""", + ), + } + spec.outputs = { + 0: PinSpecification( + name="mesh_info", + type_names=["generic_data_container"], + optional=False, + document="""""", + name_derived_class="mesh_info", + ), + } + spec.properties = SpecificationProperties("mesh info provider", "metadata") + spec.config_specification = [ + CustomConfigOptionSpec("mesh_info_provider", False, "gives mesh info") + ] + assert spec.description == "Add derived class in op specification" + assert len(spec.inputs) == 3 + assert spec.inputs[0].name == "time_scoping" + assert spec.inputs[0].type_names == ["int32"] + assert spec.inputs[3].document == """Streams (mesh file container) (optional)""" + assert spec.outputs[0] == PinSpecification( + name="mesh_info", + type_names=["generic_data_container"], + optional=False, + document="""""", + name_derived_class="mesh_info", + ) + assert spec.properties["exposure"] == "public" + assert spec.properties["category"] == "metadata" + assert spec.config_specification["mesh_info_provider"].document == "gives mesh info" + assert spec.config_specification["mesh_info_provider"].default_value_str == "false" + + @conftest.raises_for_servers_version_under("4.0") def test_create_config_op_specification(server_in_process): spec = CustomSpecification(server=server_in_process) diff --git a/tests/test_resultinfo.py b/tests/test_resultinfo.py index 1dca839925..5f428bb629 100644 --- a/tests/test_resultinfo.py +++ b/tests/test_resultinfo.py @@ -1,5 +1,5 @@ -import platform import pytest +import platform from ansys import dpf from ansys.dpf.core import Model @@ -97,31 +97,13 @@ def test_print_available_result_with_qualifiers(cfx_heating_coil): Homogeneity: specific_heat Units: j/kg*k^-1 Location: Nodal -Available qualifier labels: - - phase: Water at 25 C (2), Copper (3) - - zone: Default 1 (5), ZN1/FS1 (9), ZN1/FS2 (10), ZN1/FS3 (11), ZN1/FS4 (12), ZN1/FS5 (13), ZN1/FS6 (14), ZN1/FS7 (15), ZN1/FS8 (16), ZN1/FS9 (17), ZN1/FS10 (18), heater (8), ZN2/FS1 (19), ZN2/FS2 (20), ZN2/FS3 (21), ZN2/FS4 (22), ZN2/FS5 (23), ZN2/FS6 (24), ZN2/FS7 (25), ZN2/FS8 (26) -Available qualifier combinations: - {'phase': 2, 'zone': 5} - {'phase': 2, 'zone': 9} - {'phase': 2, 'zone': 10} - {'phase': 2, 'zone': 11} - {'phase': 2, 'zone': 12} - {'phase': 2, 'zone': 13} - {'phase': 2, 'zone': 14} - {'phase': 2, 'zone': 15} - {'phase': 2, 'zone': 16} - {'phase': 2, 'zone': 17} - {'phase': 2, 'zone': 18} - {'phase': 3, 'zone': 8} - {'phase': 3, 'zone': 19} - {'phase': 3, 'zone': 20} - {'phase': 3, 'zone': 21} - {'phase': 3, 'zone': 22} - {'phase': 3, 'zone': 23} - {'phase': 3, 'zone': 24} - {'phase': 3, 'zone': 25} - {'phase': 3, 'zone': 26}""" # noqa: E501 - assert ref in str(model.metadata.result_info.available_results[0]) +Available qualifier labels:""" # noqa: E501 + ref2 = "'phase': 2" + ref3 = "'zone': 5" + got = str(model.metadata.result_info.available_results[0]) + assert ref in got + assert ref2 in got + assert ref3 in got @pytest.mark.skipif(True, reason="Used to test memory leaks")