diff --git a/src/ansys/mapdl/core/mapdl.py b/src/ansys/mapdl/core/mapdl.py index 4e23be3348..0d0c4f2c0e 100644 --- a/src/ansys/mapdl/core/mapdl.py +++ b/src/ansys/mapdl/core/mapdl.py @@ -2902,8 +2902,10 @@ def cwd(self, *args, **kwargs): def _check_parameter_name(self, param_name): """Checks if a parameter name is allowed or not.""" + param_name = param_name.strip() - match_valid_parameter_name = r"^[a-zA-Z_][a-zA-Z\d_]{0,31}$" + match_valid_parameter_name = r"^[a-zA-Z_][a-zA-Z\d_\(\),\s\%]{0,31}$" + # Using % is allowed, because of substitution, but it is very likely MAPDL will complain. if not re.search(match_valid_parameter_name, param_name): raise ValueError( f"The parameter name `{param_name}` is an invalid parameter name." @@ -2911,9 +2913,27 @@ def _check_parameter_name(self, param_name): "It cannot start with a number either." ) - # invalid parameter (using ARGXX or ARXX) + if "(" in param_name or ")" in param_name: + if param_name.count("(") != param_name.count(")"): + raise ValueError( + "The parameter name should have all the parenthesis in pairs (closed)." + ) + + if param_name[-1] != ")": + raise ValueError( + "If using parenthesis (indexing), you cannot use any character after the closing parenthesis." + ) + + # Check recursively the parameter name without parenthesis. + # This is the real parameter name, however it must already exists to not raise an error. + sub_param_name = re.findall(r"^(.*)\(", param_name) + if sub_param_name: + self._check_parameter_name(sub_param_name[0]) + return # Following checks should not run against the parenthesis + + # Using leading underscored parameters match_reserved_leading_underscored_parameter_name = ( - r"^_[a-zA-Z\d_]{1,31}[a-zA-Z\d]$" + r"^_[a-zA-Z\d_\(\),\s_]{1,31}[a-zA-Z\d\(\),\s]$" ) # If it also ends in undescore, this won't be triggered. if re.search(match_reserved_leading_underscored_parameter_name, param_name): @@ -2922,6 +2942,7 @@ def _check_parameter_name(self, param_name): "This convention is reserved for parameters used by the GUI and/or Mechanical APDL-provided macros." ) + # invalid parameter (using ARGXX or ARXX) match_reserved_arg_parameter_name = r"^(AR|ARG)(\d{1,3})$" if re.search( match_reserved_arg_parameter_name, param_name @@ -2968,3 +2989,27 @@ def mpwrite( self.download(os.path.basename(fname_), progress_bar=progress_bar) return output + + @wraps(Commands.dim) + def dim( + self, + par="", + type_="", + imax="", + jmax="", + kmax="", + var1="", + var2="", + var3="", + csysid="", + **kwargs, + ): + self._check_parameter_name(par) # parameter name check + if "(" in par or ")" in par: + raise ValueError( + "Parenthesis are not allowed as parameter name in 'mapdl.dim'." + ) + + return super().dim( + par, type_, imax, jmax, kmax, var1, var2, var3, csysid, **kwargs + ) diff --git a/src/ansys/mapdl/core/parameters.py b/src/ansys/mapdl/core/parameters.py index c8a9536ffe..bc5a72ccdb 100644 --- a/src/ansys/mapdl/core/parameters.py +++ b/src/ansys/mapdl/core/parameters.py @@ -36,6 +36,72 @@ class Parameters: """Collection of MAPDL parameters obtainable from the :func:`ansys.mapdl.core.Mapdl.get` command. + Notes + ----- + + **Leading underscored parameters** + + The parameters starting with underscore ('_') are reserved parameters + for MAPDL macros and routines, therefore its use is discouraged, and + in PyMAPDL you cannot set them by default. + + If you need to set one of this parameters, you can use ``mapdl._run`` + to avoid PyMAPDL parameter name checks. For example + + >>> mapdl._run('_parameter=123') + 'PARAMETER _PARAMETER = 123.00000000' + + By default, this type of parameters cannot be seen when issuing + ``mapdl.parameters``. However, you can change this by setting + ``mapdl.parameters.show_leading_underscore_parameters`` equal to True. + For example: + + >>> mapdl.parameters.show_leading_underscore_parameters=True + >>> mapdl.parameters + MAPDL Parameters + ---------------- + PORT : 50053.0 + _RETURN : 0.0 + _STATUS : 0.0 + _UIQR : 17.0 + + + **Trailing underscored parameters** + + The parameters ending underscore are recommend for user routines + and macros. + You can set this type of parameters in PyMAPDL, but by default, + they cannot be seen in ``mapdl.parameters``, unless + ``mapdl.parameters.show_trailing_underscore_parameters`` is set + to True. + + >>> mapdl.parameters['param_'] = 1.0 + >>> mapdl.parameters + MAPDL Parameters + ---------------- + >>> mapdl.parameters.show_trailing_underscore_parameters=True + >>> mapdl.parameters + MAPDL Parameters + ---------------- + PARAM_ : 1.0 + + **Parameters with leading and trailing underscore** + + These are an especial type of parameters. They CANNOT be seen + in ``mapdl.parameters`` under any circumstances, and because + of it, it uses is not recommended. + + You can still retrieve them using any of the normal methods + to retrieve parameters. But you shall know the parameter name. + For example: + + >>> mapdl.parameters["_param_"] = 1.0 + >>> mapdl.parameters + MAPDL Parameters + ---------------- + >>> print(mapdl.parameters['_param_']) + 1.0 + Examples -------- Simply list all parameters except for MAPDL MATH parameters. @@ -64,6 +130,9 @@ def __init__(self, mapdl): if not isinstance(mapdl, _MapdlCore): raise TypeError("Must be implemented from MAPDL class") self._mapdl_weakref = weakref.ref(mapdl) + self.show_leading_underscore_parameters = False + self.show_trailing_underscore_parameters = False + self.full_parameters_output = self._full_parameter_output(self) @property def _mapdl(self): @@ -262,7 +331,17 @@ def type(self) -> int: @supress_logging def _parm(self): """Current MAPDL parameters""" - return interp_star_status(self._mapdl.starstatus()) + params = interp_star_status(self._mapdl.starstatus()) + + if self.show_leading_underscore_parameters: + _params = interp_star_status(self._mapdl.starstatus("_PRM")) + params.update(_params) + + if self.show_trailing_underscore_parameters: + params_ = interp_star_status(self._mapdl.starstatus("PRM_")) + params.update(params_) + + return params def __repr__(self): """Return the current parameters in a pretty format""" @@ -288,7 +367,14 @@ def __getitem__(self, key): raise TypeError("Parameter name must be a string") key = key.upper() - parameters = self._parm + # We are going to directly query the desired parameter. + # It is more efficient (less parsing) and + # you can obtain leading and trailing underscore parameters, which + # they don't appear in a normal ``*STATUS`` + + with self.full_parameters_output: + parameters = self._parm + if key not in parameters: raise IndexError("%s not a valid parameter_name" % key) @@ -304,6 +390,8 @@ def __getitem__(self, key): def __setitem__(self, key, value): """Set a parameter""" + self._mapdl._check_parameter_name(key) + # parameters = self._parm # check parameter exists if isinstance(value, (np.ndarray, list)): self._set_parameter_array(key, value) @@ -510,6 +598,36 @@ def _write_numpy_array(self, filename, arr): if not self._mapdl._local: self._mapdl.upload(filename, progress_bar=False) + class _full_parameter_output: + """Change the show_** options to true to allow full parameter output.""" + + def __init__(self, parent): + self._parent = weakref.ref(parent) + self.show_leading_underscore_parameters = None + self.show_trailing_underscore_parameters = None + + def __enter__(self): + """Storing current state.""" + self.show_leading_underscore_parameters = ( + self._parent().show_leading_underscore_parameters + ) + self.show_trailing_underscore_parameters = ( + self._parent().show_trailing_underscore_parameters + ) + + # Getting full output. + self._parent().show_leading_underscore_parameters = True + self._parent().show_trailing_underscore_parameters = True + + def __exit__(self, *args): + """Coming back to previous state.""" + self._parent().show_leading_underscore_parameters = ( + self.show_leading_underscore_parameters + ) + self._parent().show_trailing_underscore_parameters = ( + self.show_trailing_underscore_parameters + ) + def interp_star_status(status): """Interprets \*STATUS command output from MAPDL diff --git a/tests/test_mapdl.py b/tests/test_mapdl.py index da6656db45..3e4fe313c1 100644 --- a/tests/test_mapdl.py +++ b/tests/test_mapdl.py @@ -1276,192 +1276,6 @@ def test_extra_argument_in_get(mapdl, make_block): ) -@pytest.mark.parametrize( - "par_name", - [ - "asdf124", - "asd", - "a12345", - "a12345_", - pytest.param( - "_a12345", - marks=pytest.mark.xfail, - id="Starting by underscore, but not ending", - ), - "_a12345_", - pytest.param("1asdf", marks=pytest.mark.xfail, id="Starting by number"), - pytest.param( - "123asdf", marks=pytest.mark.xfail, id="Starting by several numbers" - ), - pytest.param( - "asa12df+", marks=pytest.mark.xfail, id="Invalid symbol in parameter name." - ), - # function args - pytest.param( - "AR0", - marks=pytest.mark.xfail, - id="Using `AR0` with is reserved for functions/macros", - ), - pytest.param( - "AR1", - marks=pytest.mark.xfail, - id="Using `AR1` with is reserved for functions/macros", - ), - pytest.param( - "AR10", - marks=pytest.mark.xfail, - id="Using `AR10` with is reserved for functions/macros", - ), - pytest.param( - "AR99", - marks=pytest.mark.xfail, - id="Using `AR99` with is reserved for functions/macros", - ), - pytest.param( - "AR111", - marks=pytest.mark.xfail, - id="Using `AR111` with is reserved for functions/macros", - ), - pytest.param( - "AR999", - marks=pytest.mark.xfail, - id="Using `AR999` with is reserved for functions/macros", - ), - pytest.param( - "ARG0", - marks=pytest.mark.xfail, - id="Using `ARG0` with is reserved for functions/macros", - ), - pytest.param( - "ARG1", - marks=pytest.mark.xfail, - id="Using `ARG1` with is reserved for functions/macros", - ), - pytest.param( - "ARG10", - marks=pytest.mark.xfail, - id="Using `ARG10` with is reserved for functions/macros", - ), - pytest.param( - "ARG99", - marks=pytest.mark.xfail, - id="Using `ARG99` with is reserved for functions/macros", - ), - pytest.param( - "ARG111", - marks=pytest.mark.xfail, - id="Using `ARG111` with is reserved for functions/macros", - ), - pytest.param( - "ARG999", - marks=pytest.mark.xfail, - id="Using `ARG999` with is reserved for functions/macros", - ), - # length - pytest.param( - "a23456789012345678901234567890123", - marks=pytest.mark.xfail, - id="Name too long", - ), - ], -) -def test_parameters_name(mapdl, par_name): - mapdl.run(f"{par_name} = 123") - - -@pytest.mark.parametrize( - "par_name", - [ - "asdf124", - "asd", - "a12345", - "a12345_", - pytest.param( - "_a12345", - marks=pytest.mark.xfail, - id="Starting by underscore, but not ending", - ), - "_a12345_", - pytest.param("1asdf", marks=pytest.mark.xfail, id="Starting by number"), - pytest.param( - "123asdf", marks=pytest.mark.xfail, id="Starting by several numbers" - ), - pytest.param( - "asa12df+", marks=pytest.mark.xfail, id="Invalid symbol in parameter name." - ), - # function args - pytest.param( - "AR0", - marks=pytest.mark.xfail, - id="Using `AR0` with is reserved for functions/macros", - ), - pytest.param( - "AR1", - marks=pytest.mark.xfail, - id="Using `AR1` with is reserved for functions/macros", - ), - pytest.param( - "AR10", - marks=pytest.mark.xfail, - id="Using `AR10` with is reserved for functions/macros", - ), - pytest.param( - "AR99", - marks=pytest.mark.xfail, - id="Using `AR99` with is reserved for functions/macros", - ), - pytest.param( - "AR111", - marks=pytest.mark.xfail, - id="Using `AR111` with is reserved for functions/macros", - ), - pytest.param( - "AR999", - marks=pytest.mark.xfail, - id="Using `AR999` with is reserved for functions/macros", - ), - pytest.param( - "ARG0", - marks=pytest.mark.xfail, - id="Using `ARG0` with is reserved for functions/macros", - ), - pytest.param( - "ARG1", - marks=pytest.mark.xfail, - id="Using `ARG1` with is reserved for functions/macros", - ), - pytest.param( - "ARG10", - marks=pytest.mark.xfail, - id="Using `ARG10` with is reserved for functions/macros", - ), - pytest.param( - "ARG99", - marks=pytest.mark.xfail, - id="Using `ARG99` with is reserved for functions/macros", - ), - pytest.param( - "ARG111", - marks=pytest.mark.xfail, - id="Using `ARG111` with is reserved for functions/macros", - ), - pytest.param( - "ARG999", - marks=pytest.mark.xfail, - id="Using `ARG999` with is reserved for functions/macros", - ), - # length - pytest.param( - "a23456789012345678901234567890123", - marks=pytest.mark.xfail, - id="Name too long", - ), - ], -) -def test_parameters_name_in_get(mapdl, par_name): - mapdl.get(par=par_name, entity="node", item1="count") - - @pytest.mark.parametrize("value", [1e-6, 1e-5, 1e-3, None]) def test_seltol(mapdl, value): if value: diff --git a/tests/test_parameters.py b/tests/test_parameters.py index fff927d5ee..77bdfdfbce 100644 --- a/tests/test_parameters.py +++ b/tests/test_parameters.py @@ -1,3 +1,5 @@ +import re + import numpy as np import pytest @@ -34,3 +36,153 @@ def test__get_parameter_array(mapdl, number): array = np.ones(shape) * number mapdl.load_array(name=name, array=array) mapdl.parameters._get_parameter_array(name, shape) + + +# We use also 'run' and 'get' to be more confident. +@pytest.mark.parametrize("func", ["run", "get", "_check_parameter_name", "parameters"]) +@pytest.mark.parametrize( + "par_name", + [ + "asdf124", + "asd", + "a12345", + "a12345_", + "_a12345_", + "_array2d_(1,1)", + "array3d_(1,1,1)", + pytest.param( + "_a12345", + marks=pytest.mark.xfail, + id="Starting by underscore, but not ending", + ), + pytest.param( + "_asdf(1)_", + marks=pytest.mark.xfail, + id="Indexing before underscore", + ), + pytest.param( + "_a12345", + marks=pytest.mark.xfail, + id="Starting by underscore, but not ending", + ), + pytest.param( + "_array2d(1,1)", + marks=pytest.mark.xfail, + id="Starting by underscore, but not ending", + ), + pytest.param("1asdf", marks=pytest.mark.xfail, id="Starting by number"), + pytest.param( + "123asdf", marks=pytest.mark.xfail, id="Starting by several numbers" + ), + pytest.param( + "asa12df+", marks=pytest.mark.xfail, id="Invalid symbol in parameter name." + ), + # function args + pytest.param( + "AR0", + marks=pytest.mark.xfail, + id="Using `AR0` with is reserved for functions/macros", + ), + pytest.param( + "AR1", + marks=pytest.mark.xfail, + id="Using `AR1` with is reserved for functions/macros", + ), + pytest.param( + "AR10", + marks=pytest.mark.xfail, + id="Using `AR10` with is reserved for functions/macros", + ), + pytest.param( + "AR99", + marks=pytest.mark.xfail, + id="Using `AR99` with is reserved for functions/macros", + ), + pytest.param( + "AR111", + marks=pytest.mark.xfail, + id="Using `AR111` with is reserved for functions/macros", + ), + pytest.param( + "AR999", + marks=pytest.mark.xfail, + id="Using `AR999` with is reserved for functions/macros", + ), + pytest.param( + "ARG0", + marks=pytest.mark.xfail, + id="Using `ARG0` with is reserved for functions/macros", + ), + pytest.param( + "ARG1", + marks=pytest.mark.xfail, + id="Using `ARG1` with is reserved for functions/macros", + ), + pytest.param( + "ARG10", + marks=pytest.mark.xfail, + id="Using `ARG10` with is reserved for functions/macros", + ), + pytest.param( + "ARG99", + marks=pytest.mark.xfail, + id="Using `ARG99` with is reserved for functions/macros", + ), + pytest.param( + "ARG111", + marks=pytest.mark.xfail, + id="Using `ARG111` with is reserved for functions/macros", + ), + pytest.param( + "ARG999", + marks=pytest.mark.xfail, + id="Using `ARG999` with is reserved for functions/macros", + ), + # length + pytest.param( + "a23456789012345678901234567890123", + marks=pytest.mark.xfail, + id="Name too long", + ), + pytest.param( + "aasdf234asdf5678901-2345", + marks=pytest.mark.xfail, + id="Not valid sign -", + ), + pytest.param( + "aasdf234asdf5678901+2345", + marks=pytest.mark.xfail, + id="Not valid sign +", + ), + pytest.param( + "aasdf234a?sdf5678901?2345", + marks=pytest.mark.xfail, + id="Not valid sign ?", + ), + ], +) +def test_parameters_name(mapdl, func, par_name): + if "_array2d_" in par_name: + mapdl.dim("_array2d_", "array", 2, 2) + + if "array3d_" in par_name: + mapdl.dim("array3d_", "array", 2, 2, 2) + + if func == "run": + mapdl.run(f"{par_name} = 17.0") + + elif func == "get": + mapdl.prep7() + mapdl.get(par_name, "active", 0, "rout") # it should return 17 + + elif func == "_check_parameter_name": + mapdl._check_parameter_name(par_name) + return True + + elif func == "parameters": + mapdl.parameters[par_name] = 17.0 + + if not re.search(r"[\(|\)]", par_name) and not re.search(r"_.*_", par_name): + # Avoiding check if indexing or starting and ending with _. + assert mapdl.parameters[par_name] + assert isinstance(mapdl.parameters[par_name], float)