From 48f9bc8abe3ea0b99aafa12644ff6b60352e5f88 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 11 Jan 2022 18:58:21 +0100 Subject: [PATCH] Feat/Boundary conditions listing wrappers (#819) * Changed signal termination * Adding CommandOutput class. * Substituting the output in mapdl_grpc for custom class. * Making sure all the methods call the parent class (str) * Testing simplied version * Added unit tests * Added unit tests * Fixing the style * Fixing grammar. * Fixing grammar. * Merge branch 'feat/richer-command-output' of https://github.com/pyansys/pymapdl into feat/richer-command-output * Add test_class unit * Using first implementation because the second fail because of the modified `__getattribute_` * Changing implementation to not overwrite __class__. * Fixing sphinx building by rewriting __class__ method to not be overwriten. * Style check. * changing the API, cmd=command, and command= full command (cmd + args) * Adding CommandOutputDataframe class. * Small changes. * Big restructure. Now we detect the underlying data array based on a predefined magic word (start of data) and an empty line (end of data). * Added check if output is modified by /verify * Added docstrings to classes. * Moved fixtures to main conf file * Changed class name, added supported commands. Added support to Elist and Nlist * Added test units. * Added more unit test. * Fixing style. * small changes. * small change. * incorrect package name. * UserString Implementation * Simplification of unit test. * Simplification of unit test. * Using str as base class. * removed unused import. * Generalization of 'CommandListing' based on function 'paprnt.F'. * Apply suggestions from code review Co-authored-by: Alex Kaszynski * Cleanning some commented code. Removed @command_checker since we are opting for individual wrapping. * Added automatical wrapper for listing functions. * Removed automatical wrapper. * Fixing style. * changed method name to `to_list` * Fixing style * Added unit test. * Fixing grammar. * Added dlist wrapper * Externalizing @requires_pandas Added DLIST class * Fixing conftest? * Improving unit test to avoid empty object false assertion. * Fixing unit test * replacing solve for one which does not change format. * checking format * Fixing unit test mess * testing disabling new test. * removing unused imports * removing unused imports * test * fixing wrong check against empty array and df * Removed unused import * Changing class name. * Cleaning * Cleaning again. * Fixing style. * Fixing style. * Update ansys/mapdl/core/commands.py Co-authored-by: Alex Kaszynski * Added output checker * Adding suggestions. * Fixing wrong wrapper. * Improving test. * Moving the wrapping to main Mapdl, being common to gpc, console and corba. * Added docstring injector. * Apply suggestions from code review * Apply suggestions from code review * Replacing the check of the pandas package. Adding short doc string. * importing pandas lazily * removed trailing space. * Added docstring injector to dlist and flist * removing grpc wrapper * Adding unit test for docstring injector. * Added code suggestions. Co-authored-by: Alex Kaszynski --- ansys/mapdl/core/commands.py | 52 ++++++++++++++++++++++++++++++++-- ansys/mapdl/core/mapdl.py | 14 ++++++++- ansys/mapdl/core/mapdl_grpc.py | 1 - tests/test_commands.py | 49 ++++++++++++++++++++++++++++---- 4 files changed, 106 insertions(+), 10 deletions(-) diff --git a/ansys/mapdl/core/commands.py b/ansys/mapdl/core/commands.py index 3ce03e4d27..6f359254cd 100644 --- a/ansys/mapdl/core/commands.py +++ b/ansys/mapdl/core/commands.py @@ -30,13 +30,20 @@ pip install pandas """ +MSG_BCListingOutput_to_array = """This command has strings values in some of its columns (such 'UX', 'FX', 'UY', 'TEMP', etc), +so it cannot be converted to Numpy Array. + +Please use 'to_list' or 'to_dataframe' instead.""" + + # Identify where the data start in the output GROUP_DATA_START = ['NODE', 'ELEM'] # Allowed commands to get output as array or dataframe. # In theory, these commands should follow the same format. # Some of them are not documented (already deprecated?) -# So they won't be wrapped. +# So they are not in the Mapdl class, +# so they won't be wrapped. CMD_LISTING = [ 'NLIN', # not documented 'PRCI', @@ -66,6 +73,8 @@ 'SWLI' ] +CMD_BC_LISTING = ['FLIS', 'DLIS'] + # Adding empty lines to match current format. docstring_injection = """ Returns @@ -371,6 +380,16 @@ class Commands( """Wrapped MAPDL commands""" +def _requires_pandas(func): + """Wrapper that check ``HAS_PANDAS``, if not, it will raise an exception.""" + + def func_wrapper(self, *args, **kwargs): + if HAS_PANDAS: + return func(self, *args, **kwargs) + else: + raise ModuleNotFoundError(MSG_NOT_PANDAS) + return func_wrapper + class CommandOutput(str): """Custom string subclass for handling the commands output. @@ -556,7 +575,7 @@ def to_array(self): """ return np.array(self.to_list(), dtype=float) - def to_dataframe(self): + def to_dataframe(self, data=None, columns=None): """Export the command output as a Pandas DataFrame. Returns @@ -567,4 +586,31 @@ def to_dataframe(self): import pandas as pd except ModuleNotFoundError: raise ModuleNotFoundError(MSG_NOT_PANDAS) - return pd.DataFrame(data=self.to_array(), columns=self.get_columns()) + + if not data: + data = self.to_array() + if not columns: + columns = self.get_columns() + + return pd.DataFrame(data=data, columns=data) + + +class BoundaryConditionsListingOutput(CommandListingOutput): + def to_array(self): + raise ValueError(MSG_BCListingOutput_to_array) + + def to_dataframe(self): + df = super().to_dataframe(data=self.to_list()) + if 'NODE' in df.columns: + df['NODE'] = df['NODE'].astype(int) + + if 'LABEL' in df.columns: + df['LABEL'] = df['LABEL'].astype(str) + + if 'REAL' in df.columns: + df['REAL'] = df['REAL'].astype(float) + + if 'IMAG' in df.columns: + df['IMAG'] = df['IMAG'].astype(float) + + return df \ No newline at end of file diff --git a/ansys/mapdl/core/mapdl.py b/ansys/mapdl/core/mapdl.py index c0fef1782e..0b3a94dfcd 100644 --- a/ansys/mapdl/core/mapdl.py +++ b/ansys/mapdl/core/mapdl.py @@ -25,7 +25,7 @@ from ansys.mapdl.core.errors import MapdlRuntimeError, MapdlInvalidRoutineError from ansys.mapdl.core.plotting import general_plotter from ansys.mapdl.core.post import PostProcessing -from ansys.mapdl.core.commands import Commands, CommandListingOutput, CMD_LISTING, inject_docs +from ansys.mapdl.core.commands import Commands, CommandListingOutput, BoundaryConditionsListingOutput, CMD_LISTING, CMD_BC_LISTING, inject_docs from ansys.mapdl.core.inline_functions import Query from ansys.mapdl.core import LOG as logger from ansys.mapdl.reader.rst import Result @@ -177,11 +177,23 @@ def inner_wrapper(*args, **kwargs): return CommandListingOutput(func(*args, **kwargs)) return inner_wrapper + def wrap_BC_listing_function(func): + # Injecting doc string modification + func.__func__.__doc__ = inject_docs(func.__func__.__doc__) + @wraps(func) + def inner_wrapper(*args, **kwargs): + return BoundaryConditionsListingOutput(func(*args, **kwargs)) + return inner_wrapper + for name in dir(self): if name[0:4].upper() in CMD_LISTING: func = self.__getattribute__(name) setattr(self, name, wrap_listing_function(func)) + if name[0:4].upper() in CMD_BC_LISTING: + func = self.__getattribute__(name) + setattr(self, name, wrap_BC_listing_function(func)) + @property def _name(self): # pragma: no cover """Implemented by child class""" diff --git a/ansys/mapdl/core/mapdl_grpc.py b/ansys/mapdl/core/mapdl_grpc.py index b25d791a6e..91bb0f0f08 100755 --- a/ansys/mapdl/core/mapdl_grpc.py +++ b/ansys/mapdl/core/mapdl_grpc.py @@ -65,7 +65,6 @@ from ansys.mapdl.core import __version__, _LOCAL_PORTS from ansys.mapdl.core import check_version - TMP_VAR = '__tmpvar__' VOID_REQUEST = anskernel.EmptyRequest() diff --git a/tests/test_commands.py b/tests/test_commands.py index 395553659e..2529add958 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,13 +1,11 @@ import pytest import inspect -from ansys.mapdl.core.commands import CommandOutput as CommandOutput import numpy as np from ansys.mapdl.core import examples - -from ansys.mapdl.core.commands import CommandListingOutput -from ansys.mapdl.core.commands import CommandOutput +from ansys.mapdl.core.commands import CommandOutput, CommandListingOutput, BoundaryConditionsListingOutput +from ansys.mapdl.core.commands import CMD_LISTING, CMD_BC_LISTING try: import pandas as pd @@ -60,6 +58,8 @@ 'kcmplx': 1 } +CMD_DOC_STRING_INJECTOR = CMD_LISTING.copy() +CMD_DOC_STRING_INJECTOR.extend(CMD_BC_LISTING) @pytest.fixture(scope="module") def plastic_solve(mapdl): @@ -72,6 +72,16 @@ def plastic_solve(mapdl): mapdl.set(1, 2) mapdl.mute = False +@pytest.fixture(scope="module") +def beam_solve(mapdl): + mapdl.mute = True + mapdl.finish() + mapdl.clear() + mapdl.input(examples.verif_files.vmfiles["vm10"]) + + mapdl.post1() + mapdl.set(1, 2) + mapdl.mute = False def test_cmd_class(): output = """This is the output. @@ -105,7 +115,6 @@ def test_inquire_functions(mapdl, func): assert '=' in output -# @pytest.mark.skipif(not HAS_GRPC, reason="Requires GRPC") @pytest.mark.parametrize('func,args', [ ('prnsol', ('U', 'X')), ('presol', ('S', 'X')), @@ -126,3 +135,33 @@ def test_output_listing(mapdl, plastic_solve, func, args): if HAS_PANDAS: out_df = out.to_dataframe() assert isinstance(out_df, pd.DataFrame) and not out_df.empty + + +@pytest.mark.parametrize('func', ['dlist', 'flist']) +def test_bclist(mapdl, beam_solve, func): + func_ = getattr(mapdl, func) + out = func_() + + assert isinstance(out, BoundaryConditionsListingOutput) + assert isinstance(out.to_list(), list) and bool(out.to_list()) + + with pytest.raises(ValueError): + out.to_array() + + if HAS_PANDAS: + out_df = out.to_dataframe() + assert isinstance(out_df, pd.DataFrame) and not out_df.empty + + +@pytest.mark.parametrize('method', CMD_DOC_STRING_INJECTOR) +def test_docstring_injector(mapdl, method): + """Check if the docstring has been injected.""" + for name in dir(mapdl): + if name[0:4].upper() == method: + func = mapdl.__getattribute__(name) + docstring = func.__doc__ # If '__func__' not present (AttributeError) very likely it has not been wrapped. + + assert "Returns" in docstring + assert "``str.to_list()``" in docstring + assert "``str.to_array()``" in docstring + assert "``str.to_dataframe()``" in docstring