Skip to content

Commit 62cf01e

Browse files
committed
feat: initial refactor. Still missing unit testing on new functions.
1 parent a7d4359 commit 62cf01e

24 files changed

+2499
-1636
lines changed

src/ansys/mapdl/core/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@
110110

111111
# override default launcher when on pyansys.com
112112
if "ANSJUPHUB_VER" in os.environ: # pragma: no cover
113-
from ansys.mapdl.core.jupyter import launch_mapdl_on_cluster as launch_mapdl
113+
from ansys.mapdl.core.launcher.jupyter import (
114+
launch_mapdl_on_cluster as launch_mapdl,
115+
)
114116
else:
115117
from ansys.mapdl.core.launcher import launch_mapdl
116118

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
import os
24+
import warnings
25+
26+
from ansys.mapdl.core import _HAS_ATP, LOG
27+
28+
LOCALHOST = "127.0.0.1"
29+
MAPDL_DEFAULT_PORT = 50052
30+
31+
ON_WSL = os.name == "posix" and (
32+
os.environ.get("WSL_DISTRO_NAME") or os.environ.get("WSL_INTEROP")
33+
)
34+
35+
if ON_WSL:
36+
LOG.info("On WSL: Running on WSL detected.")
37+
LOG.debug("On WSL: Allowing 'start_instance' and 'ip' arguments together.")
38+
39+
40+
from ansys.mapdl.core.launcher.console import launch_mapdl_console
41+
from ansys.mapdl.core.launcher.launcher import launch_mapdl
42+
from ansys.mapdl.core.launcher.remote import connect_to_mapdl
43+
from ansys.mapdl.core.launcher.tools import (
44+
close_all_local_instances,
45+
get_default_ansys,
46+
get_default_ansys_path,
47+
get_default_ansys_version,
48+
)
49+
50+
if _HAS_ATP:
51+
from functools import wraps
52+
53+
from ansys.tools.path import find_mapdl, get_mapdl_path
54+
from ansys.tools.path import version_from_path as _version_from_path
55+
56+
@wraps(_version_from_path)
57+
def version_from_path(*args, **kwargs):
58+
"""Wrap ansys.tool.path.version_from_path to raise a warning if the
59+
executable couldn't be found"""
60+
if kwargs.pop("launch_on_hpc", False):
61+
try:
62+
return _version_from_path(*args, **kwargs)
63+
except RuntimeError:
64+
warnings.warn("PyMAPDL could not find the ANSYS executable. ")
65+
else:
66+
return _version_from_path(*args, **kwargs)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
from typing import Optional, Union
24+
25+
from ansys.mapdl.core.launcher.tools import generate_start_parameters
26+
from ansys.mapdl.core.licensing import LicenseChecker
27+
from ansys.mapdl.core.mapdl_console import MapdlConsole
28+
29+
30+
def check_console_start_parameters(start_parm):
31+
valid_args = [
32+
"exec_file",
33+
"run_location",
34+
"jobname",
35+
"nproc",
36+
"additional_switches",
37+
"start_timeout",
38+
]
39+
for each in list(start_parm.keys()):
40+
if each not in valid_args:
41+
start_parm.pop(each)
42+
43+
return start_parm
44+
45+
46+
def launch_mapdl_console(
47+
exec_file: Optional[str] = None,
48+
run_location: Optional[str] = None,
49+
jobname: str = "file",
50+
*,
51+
nproc: Optional[int] = None,
52+
ram: Optional[Union[int, str]] = None,
53+
override: bool = False,
54+
loglevel: str = "ERROR",
55+
additional_switches: str = "",
56+
start_timeout: Optional[int] = None,
57+
log_apdl: Optional[Union[bool, str]] = None,
58+
):
59+
########################################
60+
# Processing arguments
61+
# --------------------
62+
#
63+
# processing arguments
64+
args = processing_local_arguments(locals())
65+
66+
# Check for a valid connection mode
67+
if args.get("mode", "console") != "console":
68+
raise ValueError("Invalid 'mode'.")
69+
70+
start_parm = generate_start_parameters(args)
71+
72+
# Early exit for debugging.
73+
if args["_debug_no_launch"]:
74+
# Early exit, just for testing
75+
return args # type: ignore
76+
77+
########################################
78+
# Local launching
79+
# ---------------
80+
#
81+
# Check the license server
82+
if args["license_server_check"]:
83+
LOG.debug("Checking license server.")
84+
lic_check = LicenseChecker(timeout=args["start_timeout"])
85+
lic_check.start()
86+
87+
LOG.debug("Starting MAPDL")
88+
########################################
89+
# Launch MAPDL on console mode
90+
# ----------------------------
91+
#
92+
start_parm = check_console_start_parameters(start_parm)
93+
mapdl = MapdlConsole(
94+
loglevel=args["loglevel"],
95+
log_apdl=args["log_apdl"],
96+
use_vtk=args["use_vtk"],
97+
**start_parm,
98+
)
99+
100+
# Stop license checker
101+
if args["license_server_check"]:
102+
LOG.debug("Stopping check on license server.")
103+
lic_check.stop()
104+
105+
return mapdl

src/ansys/mapdl/core/launcher/grpc.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
import os
24+
25+
# Subprocess is needed to start the backend. But
26+
# the input is controlled by the library. Excluding bandit check.
27+
import subprocess # nosec B404
28+
from typing import Dict, Optional
29+
30+
from ansys.mapdl.core import LOG
31+
from ansys.mapdl.core.launcher.local import processing_local_arguments
32+
from ansys.mapdl.core.launcher.tools import (
33+
generate_start_parameters,
34+
get_port,
35+
submitter,
36+
)
37+
from ansys.mapdl.core.licensing import LicenseChecker
38+
from ansys.mapdl.core.mapdl_grpc import MapdlGrpc
39+
40+
41+
def launch_mapdl_grpc():
42+
args = processing_local_arguments(locals())
43+
if args.get("mode", "grpc") != "grpc":
44+
raise ValueError("Invalid 'mode'.")
45+
args["port"] = get_port(args["port"], args["start_instance"])
46+
47+
start_parm = generate_start_parameters(args)
48+
49+
# Early exit for debugging.
50+
if args["_debug_no_launch"]:
51+
# Early exit, just for testing
52+
return args # type: ignore
53+
54+
# Check the license server
55+
if args["license_server_check"]:
56+
LOG.debug("Checking license server.")
57+
lic_check = LicenseChecker(timeout=args["start_timeout"])
58+
lic_check.start()
59+
60+
########################################
61+
# Launch MAPDL with gRPC
62+
# ----------------------
63+
#
64+
cmd = generate_mapdl_launch_command(
65+
exec_file=args["exec_file"],
66+
jobname=args["jobname"],
67+
nproc=args["nproc"],
68+
ram=args["ram"],
69+
port=args["port"],
70+
additional_switches=args["additional_switches"],
71+
)
72+
73+
try:
74+
#
75+
process = launch_grpc(
76+
cmd=cmd,
77+
run_location=args["run_location"],
78+
env_vars=env_vars,
79+
launch_on_hpc=args.get("launch_on_hpc"),
80+
mapdl_output=args.get("mapdl_output"),
81+
)
82+
83+
# Local mapdl launch check
84+
check_mapdl_launch(
85+
process, args["run_location"], args["start_timeout"], cmd
86+
)
87+
88+
except Exception as exception:
89+
LOG.error("An error occurred when launching MAPDL.")
90+
91+
jobid: int = start_parm.get("jobid", "Not found")
92+
93+
if args["license_server_check"]:
94+
LOG.debug("Checking license server.")
95+
lic_check.check()
96+
97+
raise exception
98+
99+
if args["just_launch"]:
100+
out = [args["ip"], args["port"]]
101+
if hasattr(process, "pid"):
102+
out += [process.pid]
103+
return out
104+
105+
########################################
106+
# Connect to MAPDL using gRPC
107+
# ---------------------------
108+
#
109+
try:
110+
mapdl = MapdlGrpc(
111+
cleanup_on_exit=args["cleanup_on_exit"],
112+
loglevel=args["loglevel"],
113+
set_no_abort=args["set_no_abort"],
114+
remove_temp_dir_on_exit=args["remove_temp_dir_on_exit"],
115+
log_apdl=args["log_apdl"],
116+
process=process,
117+
use_vtk=args["use_vtk"],
118+
**start_parm,
119+
)
120+
121+
except Exception as exception:
122+
LOG.error("An error occurred when connecting to MAPDL.")
123+
raise exception
124+
125+
return mapdl
126+
127+
128+
def launch_grpc(
129+
cmd: list[str],
130+
run_location: str = None,
131+
env_vars: Optional[Dict[str, str]] = None,
132+
launch_on_hpc: bool = False,
133+
mapdl_output: Optional[str] = None,
134+
) -> subprocess.Popen:
135+
"""Start MAPDL locally in gRPC mode.
136+
137+
Parameters
138+
----------
139+
cmd : str
140+
Command to use to launch the MAPDL instance.
141+
142+
run_location : str, optional
143+
MAPDL working directory. The default is the temporary working
144+
directory.
145+
146+
env_vars : dict, optional
147+
Dictionary with the environment variables to inject in the process.
148+
149+
launch_on_hpc : bool, optional
150+
If running on an HPC, this needs to be :class:`True` to avoid the
151+
temporary file creation on Windows.
152+
153+
mapdl_output : str, optional
154+
Whether redirect MAPDL console output (stdout and stderr) to a file.
155+
156+
Returns
157+
-------
158+
subprocess.Popen
159+
Process object
160+
"""
161+
if env_vars is None:
162+
env_vars = {}
163+
164+
# disable all MAPDL pop-up errors:
165+
env_vars.setdefault("ANS_CMD_NODIAG", "TRUE")
166+
167+
cmd_string = " ".join(cmd)
168+
if "sbatch" in cmd:
169+
header = "Running an MAPDL instance on the Cluster:"
170+
shell = os.name != "nt"
171+
cmd_ = cmd_string
172+
else:
173+
header = "Running an MAPDL instance"
174+
shell = False # To prevent shell injection
175+
cmd_ = cmd
176+
177+
LOG.info(
178+
"\n============"
179+
"\n============\n"
180+
f"{header}\nLocation:\n{run_location}\n"
181+
f"Command:\n{cmd_string}\n"
182+
f"Env vars:\n{env_vars}"
183+
"\n============"
184+
"\n============"
185+
)
186+
187+
if mapdl_output:
188+
stdout = open(str(mapdl_output), "wb", 0)
189+
stderr = subprocess.STDOUT
190+
else:
191+
stdout = subprocess.PIPE
192+
stderr = subprocess.PIPE
193+
194+
if os.name == "nt":
195+
# getting tmp file name
196+
if not launch_on_hpc:
197+
# if we are running on an HPC cluster (case not considered), we will
198+
# have to upload/create this file because it is needed for starting.
199+
tmp_inp = cmd[cmd.index("-i") + 1]
200+
with open(os.path.join(run_location, tmp_inp), "w") as f:
201+
f.write("FINISH\r\n")
202+
LOG.debug(
203+
f"Writing temporary input file: {tmp_inp} with 'FINISH' command."
204+
)
205+
206+
LOG.debug("MAPDL starting in background.")
207+
return submitter(
208+
cmd_,
209+
shell=shell, # sbatch does not work without shell.
210+
cwd=run_location,
211+
stdin=subprocess.DEVNULL,
212+
stdout=stdout,
213+
stderr=stderr,
214+
env_vars=env_vars,
215+
)

0 commit comments

Comments
 (0)