diff --git a/doc/changelog.d/3569.added.md b/doc/changelog.d/3569.added.md new file mode 100644 index 0000000000..b4f202ad3f --- /dev/null +++ b/doc/changelog.d/3569.added.md @@ -0,0 +1 @@ +refactor: annotate pymapdl part 1 \ No newline at end of file diff --git a/src/ansys/mapdl/core/__init__.py b/src/ansys/mapdl/core/__init__.py index 9ed873482d..52592a752f 100644 --- a/src/ansys/mapdl/core/__init__.py +++ b/src/ansys/mapdl/core/__init__.py @@ -20,8 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import importlib.metadata as importlib_metadata - ############################################################################### # Imports # ======= @@ -40,17 +38,16 @@ # from ansys.mapdl.core.logging import Logger -LOG = Logger(level=logging.ERROR, to_file=False, to_stdout=True) +LOG: Logger = Logger(level=logging.ERROR, to_file=False, to_stdout=True) LOG.debug("Loaded logging module as LOG") ############################################################################### # Globals # ======= # +from ansys.mapdl.core._version import __version__ from ansys.mapdl.core.helpers import is_installed, run_every_import, run_first_time -__version__: str = importlib_metadata.version(__name__.replace(".", "-")) - # A dictionary relating PyMAPDL server versions with the unified install ones VERSION_MAP: Dict[Tuple[int, int, int], str] = { (0, 0, 0): "2020R2", @@ -69,17 +66,21 @@ # Import related globals _HAS_ATP: bool = is_installed("ansys.tools.path") +_HAS_CLICK: bool = is_installed("click") _HAS_PIM: bool = is_installed("ansys.platform.instancemanagement") +_HAS_PANDAS: bool = is_installed("pandas") _HAS_PYANSYS_REPORT: bool = is_installed("ansys.tools.report") _HAS_PYVISTA: bool = is_installed("pyvista") +_HAS_REQUESTS: bool = is_installed("requests") _HAS_TQDM: bool = is_installed("tqdm") _HAS_VISUALIZER: bool = is_installed("ansys.tools.visualization_interface") + # Setup directories USER_DATA_PATH: str = user_data_dir(appname="ansys_mapdl_core", appauthor="Ansys") -EXAMPLES_PATH = os.path.join(USER_DATA_PATH, "examples") +EXAMPLES_PATH: str = os.path.join(USER_DATA_PATH, "examples") -# Store local ports +# Store ports occupied by local instances _LOCAL_PORTS: List[int] = [] ############################################################################### diff --git a/src/ansys/mapdl/core/_version.py b/src/ansys/mapdl/core/_version.py index 96081b15d2..ed3dafa51a 100644 --- a/src/ansys/mapdl/core/_version.py +++ b/src/ansys/mapdl/core/_version.py @@ -29,18 +29,15 @@ version_info = 0, 58, 'dev0' """ - -try: - import importlib.metadata as importlib_metadata -except ModuleNotFoundError: # pragma: no cover - import importlib_metadata +import importlib.metadata as importlib_metadata +from typing import Dict # Read from the pyproject.toml # major, minor, patch -__version__ = importlib_metadata.version("ansys-mapdl-core") +__version__: str = importlib_metadata.version("ansys-mapdl-core") # In descending order -SUPPORTED_ANSYS_VERSIONS = { +SUPPORTED_ANSYS_VERSIONS: Dict[int, str] = { 252: "2025R2", 251: "2025R1", 242: "2024R2", diff --git a/src/ansys/mapdl/core/cli/__init__.py b/src/ansys/mapdl/core/cli/__init__.py index a3cedac94a..623d78d662 100644 --- a/src/ansys/mapdl/core/cli/__init__.py +++ b/src/ansys/mapdl/core/cli/__init__.py @@ -20,22 +20,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -try: - import click - - _HAS_CLICK = True - -except ModuleNotFoundError: - _HAS_CLICK = False - +from ansys.mapdl.core import _HAS_CLICK if _HAS_CLICK: ################################### # PyMAPDL CLI + import click @click.group(invoke_without_command=True) @click.pass_context - def main(ctx): + def main(ctx: click.Context): pass from ansys.mapdl.core.cli.convert import convert diff --git a/src/ansys/mapdl/core/cli/convert.py b/src/ansys/mapdl/core/cli/convert.py index c618ee14ef..e8f8b76a6e 100644 --- a/src/ansys/mapdl/core/cli/convert.py +++ b/src/ansys/mapdl/core/cli/convert.py @@ -21,13 +21,14 @@ # SOFTWARE. import os +from typing import Any, List import click -_USING_PIPE = [False] +_USING_PIPE: List[bool] = [False] -def get_input_source(ctx, param, value): +def get_input_source(ctx: click.Context, param: Any, value: Any): if not value and not click.get_text_stream("stdin").isatty(): _USING_PIPE[0] = True return click.get_text_stream("stdin").read().strip() @@ -180,7 +181,7 @@ def convert( use_vtk: bool, clear_at_start: bool, check_parameter_names: bool, -): +) -> None: """Convert MAPDL code to PyMAPDL""" from ansys.mapdl.core.convert import convert_apdl_block, convert_script diff --git a/src/ansys/mapdl/core/cli/list_instances.py b/src/ansys/mapdl/core/cli/list_instances.py index 36d6d5b9df..49038dde8c 100644 --- a/src/ansys/mapdl/core/cli/list_instances.py +++ b/src/ansys/mapdl/core/cli/list_instances.py @@ -65,7 +65,7 @@ default=False, help="Print running location info.", ) -def list_instances(instances, long, cmd, location): +def list_instances(instances, long, cmd, location) -> None: import psutil from tabulate import tabulate diff --git a/src/ansys/mapdl/core/cli/start.py b/src/ansys/mapdl/core/cli/start.py index 7c8332a96c..3771b244d6 100644 --- a/src/ansys/mapdl/core/cli/start.py +++ b/src/ansys/mapdl/core/cli/start.py @@ -191,7 +191,7 @@ def start( add_env_vars: Dict[str, str], # ignored replace_env_vars: Dict[str, str], # ignored version: Union[int, str], -): +) -> None: from ansys.mapdl.core.launcher import launch_mapdl if mode: diff --git a/src/ansys/mapdl/core/cli/stop.py b/src/ansys/mapdl/core/cli/stop.py index 2e16a7404b..213c6e1041 100644 --- a/src/ansys/mapdl/core/cli/stop.py +++ b/src/ansys/mapdl/core/cli/stop.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from typing import Optional + import click @@ -49,7 +51,21 @@ default=False, help="Kill all MAPDL instances", ) -def stop(port, pid, all): +def stop(port: int, pid: Optional[int], all: bool) -> None: + """Stop MAPDL instances running on a given port or with a given process id (PID). + + This command stops MAPDL instances running on a given port or with a given process id (PID). + By default, it stops instances running on the port 50052. + + Parameters + ---------- + port : int + Port where the MAPDL instance is running. + pid : Optional[int] + PID of the MAPDL instance + all : bool + If :class:`True`, kill all the instances regardless their port or PID. + """ import psutil from ansys.mapdl.core.launcher import is_ansys_process diff --git a/src/ansys/mapdl/core/commands.py b/src/ansys/mapdl/core/commands.py index 25e88dd528..2f1e3a0eec 100644 --- a/src/ansys/mapdl/core/commands.py +++ b/src/ansys/mapdl/core/commands.py @@ -22,9 +22,15 @@ from functools import wraps import re +from typing import Any, Callable, Dict, List, Optional, Tuple import numpy as np +from ansys.mapdl.core import _HAS_PANDAS + +if _HAS_PANDAS: + import pandas + from ._commands import ( apdl, aux2_, @@ -48,35 +54,37 @@ ) # compiled regular expressions used for parsing tablular outputs -REG_LETTERS = re.compile(r"[a-df-zA-DF-Z]+") # all except E or e -REG_FLOAT_INT = re.compile( +REG_LETTERS: re.Pattern = re.compile(r"[a-df-zA-DF-Z]+") # all except E or e +REG_FLOAT_INT: re.Pattern = re.compile( r"[+-]?[0-9]*[.]?[0-9]*[Ee]?[+-]?[0-9]+|\s[0-9]+\s" ) # match number groups -BC_REGREP = re.compile(r"^\s*([0-9]+)\s*([A-Za-z]+)((?:\s+[0-9]*[.]?[0-9]+)+)$") +BC_REGREP: re.Pattern = re.compile( + r"^\s*([0-9]+)\s*([A-Za-z]+)((?:\s+[0-9]*[.]?[0-9]+)+)$" +) -MSG_NOT_PANDAS = """'Pandas' is not installed or could not be found. +MSG_NOT_PANDAS: str = """'Pandas' is not installed or could not be found. Hence this command is not applicable. You can install it using: pip install pandas """ -MSG_BCLISTINGOUTPUT_TO_ARRAY = """This command has strings values in some of its columns (such 'UX', 'FX', 'UY', 'TEMP', etc), +MSG_BCLISTINGOUTPUT_TO_ARRAY: str = """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"] +GROUP_DATA_START: List[str] = ["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 are not in the Mapdl class, # so they won't be wrapped. -CMD_RESULT_LISTING = [ +CMD_RESULT_LISTING: List[str] = [ "NLIN", # not documented "PRCI", "PRDI", # Not documented. @@ -104,7 +112,7 @@ "SWLI", ] -CMD_BC_LISTING = [ +CMD_BC_LISTING: List[str] = [ "DKLI", "DLLI", "DALI", @@ -120,7 +128,7 @@ "BFAL", ] -COLNAMES_BC_LISTING = { +COLNAMES_BC_LISTING: Dict[str, List[str]] = { "DKLI": ["KEYPOINT", "LABEL", "REAL", "IMAG", "EXP KEY"], "DLLI": ["LINE", "LABEL", "REAL", "IMAG", "NAREA"], "DALI": ["AREA", "LABEL", "REAL", "IMAG"], @@ -133,7 +141,7 @@ "BFAL": ["AREA", "LABEL", "VALUE"], } -CMD_ENTITY_LISTING = [ +CMD_ENTITY_LISTING: List[str] = [ "NLIS", # "ELIS", # To be implemented later # "KLIS", @@ -142,12 +150,12 @@ # "VLIS", ] -CMD_LISTING = [] +CMD_LISTING: List[str] = [] CMD_LISTING.extend(CMD_ENTITY_LISTING) CMD_LISTING.extend(CMD_RESULT_LISTING) # Adding empty lines to match current format. -CMD_DOCSTRING_INJECTION = r""" +CMD_DOCSTRING_INJECTION: str = r""" Returns ------- @@ -166,7 +174,7 @@ """ -XSEL_DOCSTRING_INJECTION = r""" +XSEL_DOCSTRING_INJECTION: str = r""" Returns ------- @@ -178,7 +186,7 @@ """ -CMD_XSEL = [ +CMD_XSEL: List[str] = [ "NSEL", "ESEL", "KSEL", @@ -188,13 +196,13 @@ ] -def get_indentation(indentation_regx, docstring): +def get_indentation(indentation_regx: str, docstring: str) -> List[Any]: return re.findall(indentation_regx, docstring, flags=re.DOTALL | re.IGNORECASE)[0][ 0 ] -def indent_text(indentation, docstring_injection): +def indent_text(indentation: str, docstring_injection: str) -> str: return "\n".join( [ indentation + each @@ -204,18 +212,18 @@ def indent_text(indentation, docstring_injection): ) -def get_docstring_indentation(docstring): +def get_docstring_indentation(docstring: str) -> List[Any]: indentation_regx = r"\n(\s*)\n" return get_indentation(indentation_regx, docstring) -def get_sections(docstring): +def get_sections(docstring: str) -> List[str]: return [ each.strip().lower() for each in re.findall(r"\n\s*(\S*)\n\s*-+\n", docstring) ] -def get_section_indentation(section_name, docstring): +def get_section_indentation(section_name: str, docstring: str) -> List[Any]: sections = get_sections(docstring) if section_name.lower().strip() not in sections: raise ValueError( @@ -227,7 +235,9 @@ def get_section_indentation(section_name, docstring): return get_indentation(indentation_regx, docstring) -def inject_before(section, indentation, indented_doc_inject, docstring): +def inject_before( + section: str, indentation: str, indented_doc_inject: str, docstring: str +) -> str: return re.sub( section + r"\n\s*-*", f"{indented_doc_inject.strip()}\n\n{indentation}" + r"\g<0>", @@ -236,7 +246,7 @@ def inject_before(section, indentation, indented_doc_inject, docstring): ) -def inject_after_return_section(indented_doc_inject, docstring): +def inject_after_return_section(indented_doc_inject: str, docstring: str) -> str: return re.sub( "Returns" + r"\n\s*-*", f"{indented_doc_inject.strip()}\n", @@ -245,7 +255,7 @@ def inject_after_return_section(indented_doc_inject, docstring): ) -def inject_docs(docstring, docstring_injection=None): +def inject_docs(docstring: str, docstring_injection: Optional[str] = None) -> str: """Inject a string in a docstring""" if not docstring_injection: docstring_injection = CMD_DOCSTRING_INJECTION @@ -295,7 +305,7 @@ def inject_docs(docstring, docstring_injection=None): return docstring + "\n" + indented_doc_inject -def check_valid_output(func): +def check_valid_output(func: Callable) -> Callable: """Wrapper that check if output can be wrapped by pandas, if not, it will raise an exception.""" @wraps(func) @@ -511,18 +521,6 @@ 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. @@ -541,7 +539,7 @@ class CommandOutput(str): # - https://docs.python.org/3/library/collections.html#userstring-objects # - Source code of UserString - def __new__(cls, content, cmd=None): + def __new__(cls, content: str, cmd=None): obj = super().__new__(cls, content) obj._cmd = cmd return obj @@ -552,7 +550,7 @@ def cmd(self): return self._cmd.split(",")[0] @cmd.setter - def cmd(self, cmd): + def cmd(self, cmd: str): """Not allowed to change the value of ``cmd``.""" raise AttributeError("The `cmd` attribute cannot be set") @@ -583,23 +581,26 @@ class CommandListingOutput(CommandOutput): """ - def __new__(cls, content, cmd=None, magicwords=None, columns_names=None): + def __new__( + cls, + content: str, + cmd: Optional[str] = None, + magicwords: Optional[str] = None, + columns_names: Optional[List[str]] = None, + ): obj = super().__new__(cls, content) obj._cmd = cmd obj._magicwords = magicwords obj._columns_names = columns_names return obj - def __init__(self, *args, **kwargs): + def __init__(self, *args: Tuple[Any], **kwargs: Dict[Any, Any]) -> None: self._cache = None - def _is_data_start(self, line, magicwords=None): + def _is_data_start(self, line: str, magicwords: List[str] = None) -> bool: """Check if line is the start of a data group.""" if not magicwords: - if self._magicwords: - magicwords = self._magicwords - else: - magicwords = GROUP_DATA_START + magicwords = self._magicwords or GROUP_DATA_START # Checking if we are supplying a custom start function. if self.custom_data_start(line) is not None: @@ -610,7 +611,7 @@ def _is_data_start(self, line, magicwords=None): return True return False - def _is_data_end(self, line): + def _is_data_end(self, line: str) -> bool: """Check if line is the end of a data group.""" # Checking if we are supplying a custom start function. @@ -619,7 +620,7 @@ def _is_data_end(self, line): else: return self._is_empty(line) - def custom_data_start(self, line): + def custom_data_start(self, line: str) -> None: """Custom data start line check function. This function is left empty so it can be overwritten by the user. @@ -629,7 +630,7 @@ def custom_data_start(self, line): """ return None - def custom_data_end(self, line): + def custom_data_end(self, line: str) -> None: """Custom data end line check function. This function is left empty so it can be overwritten by the user. @@ -640,14 +641,14 @@ def custom_data_end(self, line): return None @staticmethod - def _is_empty_line(line): + def _is_empty_line(line: str) -> bool: return bool(line.split()) - def _format(self): + def _format(self) -> str: """Perform some formatting (replacing mainly) in the raw text.""" return re.sub(r"[^E](-)", " -", self.__str__()) - def _get_body(self, trail_header=None): + def _get_body(self, trail_header: List[str] = None) -> str: """Get command body text. It removes the maximum absolute values tail part and makes sure there is @@ -672,7 +673,9 @@ def _get_body(self, trail_header=None): body = body[:i] return body - def _get_data_group_indexes(self, body, magicwords=None): + def _get_data_group_indexes( + self, body: str, magicwords: Optional[List[str]] = None + ) -> List[Tuple[int, int]]: """Return the indexes of the start and end of the data groups.""" if "*****ANSYS VERIFICATION RUN ONLY*****" in str(self[:1000]): shift = 2 @@ -697,7 +700,7 @@ def _get_data_group_indexes(self, body, magicwords=None): return zip(start_idxs, ends) - def get_columns(self): + def get_columns(self) -> Optional[List[str]]: """Get the column names for the dataframe. Returns @@ -715,7 +718,7 @@ def get_columns(self): except: return None - def _parse_table(self): + def _parse_table(self) -> np.ndarray: """Parse tabular command output. Returns @@ -734,14 +737,14 @@ def _parse_table(self): return np.array(parsed_lines, dtype=np.float64) @property - def _parsed(self): + def _parsed(self) -> str: """Return parsed output.""" if self._cache is None: self._cache = self._parse_table() return self._cache @check_valid_output - def to_list(self): + def to_list(self) -> List[str]: """Export the command output a list or list of lists. Returns @@ -750,7 +753,7 @@ def to_list(self): """ return self._parsed.tolist() - def to_array(self): + def to_array(self) -> np.ndarray: """Export the command output as a numpy array. Returns @@ -760,7 +763,9 @@ def to_array(self): """ return self._parsed - def to_dataframe(self, data=None, columns=None): + def to_dataframe( + self, data: Optional[np.ndarray] = None, columns: Optional[List[str]] = None + ) -> "pandas.DataFrame": """Export the command output as a Pandas DataFrame. Parameters @@ -789,9 +794,9 @@ def to_dataframe(self, data=None, columns=None): (inheritate from :func:`to_array() ` method). """ - try: - import pandas as pd - except ModuleNotFoundError: + if _HAS_PANDAS: + import pandas + else: raise ModuleNotFoundError(MSG_NOT_PANDAS) if data is None: @@ -799,7 +804,7 @@ def to_dataframe(self, data=None, columns=None): if not columns: columns = self.get_columns() - return pd.DataFrame(data=data, columns=columns) + return pandas.DataFrame(data=data, columns=columns) class BoundaryConditionsListingOutput(CommandListingOutput): @@ -819,10 +824,10 @@ class BoundaryConditionsListingOutput(CommandListingOutput): """ - def bc_colnames(self): + def bc_colnames(self) -> Optional[List[str]]: """Get the column names based on bc list command""" - bc_type = { + bc_type: Dict[str, str] = { "BODY FORCES": "BF", "SURFACE LOAD": "SF", "POINT LOAD": "F", @@ -871,7 +876,7 @@ def bc_colnames(self): return None - def get_columns(self): + def get_columns(self) -> List[str]: """Get the column names for the dataframe. Returns @@ -895,7 +900,7 @@ def get_columns(self): except: return None - def _parse_table(self): + def _parse_table(self) -> List[str]: """Parse tabular command output.""" parsed_lines = [] for line in self.splitlines(): @@ -909,7 +914,7 @@ def _parse_table(self): return parsed_lines @check_valid_output - def to_list(self): + def to_list(self) -> List[str]: """Export the command output a list or list of lists. Returns @@ -921,7 +926,7 @@ def to_list(self): def to_array(self): raise ValueError(MSG_BCLISTINGOUTPUT_TO_ARRAY) - def to_dataframe(self): + def to_dataframe(self) -> "pandas.DataFrame": """Convert the command output to a Pandas Dataframe. Returns @@ -960,7 +965,7 @@ def to_dataframe(self): class ComponentListing(CommandListingOutput): @property - def _parsed(self): + def _parsed(self) -> np.ndarray: from ansys.mapdl.core.component import _parse_cmlist # To keep same API as commands @@ -968,5 +973,5 @@ def _parsed(self): class StringWithLiteralRepr(str): - def __repr__(self): + def __repr__(self) -> str: return self.__str__() diff --git a/src/ansys/mapdl/core/common_grpc.py b/src/ansys/mapdl/core/common_grpc.py index ab5be839ce..65ac3fc5f7 100644 --- a/src/ansys/mapdl/core/common_grpc.py +++ b/src/ansys/mapdl/core/common_grpc.py @@ -22,17 +22,18 @@ """Common gRPC functions""" from time import sleep -from typing import List, Literal, get_args +from typing import Dict, Iterable, List, Literal, Optional, get_args +from ansys.api.mapdl.v0 import ansys_kernel_pb2 as anskernel import numpy as np from ansys.mapdl.core.errors import MapdlConnectionError, MapdlRuntimeError # chunk sizes for streaming and file streaming -DEFAULT_CHUNKSIZE = 256 * 1024 # 256 kB -DEFAULT_FILE_CHUNK_SIZE = 1024 * 1024 # 1MB +DEFAULT_CHUNKSIZE: int = 256 * 1024 # 256 kB +DEFAULT_FILE_CHUNK_SIZE: int = 1024 * 1024 # 1MB -ANSYS_VALUE_TYPE = { +ANSYS_VALUE_TYPE: Dict[int, Optional[np.typing.DTypeLike]] = { 0: None, # UNKNOWN 1: np.int32, # INTEGER 2: np.int64, # HYPER @@ -45,7 +46,7 @@ } -VGET_ENTITY_TYPES_TYPING = Literal[ +VGET_ENTITY_TYPES_TYPING: List[str] = Literal[ "NODE", "ELEM", "KP", @@ -59,9 +60,9 @@ VGET_ENTITY_TYPES: List[str] = list(get_args(VGET_ENTITY_TYPES_TYPING)) -STRESS_TYPES = ["X", "Y", "Z", "XY", "YZ", "XZ", "1", "2", "3", "INT", "EQV"] -COMP_TYPE = ["X", "Y", "Z", "SUM"] -VGET_NODE_ENTITY_TYPES = { +STRESS_TYPES: List[str] = ["X", "Y", "Z", "XY", "YZ", "XZ", "1", "2", "3", "INT", "EQV"] +COMP_TYPE: List[str] = ["X", "Y", "Z", "SUM"] +VGET_NODE_ENTITY_TYPES: Dict[str, List[str]] = { "U": ["X", "Y", "Z"], "S": STRESS_TYPES, "EPTO": STRESS_TYPES, @@ -89,7 +90,7 @@ class GrpcError(MapdlRuntimeError): """Raised when gRPC fails""" - def __init__(self, msg=""): + def __init__(self, msg: str = "") -> None: super().__init__(self, msg) @@ -168,7 +169,9 @@ def check_vget_input(entity: str, item: str, itnum: str) -> str: return "%s, , %s, %s" % (entity, item, itnum) -def parse_chunks(chunks, dtype=None): +def parse_chunks( + chunks: Iterable[anskernel.Chunk], dtype: Optional[np.typing.DTypeLike] = None +) -> np.ndarray: """Deserialize gRPC chunks into a numpy array Parameters diff --git a/src/ansys/mapdl/core/component.py b/src/ansys/mapdl/core/component.py index 21bc4765c0..2f1b989dd2 100644 --- a/src/ansys/mapdl/core/component.py +++ b/src/ansys/mapdl/core/component.py @@ -25,7 +25,9 @@ from typing import ( TYPE_CHECKING, Any, + Callable, Dict, + Iterator, List, Literal, Optional, @@ -45,13 +47,13 @@ if TYPE_CHECKING: # pragma: no cover import logging -ENTITIES_TYP = Literal[ +ENTITIES_TYP: List[str] = Literal[ "NODE", "NODES", "ELEM", "ELEMS", "ELEMENTS", "VOLU", "AREA", "LINE", "KP" ] -VALID_ENTITIES = list(get_args(ENTITIES_TYP)) +VALID_ENTITIES: List[str] = list(get_args(ENTITIES_TYP)) -SELECTOR_FUNCTION = [ +SELECTOR_FUNCTION: List[str] = [ "NSEL", "NSEL", "ESEL", @@ -63,7 +65,7 @@ "KSEL", ] -ENTITIES_MAPPING = { +ENTITIES_MAPPING: Dict[str, Callable] = { entity.upper(): func for entity, func in zip(VALID_ENTITIES, SELECTOR_FUNCTION) } @@ -71,7 +73,7 @@ ITEMS_VALUES = Optional[Union[str, int, List[int], NDArray[Any]]] UNDERLYING_DICT = Dict[str, ITEMS_VALUES] -warning_entity = ( +WARNING_ENTITY: str = ( "Assuming a {default_entity} selection.\n" "It is recommended you use the following notation to avoid this warning:\n" ">>> mapdl.components['{key}'] = '{default_entity}', {value}\n" @@ -82,7 +84,7 @@ def _check_valid_pyobj_to_entities( items: Union[Tuple[int, ...], List[int], NDArray[Any]] -): +) -> None: """Check whether the python objects can be converted to entities. At the moment, only list and numpy arrays of ints are allowed. """ @@ -130,7 +132,7 @@ class Component(tuple): [1, 2, 3] """ - def __init__(self, *args, **kwargs): + def __init__(self, *args: Tuple[Any], **kwargs: Dict[Any, Any]) -> None: """Component object""" # Using tuple init because using object.__init__ # For more information visit: @@ -151,7 +153,7 @@ def __new__( return obj - def __str__(self): + def __str__(self) -> str: tup_str = super().__str__() return f"Component(type='{self._type}', items={tup_str})" @@ -165,7 +167,7 @@ def type(self) -> ENTITIES_TYP: return self._type @property - def items(self) -> tuple: + def items(self) -> Tuple[int]: """Return the ids of the entities in the component.""" return tuple(self) @@ -258,7 +260,7 @@ def __init__(self, mapdl: Mapdl) -> None: self._default_entity_warning: bool = True @property - def default_entity(self): + def default_entity(self) -> ENTITIES_TYP: """Default entity for component creation.""" return self._default_entity @@ -271,12 +273,12 @@ def default_entity(self, value: ENTITIES_TYP): self._default_entity = value.upper() @property - def default_entity_warning(self): + def default_entity_warning(self) -> bool: """Enables the warning when creating components other than node components without specifying its type.""" return self._default_entity_warning @default_entity_warning.setter - def default_entity_warning(self, value: bool): + def default_entity_warning(self, value: bool) -> None: self._default_entity_warning = value @property @@ -301,7 +303,7 @@ def _comp(self) -> UNDERLYING_DICT: return self.__comp @_comp.setter - def _comp(self, value): + def _comp(self, value) -> None: self.__comp = value def __getitem__(self, key: str) -> ITEMS_VALUES: @@ -371,7 +373,7 @@ def __setitem__(self, key: str, value: ITEMS_VALUES) -> None: # Asumng default entity if self.default_entity_warning: warnings.warn( - warning_entity.format( + WARNING_ENTITY.format( default_entity=self.default_entity, key=key, value=value, @@ -401,7 +403,7 @@ def __setitem__(self, key: str, value: ITEMS_VALUES) -> None: # Assuming we are defining a CM with nodes if self.default_entity_warning: warnings.warn( - warning_entity.format( + WARNING_ENTITY.format( default_entity=self.default_entity, key=key, value=value, @@ -457,7 +459,7 @@ def __contains__(self, key: str) -> bool: """ return key.upper() in self._comp.keys() - def __iter__(self): + def __iter__(self) -> Iterator: """ Return an iterator over the component names. @@ -470,7 +472,7 @@ def __iter__(self): yield from self._comp.keys() @property - def names(self): + def names(self) -> Tuple[str]: """ Return a tuple that contains the components. @@ -483,7 +485,7 @@ def names(self): return tuple(self._comp.keys()) @property - def types(self): + def types(self) -> Tuple[str]: """ Return the types of the components. @@ -495,7 +497,7 @@ def types(self): """ return tuple(self._comp.values()) - def items(self): + def items(self) -> UNDERLYING_DICT: """ Return a view object that contains the name-type pairs for each component. diff --git a/src/ansys/mapdl/core/convert.py b/src/ansys/mapdl/core/convert.py index cbce45f5d4..36426971a0 100644 --- a/src/ansys/mapdl/core/convert.py +++ b/src/ansys/mapdl/core/convert.py @@ -23,6 +23,7 @@ from logging import Logger, StreamHandler import os import re +from typing import Any, Callable, Dict, List, Optional, Tuple from warnings import warn from ansys.mapdl.core import __version__ @@ -32,31 +33,31 @@ # Because the APDL version has empty arguments, whereas the PyMAPDL # doesn't have them. Hence the order of arguments is messed up. -FORMAT_OPTIONS = { +FORMAT_OPTIONS: Dict[str, Any] = { "select": "W191,W291,W293,W391,E115,E117,E122,E124,E125,E225,E231,E301,E303,F401,F403", "max-line-length": 100, } -LOGLEVEL_DEFAULT = "WARNING" -AUTO_EXIT_DEFAULT = True -LINE_ENDING_DEFAULT = None -EXEC_FILE_DEFAULT = None -MACROS_AS_FUNCTIONS_DEFAULT = True -USE_FUNCTION_NAMES_DEFAULT = True -SHOW_LOG_DEFAULT = False -ADD_IMPORTS_DEFAULT = True -COMMENT_SOLVE_DEFAULT = False -CLEANUP_OUTPUT_DEFAULT = True -HEADER_DEFAULT = True -PRINT_COM_DEFAULT = True -ONLY_COMMANDS_DEFAULT = False -USE_VTK_DEFAULT = None -CLEAR_AT_START_DEFAULT = False -CHECK_PARAMETER_NAMES_DEFAULT = True +LOGLEVEL_DEFAULT: StreamHandler = "WARNING" +AUTO_EXIT_DEFAULT: bool = True +LINE_ENDING_DEFAULT: Optional[str] = None +EXEC_FILE_DEFAULT: Optional[str] = None +MACROS_AS_FUNCTIONS_DEFAULT: bool = True +USE_FUNCTION_NAMES_DEFAULT: bool = True +SHOW_LOG_DEFAULT: bool = False +ADD_IMPORTS_DEFAULT: bool = True +COMMENT_SOLVE_DEFAULT: bool = False +CLEANUP_OUTPUT_DEFAULT: bool = True +HEADER_DEFAULT: bool = True +PRINT_COM_DEFAULT: bool = True +ONLY_COMMANDS_DEFAULT: bool = False +USE_VTK_DEFAULT: Optional[bool] = None +CLEAR_AT_START_DEFAULT: bool = False +CHECK_PARAMETER_NAMES_DEFAULT: bool = True # This commands have "--" as one or some arguments -COMMANDS_WITH_EMPTY_ARGS = { +COMMANDS_WITH_EMPTY_ARGS: Dict[str, Tuple[Any]] = { "/CMA": (), # "/CMAP, "/NER": (), # "/NERR, "/PBF": (), # "/PBF, @@ -114,37 +115,37 @@ } -COMMANDS_TO_NOT_BE_CONVERTED = [ +COMMANDS_TO_NOT_BE_CONVERTED: List[str] = [ "CMPL", # CMPLOT default behaviour does not match the `mapdl.cmplot`'s at the moemnt # CDREAD # commented above ] -FORCED_MAPPING = { +FORCED_MAPPING: Dict[str, str] = { # Forced mapping between MAPDL and PyMAPDL "SECT": "sectype", # Because it is shadowed by `sectinqr` } def convert_script( - filename_in, - filename_out=None, - loglevel="WARNING", - auto_exit=True, - line_ending=None, - exec_file=None, - macros_as_functions=True, - use_function_names=True, - show_log=False, - add_imports=True, - comment_solve=False, - cleanup_output=True, - header=True, - print_com=True, - only_commands=False, - use_vtk=None, - clear_at_start=False, - check_parameter_names=True, -): + filename_in: str, + filename_out: Optional[str] = None, + loglevel: str = "WARNING", + auto_exit: bool = True, + line_ending: Optional[str] = None, + exec_file: Optional[str] = None, + macros_as_functions: bool = True, + use_function_names: bool = True, + show_log: bool = False, + add_imports: bool = True, + comment_solve: bool = False, + cleanup_output: bool = True, + header: bool = True, + print_com: bool = True, + only_commands: bool = False, + use_vtk: Optional[bool] = None, + clear_at_start: bool = False, + check_parameter_names: bool = True, +) -> List[str]: """Converts an ANSYS input file to a python PyMAPDL script. Parameters @@ -287,24 +288,24 @@ def convert_script( def convert_apdl_block( - apdl_strings, - loglevel="WARNING", - auto_exit=True, - line_ending=None, - exec_file=None, - macros_as_functions=True, - use_function_names=True, - show_log=False, - add_imports=True, - comment_solve=False, - cleanup_output=True, - header=True, - print_com=True, - only_commands=False, - use_vtk=None, - clear_at_start=False, - check_parameter_names=False, -): + apdl_strings: str, + loglevel: str = "WARNING", + auto_exit: bool = True, + line_ending: Optional[str] = None, + exec_file: Optional[str] = None, + macros_as_functions: bool = True, + use_function_names: bool = True, + show_log: bool = False, + add_imports: bool = True, + comment_solve: bool = False, + cleanup_output: bool = True, + header: bool = True, + print_com: bool = True, + only_commands: bool = False, + use_vtk: Optional[bool] = None, + clear_at_start: bool = False, + check_parameter_names: bool = False, +) -> List[str]: """Converts an ANSYS input string to a python PyMAPDL string. Parameters @@ -428,67 +429,6 @@ def convert_apdl_block( return translator.lines -def _convert( - apdl_strings, - loglevel="WARNING", - auto_exit=True, - line_ending=None, - exec_file=None, - macros_as_functions=True, - use_function_names=True, - show_log=False, - add_imports=True, - comment_solve=False, - cleanup_output=True, - header=True, - print_com=True, - only_commands=False, - use_vtk=None, - clear_at_start=False, - check_parameter_names=True, -): - if only_commands: - auto_exit = False - add_imports = False - header = False - - translator = FileTranslator( - loglevel, - line_ending, - exec_file=exec_file, - macros_as_functions=macros_as_functions, - use_function_names=use_function_names, - show_log=show_log, - add_imports=add_imports, - comment_solve=comment_solve, - cleanup_output=cleanup_output, - header=header, - print_com=print_com, - use_vtk=use_vtk, - clear_at_start=clear_at_start, - check_parameter_names=check_parameter_names, - ) - - if isinstance(apdl_strings, str): - # os.linesep does not work well, so we are making sure - # the line separation is appropriate. - regx = f"[^\\r]({translator.line_ending})" - if not re.search(regx, apdl_strings): - if "\r\n" in apdl_strings: - translator.line_ending = "\r\n" - elif "\n" in apdl_strings: - translator.line_ending = "\n" - - apdl_strings = apdl_strings.split(translator.line_ending) - - for line in apdl_strings: - translator.translate_line(line) - - if auto_exit and add_imports: - translator.write_exit() - return translator - - class Lines(list): def __init__(self, mute): self._log = Logger("convert_logger") @@ -496,14 +436,14 @@ def __init__(self, mute): self._mute = mute super().__init__() - def append(self, item, mute=True): + def append(self, item: Any, mute: bool = True) -> None: # append the item to itself (the list) if not self._mute and item: stripped_msg = item.replace("\n", "\\n") self._log.info(msg=f"Converted: '{stripped_msg}'") super(Lines, self).append(item) - def _setup_logger(self): + def _setup_logger(self) -> None: stdhdl = StreamHandler() stdhdl.setLevel(10) stdhdl.set_name("stdout") @@ -518,21 +458,21 @@ class FileTranslator: def __init__( self, - loglevel="INFO", - line_ending=None, - exec_file=None, - macros_as_functions=True, - use_function_names=True, - show_log=False, - add_imports=True, - comment_solve=False, - cleanup_output=True, - header=True, - print_com=True, - use_vtk=None, - clear_at_start=False, - check_parameter_names=False, - ): + loglevel: str = "INFO", + line_ending: Optional[str] = None, + exec_file: Optional[str] = None, + macros_as_functions: bool = True, + use_function_names: bool = True, + show_log: bool = False, + add_imports: bool = True, + comment_solve: bool = False, + cleanup_output: bool = True, + header: bool = True, + print_com: bool = True, + use_vtk: Optional[bool] = None, + clear_at_start: bool = False, + check_parameter_names: bool = False, + ) -> None: self._non_interactive_level = 0 self.lines = Lines(mute=not show_log) self._functions = [] @@ -591,7 +531,7 @@ def __init__( self._in_block = False self._block_current_cmd = None - def write_header(self): + def write_header(self) -> None: if isinstance(self._header, bool): if self._header: header = f'"""Script generated by ansys-mapdl-core version {__version__}"""\n' @@ -604,10 +544,10 @@ def write_header(self): "The keyword argument 'header' should be a string or a boolean." ) - def write_exit(self): + def write_exit(self) -> None: self.lines.append(f"\n{self.obj_name}.exit()") - def format_using_autopep8(self, text=None): + def format_using_autopep8(self, text: str = None) -> str: """Format internal `self.lines` with autopep8. Parameters @@ -634,7 +574,7 @@ def format_using_autopep8(self, text=None): # for development purposes return autopep8.fix_code(text) - def save(self, filename, format_autopep8=True): + def save(self, filename, format_autopep8: bool = True) -> None: """Saves lines to file""" if os.path.isfile(filename): os.remove(filename) @@ -649,7 +589,7 @@ def save(self, filename, format_autopep8=True): with open(filename, "w") as f: f.write(self.line_ending.join(self.lines)) - def initialize_mapdl_object(self, loglevel, exec_file): + def initialize_mapdl_object(self, loglevel: str, exec_file: str) -> None: """Initializes ansys object as lines""" core_module = "ansys.mapdl.core" # shouldn't change self.lines.append(f"from {core_module} import launch_mapdl") @@ -675,16 +615,16 @@ def initialize_mapdl_object(self, loglevel, exec_file): self.lines.append(f"{self.obj_name}.clear() # Clearing session") @property - def line_ending(self): + def line_ending(self) -> str: return self._line_ending @line_ending.setter - def line_ending(self, line_ending): + def line_ending(self, line_ending: str) -> None: if line_ending not in ["\n", "\r\n"]: raise ValueError('Line ending must be either "\\n", "\\r\\n"') self._line_ending = line_ending - def translate_line(self, line): + def translate_line(self, line: str) -> Optional[str]: """Converts a single line from an ANSYS APDL script""" if "$" in line: @@ -1001,12 +941,12 @@ def translate_line(self, line): else: self.store_run_command(line.strip()) - def _pymapdl_command(self, command): + def _pymapdl_command(self, command: str) -> str: if command[0] in ["/", "*"]: command = command[1:] return command - def start_function(self, func_name): + def start_function(self, func_name: str) -> None: self._functions.append(func_name) self.store_empty_line() self.store_empty_line() @@ -1029,10 +969,10 @@ def start_function(self, func_name): self.lines.append(line) self.indent = self.indent + " " - def store_under_scored_run_command(self, command): + def store_under_scored_run_command(self, command: str) -> None: self.store_run_command(command, run_underscored=True) - def store_run_command(self, command, run_underscored=False): + def store_run_command(self, command: str, run_underscored: bool = False) -> None: """Stores pyansys.ANSYS command that cannot be broken down into a function and parameters. """ @@ -1078,20 +1018,20 @@ def store_run_command(self, command, run_underscored=False): ) self.lines.append(line) - def store_comment(self): + def store_comment(self) -> None: """Stores a line containing only a comment""" line = f"{self.indent}# {self.comment}" self.lines.append(line) - def store_empty_line(self): + def store_empty_line(self) -> None: """Stores an empty line""" self.lines.append("") - def store_python_command(self, command): + def store_python_command(self, command: str) -> None: line = f"{self.indent}{command}" self.lines.append(line) - def _parse_arguments(self, parameters): + def _parse_arguments(self, parameters: List[str]) -> str: parsed_parameters = [] for parameter in parameters: parameter = parameter.strip() @@ -1109,7 +1049,7 @@ def _parse_arguments(self, parameters): return ", ".join(parsed_parameters) - def store_command(self, function, parameters): + def store_command(self, function: Callable, parameters: List[str]) -> None: """Stores a valid pyansys function with parameters""" parameter_str = self._parse_arguments(parameters) @@ -1131,7 +1071,7 @@ def store_command(self, function, parameters): self.lines.append(line) - def start_non_interactive(self): + def start_non_interactive(self) -> None: self._non_interactive_level += 1 if self.non_interactive: return @@ -1140,13 +1080,13 @@ def start_non_interactive(self): self.non_interactive = True self.indent = self.indent + " " - def end_non_interactive(self): + def end_non_interactive(self) -> None: self._non_interactive_level -= 1 if self._non_interactive_level <= 0: self.indent = self.indent[4:] self.non_interactive = False - def start_chained_commands(self): + def start_chained_commands(self) -> None: self._chained_commands += 1 if self.chained_commands: return @@ -1155,13 +1095,13 @@ def start_chained_commands(self): self.chained_commands = True self.indent = self.indent + " " - def end_chained_commands(self): + def end_chained_commands(self) -> None: self._chained_commands -= 1 if self._chained_commands <= 0: self.indent = self.indent[4:] self.chained_commands = False - def output_to_file(self, line): + def output_to_file(self, line: str) -> bool: """Return if an APDL line is redirecting to a file.""" if line[:4].upper() == "/OUT": # We are redirecting the output to somewhere, probably a file. @@ -1182,7 +1122,7 @@ def output_to_file(self, line): return False - def output_to_default(self, line): + def output_to_default(self, line: str) -> bool: if line[:4].upper() == "/OUT": # We are redirecting the output to somewhere, probably a file. # Because of the problem with the ansys output, we need to execute @@ -1201,7 +1141,7 @@ def output_to_default(self, line): return False - def _get_items(self, line_): + def _get_items(self, line_: str) -> List[str]: """Parse the line items (comma separated elements) but ignoring the ones inside parenthesis, or brackets""" parenthesis_count = 0 @@ -1225,7 +1165,7 @@ def _get_items(self, line_): return items - def _get_valid_pymapdl_methods_short(self): + def _get_valid_pymapdl_methods_short(self) -> List[str]: pymethods = dir(Commands) reduced_list = [] @@ -1240,9 +1180,70 @@ def _get_valid_pymapdl_methods_short(self): reduced_list.append(each_method[:4]) return reduced_list - def find_match(self, cmd): + def find_match(self, cmd: str) -> str: pymethods = sorted(dir(Commands)) for each in pymethods: if each.startswith(cmd): return each + + +def _convert( + apdl_strings: str, + loglevel: str = "WARNING", + auto_exit: bool = True, + line_ending: Optional[str] = None, + exec_file: Optional[str] = None, + macros_as_functions: bool = True, + use_function_names: bool = True, + show_log: bool = False, + add_imports: bool = True, + comment_solve: bool = False, + cleanup_output: bool = True, + header: bool = True, + print_com: bool = True, + only_commands: bool = False, + use_vtk: Optional[bool] = None, + clear_at_start: bool = False, + check_parameter_names: bool = False, +) -> FileTranslator: + if only_commands: + auto_exit = False + add_imports = False + header = False + + translator = FileTranslator( + loglevel, + line_ending, + exec_file=exec_file, + macros_as_functions=macros_as_functions, + use_function_names=use_function_names, + show_log=show_log, + add_imports=add_imports, + comment_solve=comment_solve, + cleanup_output=cleanup_output, + header=header, + print_com=print_com, + use_vtk=use_vtk, + clear_at_start=clear_at_start, + check_parameter_names=check_parameter_names, + ) + + if isinstance(apdl_strings, str): + # os.linesep does not work well, so we are making sure + # the line separation is appropriate. + regx = f"[^\\r]({translator.line_ending})" + if not re.search(regx, apdl_strings): + if "\r\n" in apdl_strings: + translator.line_ending = "\r\n" + elif "\n" in apdl_strings: + translator.line_ending = "\n" + + apdl_strings = apdl_strings.split(translator.line_ending) + + for line in apdl_strings: + translator.translate_line(line) + + if auto_exit and add_imports: + translator.write_exit() + return translator diff --git a/src/ansys/mapdl/core/errors.py b/src/ansys/mapdl/core/errors.py index ebbffcfa53..65c3172a65 100644 --- a/src/ansys/mapdl/core/errors.py +++ b/src/ansys/mapdl/core/errors.py @@ -25,16 +25,16 @@ import signal import threading from time import sleep -from typing import Callable, Optional +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple import grpc from ansys.mapdl.core import LOG as logger -SIGINT_TRACKER = [] +SIGINT_TRACKER: List = [] -LOCKFILE_MSG = """ +LOCKFILE_MSG: str = """ Another ANSYS job with the same job name is already running in this directory, or the lock file has not been deleted from an abnormally terminated ANSYS run. @@ -44,7 +44,7 @@ """ -TYPE_MSG = ( +TYPE_MSG: str = ( "Invalid datatype. Must be one of the following:\n" + "np.int32, np.int64, or np.double" ) @@ -286,7 +286,7 @@ def handler(sig, frame): # pragma: no cover SIGINT_TRACKER.append(True) -def protect_grpc(func): +def protect_grpc(func: Callable) -> Callable: """Capture gRPC exceptions and return a more succinct error message Capture KeyboardInterrupt to avoid segfaulting MAPDL. @@ -390,7 +390,7 @@ def wrapper(*args, **kwargs): return wrapper -def retrieve_mapdl_from_args(args): +def retrieve_mapdl_from_args(args: Iterable[Any]) -> "Mapdl": # can't use isinstance here due to circular imports try: class_name = args[0].__class__.__name__ @@ -405,7 +405,14 @@ def retrieve_mapdl_from_args(args): return mapdl -def handle_generic_grpc_error(error, func, args, kwargs, reason="", suggestion=""): +def handle_generic_grpc_error( + error: Exception, + func: Callable, + args: Tuple[Any], + kwargs: Dict[Any, Any], + reason: str = "", + suggestion: str = "", +): """Handle non-custom gRPC errors""" mapdl = retrieve_mapdl_from_args(args) diff --git a/src/ansys/mapdl/core/examples/downloads.py b/src/ansys/mapdl/core/examples/downloads.py index 67c793e091..e8acc07aee 100644 --- a/src/ansys/mapdl/core/examples/downloads.py +++ b/src/ansys/mapdl/core/examples/downloads.py @@ -25,20 +25,17 @@ from functools import wraps import os import shutil +from typing import Callable, Dict, Optional import zipfile -try: - import requests - - _HAS_REQUESTS = True - -except ModuleNotFoundError: - _HAS_REQUESTS = False - from ansys.mapdl import core as pymapdl +from ansys.mapdl.core import _HAS_REQUESTS + +if _HAS_REQUESTS: + import requests -def check_directory_exist(directory): +def check_directory_exist(directory: str) -> Callable: # Wrapping LISTING FUNCTIONS. def wrap_function(func): @wraps(func) @@ -54,13 +51,13 @@ def inner_wrapper(*args, **kwargs): return wrap_function -def get_ext(filename): +def get_ext(filename: str) -> str: """Extract the extension of the filename""" ext = os.path.splitext(filename)[1].lower() return ext -def delete_downloads(): +def delete_downloads() -> bool: """Delete all downloaded examples to free space or update the files""" if os.path.exists(pymapdl.EXAMPLES_PATH): shutil.rmtree(pymapdl.EXAMPLES_PATH) @@ -68,13 +65,13 @@ def delete_downloads(): @check_directory_exist(pymapdl.EXAMPLES_PATH) -def _decompress(filename): +def _decompress(filename: str) -> None: zip_ref = zipfile.ZipFile(filename, "r") zip_ref.extractall(pymapdl.EXAMPLES_PATH) return zip_ref.close() -def _get_file_url(filename, directory=None): +def _get_file_url(filename: str, directory: Optional[str] = None) -> str: if directory: return ( f"https://github.com/ansys/example-data/raw/master/{directory}/{filename}" @@ -82,14 +79,14 @@ def _get_file_url(filename, directory=None): return f"https://github.com/ansys/example-data/raw/master/{filename}" -def _check_url_exist(url): +def _check_url_exist(url: str) -> bool: response = requests.get(url, timeout=10) # 10 seconds timeout return response.status_code == 200 @check_directory_exist(pymapdl.EXAMPLES_PATH) -def _retrieve_file(url, filename, _test=False): - # scape test +def _retrieve_file(url: str, filename: str, _test: bool = False) -> str: + # escape test if pymapdl.RUNNING_TESTS: return _check_url_exist(url) @@ -112,7 +109,9 @@ def _retrieve_file(url, filename, _test=False): return local_path -def _download_file(filename, directory=None, _test=False): +def _download_file( + filename: str, directory: Optional[str] = None, _test: Optional[bool] = False +) -> str: url = _get_file_url(filename, directory) try: return _retrieve_file(url, filename, _test) @@ -127,7 +126,7 @@ def _download_file(filename, directory=None, _test=False): ) -def download_bracket(): +def download_bracket() -> str: """Download an IGS bracket. Examples @@ -141,27 +140,27 @@ def download_bracket(): return _download_file("bracket.iges", "geometry") -def download_tech_demo_data(example, filename): +def download_tech_demo_data(example: str, filename: str) -> str: """Download Tech Demos external data.""" example = "tech_demos/" + example return _download_file(filename=filename, directory=example) -def download_vtk_rotor(): +def download_vtk_rotor() -> str: """Download rotor vtk file.""" return _download_file("rotor.vtk", "geometry") -def _download_rotor_tech_demo_vtk(): +def _download_rotor_tech_demo_vtk() -> str: """Download the rotor surface VTK file.""" return _download_file("rotor2.vtk", "geometry") -def download_example_data(filename, directory=None): +def download_example_data(filename: str, directory: Optional[str] = None) -> str: return _download_file(filename, directory=directory) -def download_manifold_example_data() -> dict: +def download_manifold_example_data() -> Dict[str, str]: """Download the manifold example data and return the download paths into a dictionary domain id->path. Examples files are downloaded to a persistent cache to avoid @@ -193,7 +192,7 @@ def download_manifold_example_data() -> dict: } -def download_cfx_mapping_example_data() -> dict: +def download_cfx_mapping_example_data() -> Dict[str, str]: """Download the CFX mapping data and return the download paths into a dictionary domain id->path. Examples files are downloaded to a persistent cache to avoid diff --git a/src/ansys/mapdl/core/examples/examples.py b/src/ansys/mapdl/core/examples/examples.py index bbb8a2715e..d8d60e7003 100644 --- a/src/ansys/mapdl/core/examples/examples.py +++ b/src/ansys/mapdl/core/examples/examples.py @@ -23,20 +23,13 @@ """pymapdl examples""" import os -from ansys.mapdl.core.plotting.theme import PyMAPDL_cmap - # get location of this folder and the example files -dir_path = os.path.dirname(os.path.realpath(__file__)) +dir_path: str = os.path.dirname(os.path.realpath(__file__)) # add any files you'd like to import here. For example: -wing_model = os.path.join(dir_path, "wing.dat") +wing_model: str = os.path.join(dir_path, "wing.dat") # be sure to add the input file directly in this directory # This way, files can be loaded with: # from ansys.mapdl.core import examples # examples.wing_model - - -def ansys_colormap(): - """Return the default ansys color map made of 9 colours (blue-green-red).""" - return PyMAPDL_cmap diff --git a/src/ansys/mapdl/core/examples/verif_files.py b/src/ansys/mapdl/core/examples/verif_files.py index 7a6ccb2ccd..327c4f8989 100755 --- a/src/ansys/mapdl/core/examples/verif_files.py +++ b/src/ansys/mapdl/core/examples/verif_files.py @@ -25,14 +25,18 @@ import glob import inspect import os +from typing import Dict -module_path = os.path.dirname(inspect.getfile(inspect.currentframe())) +module_path: str = os.path.dirname(inspect.getfile(inspect.currentframe())) -def load_vmfiles(): +def load_vmfiles() -> Dict[str, str]: """load vmfiles and store their filenames""" vmfiles = {} verif_path = os.path.join(module_path, "verif") + if not os.path.exists(verif_path): + return {} + for filename in glob.glob(os.path.join(verif_path, "*dat")): basename = os.path.basename(filename) vmname = os.path.splitext(basename)[0] @@ -42,7 +46,4 @@ def load_vmfiles(): # save the module from failing if the verification files are unavailable. -try: - vmfiles = load_vmfiles() -except: - vmfiles = [] +vmfiles: Dict[str, str] = load_vmfiles() diff --git a/src/ansys/mapdl/core/information.py b/src/ansys/mapdl/core/information.py index 1a9cb81789..3cd447a4a2 100644 --- a/src/ansys/mapdl/core/information.py +++ b/src/ansys/mapdl/core/information.py @@ -22,13 +22,14 @@ from functools import wraps import re +from typing import Callable, List, Optional import weakref from ansys.mapdl import core as pymapdl from ansys.mapdl.core.errors import MapdlExitedError -def update_information_first(update=False): +def update_information_first(update: bool = False) -> Callable: """ Decorator to wrap :class:`Information ` methods to force update the fields when accessed. @@ -86,7 +87,7 @@ class Information: """ - def __init__(self, mapdl): + def __init__(self, mapdl: "Mapdl") -> None: """Class Initializer""" from ansys.mapdl.core.mapdl import MapdlBase # lazy import to avoid circular @@ -102,11 +103,11 @@ def __init__(self, mapdl): } @property - def _mapdl(self): + def _mapdl(self) -> "Mapdl": """Return the weakly referenced MAPDL instance.""" return self._mapdl_weakref() - def _update(self): + def _update(self) -> None: """We might need to do more calls if we implement properties that change over the MAPDL session.""" try: @@ -122,7 +123,7 @@ def _update(self): self._stats = stats self._mapdl._log.debug("Information class: Updated") - def __repr__(self): + def __repr__(self) -> str: if self._mapdl.is_console and self._mapdl.exited: return "MAPDL exited" @@ -138,92 +139,92 @@ def __repr__(self): @property @update_information_first(False) - def product(self): + def product(self) -> str: """Retrieve the product from the MAPDL instance.""" return self._get_product() @property @update_information_first(False) - def mapdl_version(self): + def mapdl_version(self) -> str: """Retrieve the MAPDL version from the MAPDL instance.""" return self._get_mapdl_version() @property @update_information_first(False) - def mapdl_version_release(self): + def mapdl_version_release(self) -> str: """Retrieve the MAPDL version release from the MAPDL instance.""" st = self._get_mapdl_version() return self._get_between("RELEASE", "BUILD", st).strip() @property @update_information_first(False) - def mapdl_version_build(self): + def mapdl_version_build(self) -> str: """Retrieve the MAPDL version build from the MAPDL instance.""" st = self._get_mapdl_version() return self._get_between("BUILD", "UPDATE", st).strip() @property @update_information_first(False) - def mapdl_version_update(self): + def mapdl_version_update(self) -> str: """Retrieve the MAPDL version update from the MAPDL instance.""" st = self._get_mapdl_version() return self._get_between("UPDATE", "", st).strip() @property @update_information_first(False) - def pymapdl_version(self): + def pymapdl_version(self) -> str: """Retrieve the PyMAPDL version from the MAPDL instance.""" return self._get_pymapdl_version() @property @update_information_first(False) - def products(self): + def products(self) -> str: """Retrieve the products from the MAPDL instance.""" return self._get_products() @property @update_information_first(False) - def preprocessing_capabilities(self): + def preprocessing_capabilities(self) -> str: """Retrieve the preprocessing capabilities from the MAPDL instance.""" return self._get_preprocessing_capabilities() @property @update_information_first(False) - def aux_capabilities(self): + def aux_capabilities(self) -> str: """Retrieve the aux capabilities from the MAPDL instance.""" return self._get_aux_capabilities() @property @update_information_first(True) - def solution_options(self): + def solution_options(self) -> str: """Retrieve the solution options from the MAPDL instance.""" return self._get_solution_options() @property @update_information_first(False) - def post_capabilities(self): + def post_capabilities(self) -> str: """Retrieve the post capabilities from the MAPDL instance.""" return self._get_post_capabilities() @property @update_information_first(True) - def titles(self): + def titles(self) -> str: """Retrieve the titles from the MAPDL instance.""" return self._get_titles() @property @update_information_first(True) - def title(self): + def title(self) -> str: """Retrieve and set the title from the MAPDL instance.""" return self._mapdl.inquire("", "title") @title.setter - def title(self, title): + def title(self, title) -> str: return self._mapdl.run(f"/TITLE, {title}") @property @update_information_first(True) - def stitles(self, i=None): + def stitles(self, i: int = None) -> str: """Retrieve or set the value for the MAPDL stitle (subtitles). If 'stitle' includes newline characters (`\\n`), then each line @@ -241,7 +242,7 @@ def stitles(self, i=None): return self._get_stitles()[i] @stitles.setter - def stitles(self, stitle, i=None): + def stitles(self, stitle: str, i: int = None) -> None: if stitle is None: # Case to empty stitle = ["", "", "", ""] @@ -268,71 +269,76 @@ def stitles(self, stitle, i=None): @property @update_information_first(True) - def units(self): + def units(self) -> str: """Retrieve the units from the MAPDL instance.""" return self._get_units() @property @update_information_first(True) - def scratch_memory_status(self): + def scratch_memory_status(self) -> str: """Retrieve the scratch memory status from the MAPDL instance.""" return self._get_scratch_memory_status() @property @update_information_first(True) - def database_status(self): + def database_status(self) -> str: """Retrieve the database status from the MAPDL instance.""" return self._get_database_status() @property @update_information_first(True) - def config_values(self): + def config_values(self) -> str: """Retrieve the config values from the MAPDL instance.""" return self._get_config_values() @property @update_information_first(True) - def global_status(self): + def global_status(self) -> str: """Retrieve the global status from the MAPDL instance.""" return self._get_global_status() @property @update_information_first(True) - def job_information(self): + def job_information(self) -> str: """Retrieve the job information from the MAPDL instance.""" return self._get_job_information() @property @update_information_first(True) - def model_information(self): + def model_information(self) -> str: """Retrieve the model information from the MAPDL instance.""" return self._get_model_information() @property @update_information_first(True) - def boundary_condition_information(self): + def boundary_condition_information(self) -> str: """Retrieve the boundary condition information from the MAPDL instance.""" return self._get_boundary_condition_information() @property @update_information_first(True) - def routine_information(self): + def routine_information(self) -> str: """Retrieve the routine information from the MAPDL instance.""" return self._get_routine_information() @property @update_information_first(True) - def solution_options_configuration(self): + def solution_options_configuration(self) -> str: """Retrieve the solution options configuration from the MAPDL instance.""" return self._get_solution_options_configuration() @property @update_information_first(True) - def load_step_options(self): + def load_step_options(self) -> str: """Retrieve the load step options from the MAPDL instance.""" return self._get_load_step_options() - def _get_between(self, init_string, end_string=None, string=None): + def _get_between( + self, + init_string: str, + end_string: Optional[str] = None, + string: Optional[str] = None, + ) -> str: if not string: self._update() string = self._stats @@ -345,24 +351,24 @@ def _get_between(self, init_string, end_string=None, string=None): en = string.find(end_string) return "\n".join(string[st:en].splitlines()).strip() - def _get_product(self): + def _get_product(self) -> str: return self._get_products().splitlines()[0] - def _get_mapdl_version(self): + def _get_mapdl_version(self) -> str: titles_ = self._get_titles() st = titles_.find("RELEASE") en = titles_.find("INITIAL", st) return titles_[st:en].split("CUSTOMER")[0].strip() - def _get_pymapdl_version(self): + def _get_pymapdl_version(self) -> str: return pymapdl.__version__ - def _get_title(self): + def _get_title(self) -> str: match = re.match(r"TITLE=(.*)$", self._get_titles()) if match: return match.groups(1)[0].strip() - def _get_stitles(self): + def _get_stitles(self) -> List[str]: return [ ( re.search(f"SUBTITLE {i}=(.*)", self._get_titles()) @@ -374,87 +380,87 @@ def _get_stitles(self): for i in range(1, 5) ] - def _get_products(self): + def _get_products(self) -> str: init_ = "*** Products ***" end_string = "*** PreProcessing Capabilities ***" return self._get_between(init_, end_string) - def _get_preprocessing_capabilities(self): + def _get_preprocessing_capabilities(self) -> str: init_ = "*** PreProcessing Capabilities ***" end_string = "*** Aux Capabilities ***" return self._get_between(init_, end_string) - def _get_aux_capabilities(self): + def _get_aux_capabilities(self) -> str: init_ = "*** Aux Capabilities ***" end_string = "*** Solution Options ***" return self._get_between(init_, end_string) - def _get_solution_options(self): + def _get_solution_options(self) -> str: init_ = "*** Solution Options ***" end_string = "*** Post Capabilities ***" return self._get_between(init_, end_string) - def _get_post_capabilities(self): + def _get_post_capabilities(self) -> str: init_ = "*** Post Capabilities ***" end_string = "***** TITLES *****" return self._get_between(init_, end_string) - def _get_titles(self): + def _get_titles(self) -> str: init_ = "***** TITLES *****" end_string = "***** UNITS *****" return self._get_between(init_, end_string) - def _get_units(self): + def _get_units(self) -> str: init_ = "***** UNITS *****" end_string = "***** SCRATCH MEMORY STATUS *****" return self._get_between(init_, end_string) - def _get_scratch_memory_status(self): + def _get_scratch_memory_status(self) -> str: init_ = "***** SCRATCH MEMORY STATUS *****" end_string = "***** DATABASE STATUS *****" return self._get_between(init_, end_string) - def _get_database_status(self): + def _get_database_status(self) -> str: init_ = "***** DATABASE STATUS *****" end_string = "***** CONFIG VALUES *****" return self._get_between(init_, end_string) - def _get_config_values(self): + def _get_config_values(self) -> str: init_ = "***** CONFIG VALUES *****" end_string = "G L O B A L S T A T U S" return self._get_between(init_, end_string) - def _get_global_status(self): + def _get_global_status(self) -> str: init_ = "G L O B A L S T A T U S" end_string = "J O B I N F O R M A T I O N" return self._get_between(init_, end_string) - def _get_job_information(self): + def _get_job_information(self) -> str: init_ = "J O B I N F O R M A T I O N" end_string = "M O D E L I N F O R M A T I O N" return self._get_between(init_, end_string) - def _get_model_information(self): + def _get_model_information(self) -> str: init_ = "M O D E L I N F O R M A T I O N" end_string = "B O U N D A R Y C O N D I T I O N I N F O R M A T I O N" return self._get_between(init_, end_string) - def _get_boundary_condition_information(self): + def _get_boundary_condition_information(self) -> str: init_ = "B O U N D A R Y C O N D I T I O N I N F O R M A T I O N" end_string = "R O U T I N E I N F O R M A T I O N" return self._get_between(init_, end_string) - def _get_routine_information(self): + def _get_routine_information(self) -> str: init_ = "R O U T I N E I N F O R M A T I O N" end_string = None return self._get_between(init_, end_string) - def _get_solution_options_configuration(self): + def _get_solution_options_configuration(self) -> str: init_ = "S O L U T I O N O P T I O N S" end_string = "L O A D S T E P O P T I O N S" return self._get_between(init_, end_string) - def _get_load_step_options(self): + def _get_load_step_options(self) -> str: init_ = "L O A D S T E P O P T I O N S" end_string = None return self._get_between(init_, end_string) diff --git a/src/ansys/mapdl/core/inline_functions/connectivity_queries.py b/src/ansys/mapdl/core/inline_functions/connectivity_queries.py index a39fc9d432..1314e05d0b 100644 --- a/src/ansys/mapdl/core/inline_functions/connectivity_queries.py +++ b/src/ansys/mapdl/core/inline_functions/connectivity_queries.py @@ -26,7 +26,7 @@ class _ConnectivityQueries(_QueryExecution): _mapdl = None - def nelem(self, e, npos) -> int: + def nelem(self, e: int, npos: int) -> int: """Return the number of the node at position ``npos`` in element ``e``. Returns the node number in position `npos` for element number ``e``. @@ -64,7 +64,7 @@ def nelem(self, e, npos) -> int: """ return self._run_query(f"NELEM({e},{npos})", integer=True) - def enextn(self, n, loc) -> int: + def enextn(self, n: int, loc: int) -> int: """Returns the ``loc`` element connected to node ``n``. Returns the element connected to node ``n``. ``loc`` is the position diff --git a/src/ansys/mapdl/core/inline_functions/core.py b/src/ansys/mapdl/core/inline_functions/core.py index 65d7d59d9e..44032cc8c6 100644 --- a/src/ansys/mapdl/core/inline_functions/core.py +++ b/src/ansys/mapdl/core/inline_functions/core.py @@ -102,12 +102,12 @@ def _run_query(self, command: str, integer: bool) -> Union[int, float]: return self._parse_parameter_integer_response(resp) return self._parse_parameter_float_response(resp) - def _parse_parameter_integer_response(self, response) -> int: + def _parse_parameter_integer_response(self, response: str) -> int: """Parse integer response.""" return int(self._parse_parameter_float_response(response)) @staticmethod - def _parse_parameter_float_response(response) -> float: + def _parse_parameter_float_response(response: str) -> float: if "PARAMETER" not in response or "=" not in response: raise TypeError(f"Parameter response not recognised: " f'"{response}"') parts = response.rsplit("=", 1) diff --git a/src/ansys/mapdl/core/inline_functions/geometry_queries.py b/src/ansys/mapdl/core/inline_functions/geometry_queries.py index a9c3d14a4d..eb44ea59ef 100644 --- a/src/ansys/mapdl/core/inline_functions/geometry_queries.py +++ b/src/ansys/mapdl/core/inline_functions/geometry_queries.py @@ -26,7 +26,7 @@ class _AngleQueries(_QueryExecution): _mapdl = None - def anglen(self, n1, n2, n3) -> float: + def anglen(self, n1: int, n2: int, n3: int) -> float: """Return the angle between 3 nodes where ``n1`` is the vertex. Subtended angle between two lines (defined by three @@ -66,7 +66,7 @@ def anglen(self, n1, n2, n3) -> float: """ return self._run_query(f"ANGLEN({n1},{n2},{n3})", integer=False) - def anglek(self, k1, k2, k3) -> float: + def anglek(self, k1: int, k2: int, k3: int) -> float: """Return the angle between 3 keypoints where ``k1`` is the vertex. Subtended angle between two lines (defined by three @@ -111,7 +111,7 @@ def anglek(self, k1, k2, k3) -> float: class _AreaQueries(_QueryExecution): _mapdl = None - def areand(self, n1, n2, n3) -> float: + def areand(self, n1: int, n2: int, n3: int) -> float: """Area of the triangle with vertices at nodes ``n1``, ``n2``, and ``n3``. Parameters @@ -143,7 +143,7 @@ def areand(self, n1, n2, n3) -> float: """ return self._run_query(f"AREAND({n1},{n2},{n3})", integer=False) - def areakp(self, k1, k2, k3) -> float: + def areakp(self, k1: int, k2: int, k3: int) -> float: """Area of the triangle with vertices at keypoints ``k1``, ``k2``, and ``k3``. Parameters @@ -179,7 +179,7 @@ def areakp(self, k1, k2, k3) -> float: class _DistanceQueries(_QueryExecution): _mapdl = None - def distnd(self, n1, n2) -> float: + def distnd(self, n1: int, n2: int) -> float: """Compute the distance between nodes ``n1`` and ``n2``. Parameters @@ -208,7 +208,7 @@ def distnd(self, n1, n2) -> float: """ return self._run_query(f"DISTND({n1},{n2})", integer=False) - def distkp(self, k1, k2) -> float: + def distkp(self, k1: int, k2: int) -> float: """Compute the distance between keypoints ``k1`` and ``k2``. Parameters diff --git a/src/ansys/mapdl/core/jupyter.py b/src/ansys/mapdl/core/jupyter.py index 754ac92280..3ccec5e20d 100644 --- a/src/ansys/mapdl/core/jupyter.py +++ b/src/ansys/mapdl/core/jupyter.py @@ -31,11 +31,11 @@ ) -MAX_CPU = 128 -MAX_MEM = 256 +MAX_CPU: int = 128 +MAX_MEM: int = 256 -def check_manager(): +def check_manager() -> None: try: # response = manager.ping() manager.ping() @@ -44,14 +44,14 @@ def check_manager(): def launch_mapdl_on_cluster( - nproc=2, - memory=4, - loglevel="ERROR", - additional_switches="", - verbose=False, - start_timeout=600, - tag="latest", -): + nproc: int = 2, + memory: int = 4, + loglevel: str = "ERROR", + additional_switches: str = "", + verbose: bool = False, + start_timeout: int = 600, + tag: str = "latest", +) -> "Mapdl": """Start MAPDL on the ANSYS jupyter cluster in gRPC mode. Parameters diff --git a/src/ansys/mapdl/core/krylov.py b/src/ansys/mapdl/core/krylov.py index 9f2ebb6333..c035c82968 100644 --- a/src/ansys/mapdl/core/krylov.py +++ b/src/ansys/mapdl/core/krylov.py @@ -21,13 +21,26 @@ # SOFTWARE. import os +from typing import List, Literal, Optional, Tuple, Union import weakref +from ansys.math.core.math import AnsMath, AnsVec import numpy as np from ansys.mapdl.core import Mapdl from ansys.mapdl.core.errors import MapdlRuntimeError +RESIDUAL_ALGORITHM: List[str] = [ + "l-inf", + "linf", + "l-1", + "l1", + "l-2", + "l2", +] + +RESIDUAL_ALGORITHM_LITERAL = Literal[tuple(RESIDUAL_ALGORITHM)] + class KrylovSolver: """Abstract mapdl krylov class. Created from a ``Mapdl`` instance. @@ -94,11 +107,11 @@ def __init__(self, mapdl: Mapdl): self.orthogonality = None @property - def _mapdl(self): + def _mapdl(self) -> Mapdl: """Return the weakly referenced instance of mapdl.""" return self._mapdl_weakref() - def _check_full_file_exists(self, full_file=None): + def _check_full_file_exists(self, full_file: str = None) -> None: """Check full file exists.""" current_dir = self._mapdl.directory # Check if full file exists @@ -120,7 +133,7 @@ def _check_full_file_exists(self, full_file=None): ) @property - def is_orthogonal(self): + def is_orthogonal(self) -> bool: """ Check whether the solution is orthogonal. @@ -133,7 +146,9 @@ def is_orthogonal(self): eye_ = np.eye(N=self.orthogonality.shape[0]) return np.allclose(self.orthogonality, eye_) - def _check_input_gensubspace(self, max_dim_q, freq_val, check_orthogonality): + def _check_input_gensubspace( + self, max_dim_q: int, freq_val: int, check_orthogonality: bool + ): """Validate the inputs to the ``gensubspace`` method.""" # Check for illegal input values by the user @@ -154,7 +169,9 @@ def _check_input_gensubspace(self, max_dim_q, freq_val, check_orthogonality): "True or False" ) - def _check_input_solve(self, freq_start, freq_end, freq_steps, ramped_load): + def _check_input_solve( + self, freq_start: int, freq_end: int, freq_steps: int, ramped_load: bool + ): """Validate the inputs to the ``solve`` method.""" if not isinstance(freq_start, int) or freq_start < 0: @@ -182,7 +199,10 @@ def _check_input_solve(self, freq_start, freq_end, freq_steps, ramped_load): ) def _check_input_expand( - self, return_solution, residual_computation, residual_algorithm + self, + return_solution: bool, + residual_computation: bool, + residual_algorithm: RESIDUAL_ALGORITHM_LITERAL, ): """Validate the inputs to the ``expand`` method.""" @@ -192,21 +212,15 @@ def _check_input_expand( ) if not isinstance(residual_computation, bool): raise ValueError("The 'residual_computation' must be True or False.") - if not isinstance( - residual_algorithm, str - ) or residual_algorithm.lower() not in [ - "l-inf", - "linf", - "l-1", - "l1", - "l-2", - "l2", - ]: + if ( + not isinstance(residual_algorithm, str) + or residual_algorithm.lower() not in RESIDUAL_ALGORITHM + ): raise ValueError( "The provided 'residual_algorithm' is not allowed. Only allowed are 'L-inf', 'Linf', 'L-1', 'L1', 'L-2', 'L2'." ) - def _get_data_from_full_file(self): + def _get_data_from_full_file(self) -> None: """Extract stiffness, mass, damping, and force matrices from the FULL file.""" self._mat_k = self.mm.stiff(fname=self.full_file) @@ -221,7 +235,7 @@ def _get_data_from_full_file(self): self._mapdl.vec("fz0", "Z", "COPY", "fz") self.fz0 = self.mm.vec(name="fz0") - def _calculate_orthogonality(self, uz, num_q): + def _calculate_orthogonality(self, uz: AnsVec, num_q: int): """Check Orthonormality of vectors""" if self.orthogonality is not None: @@ -246,8 +260,12 @@ def _calculate_orthogonality(self, uz, num_q): return self.orthogonality def gensubspace( - self, max_dim_q, frequency, check_orthogonality=False, full_file=None - ): + self, + max_dim_q: int, + frequency: int, + check_orthogonality: bool = False, + full_file: Optional[str] = None, + ) -> AnsMath: """Generate a Krylov subspace for model reduction in a harmonic analysis. This method generates a Krylov subspace used for a model reduction @@ -406,7 +424,9 @@ def gensubspace( self._run_gensubspace = True return self.Qz - def solve(self, freq_start, freq_end, freq_steps, ramped_load=True): + def solve( + self, freq_start: str, freq_end: str, freq_steps: str, ramped_load: bool = True + ) -> AnsMath: """Reduce the system of equations and solve at each frequency. This method uses a Krylov subspace to solve a reduced harmonic @@ -516,11 +536,11 @@ def solve(self, freq_start, freq_end, freq_steps, ramped_load=True): def expand( self, - residual_computation=False, - residual_algorithm=None, - compute_solution_vectors=True, - return_solution=False, - ): + residual_computation: bool = False, + residual_algorithm: Optional[RESIDUAL_ALGORITHM_LITERAL] = None, + compute_solution_vectors: bool = True, + return_solution: bool = False, + ) -> np.ndarray: """Expand the reduced solution back to FE space. This method expands the reduced solution for a harmonic analysis @@ -637,7 +657,9 @@ def expand( if return_solution: return self.solution_vectors - def compute_residuals(self, iFreq, RzV, Xi, omega): + def compute_residuals( + self, iFreq: int, RzV: AnsMath, Xi: AnsMath, omega: float + ) -> Tuple[float]: """Compute residuals of the matrices""" # form {iRHS} self.iRHS.zeros() @@ -699,7 +721,9 @@ def compute_residuals(self, iFreq, RzV, Xi, omega): return norm_rz, norm_fz - def _compute_solution_vector(self, Xi): + def _compute_solution_vector( + self, Xi: AnsMath + ) -> List[Tuple[Union[int, int, complex]]]: self._mapdl.mult( m1=self.Nod2Solv.id, t1="TRANS", m2=Xi.id, t2="", m3="Xii" ) # Map {Xi} to internal (ANSYS) order diff --git a/tests/test_examples.py b/tests/test_examples.py index cbe980656e..43b769776a 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -171,20 +171,20 @@ def test_detach_examples_submodule(): """ import sys -assert 'ansys.mapdl.core' not in sys.modules -assert 'requests' not in sys.modules -assert 'ansys.mapdl.core.examples' not in sys.modules +assert 'ansys.mapdl.core' not in sys.modules, 'PyMAPDL is loaded!' +assert 'requests' not in sys.modules, 'Requests is loaded!' +assert 'ansys.mapdl.core.examples' not in sys.modules, 'Examples is loaded!' from ansys.mapdl import core as pymapdl -assert 'ansys.mapdl.core' in sys.modules -assert 'ansys.mapdl.core.examples' not in sys.modules -assert 'requests' not in sys.modules +assert 'ansys.mapdl.core' in sys.modules, 'PyMAPDL is not loaded!' +assert 'ansys.mapdl.core.examples' not in sys.modules, 'Examples is loaded!' +assert 'requests' in sys.modules, 'Requests is loaded!' from ansys.mapdl.core.examples import vmfiles -assert 'ansys.mapdl.core.examples' in sys.modules -assert 'requests' in sys.modules +assert 'ansys.mapdl.core.examples' in sys.modules, 'examples is not loaded!' +assert 'requests' in sys.modules, 'requests is not loaded!' print('Everything went well') """.strip() @@ -198,3 +198,11 @@ def test_detach_examples_submodule(): out = p.communicate()[0].decode() assert out.strip() == "Everything went well" + + +def test_external_models(): + from ansys.mapdl.core.examples import examples + + for each in dir(examples): + if each not in ["os", "dir_path"] and not each.startswith("__"): + obj = getattr(examples, each)