pymapdl
pymapdl copied to clipboard
Very variable time for launch_mapdl()
🤓 Before submitting the issue
- [X] I have visited the Troubleshooting section.
- [X] I have searched among the existing issues.
- [X] I am using a Python virtual environment.
- [X] I am using the latest version of PyMAPDL and its dependencies (fully updated virtual environment). You can update them using
pip install --upgrade --upgrade-strategy eager ansys-mapdl-core
in your activated virtual environment.
🔍 Description of the bug
I've noticed that time to execute mapdl.launch_mapdl() can vary from a lot when executing sucessive tests. To reproduce this I've adapted [this example](https://mapdl.docs.pyansys.com/version/stable/examples/gallery_examples/00-mapdl-examples/2d_plate_with_a_hole.html#sphx-glr-examples-gallery-examples-00-mapdl-examples-2d-plate-with-a-hole-py)
.
Here is the bar graph obtained when evaluating elapsed times:
The difference in total elapsed time is entirely explained from the difference in launch time for MAPDL. It is not normal that this launch time can vary that much. Any ideas?
🕵️ Steps To Reproduce
"""
Testing PyMAPDL launch time with model
MAPDL 2D Plane Stress Concentration Analysis
"""
import matplotlib.pyplot as plt
import numpy as np
import time
from ansys.mapdl.core import launch_mapdl
import pandas as pd
length = 0.4
width = 0.1
ratio = 0.3 # diameter/width
diameter = width * ratio
radius = diameter * 0.5
###############################################################################
# Batch Analysis
# ~~~~~~~~~~~~~~
# Function to relaunch PyMAPDL, compute the
# stress concentration for a variety of hole diameters.
def compute_stress_con(ratio):
"""Compute the stress concentration for plate with a hole loaded
with a uniaxial force.
"""
start = time.time()
mapdl = launch_mapdl()
mapdl_launch_time = time.time() - start
begin_time = time.time()
mapdl.clear("NOSTART")
mapdl.prep7()
mapdl.units("SI") # SI - International system (m, kg, s, K).
# define a PLANE183 element type with thickness
mapdl.et(1, "PLANE183", kop3=3)
mapdl.r(1, 0.001) # thickness of 0.001 meters)
# Define a material (nominal steel in SI)
mapdl.mp("EX", 1, 210e9) # Elastic moduli in Pa (kg/(m*s**2))
mapdl.mp("DENS", 1, 7800) # Density in kg/m3
mapdl.mp("NUXY", 1, 0.3) # Poisson's Ratio
mapdl.emodif("ALL", "MAT", 1)
# Geometry
# ~~~~~~~~
# Create a rectangular area with the hole in the middle
diameter = width * ratio
radius = diameter * 0.5
# create the rectangle
rect_anum = mapdl.blc4(width=length, height=width)
# create a circle in the middle of the rectangle
circ_anum = mapdl.cyl4(length / 2, width / 2, radius)
# Note how pyansys parses the output and returns the area numbers
# created by each command. This can be used to execute a boolean
# operation on these areas to cut the circle out of the rectangle.
plate_with_hole_anum = mapdl.asba(rect_anum, circ_anum)
# Meshing
# ~~~~~~~
# Mesh the plate using a higher density near the hole and a lower
# density for the remainder of the plate
mapdl.aclear("all")
# ensure there are at least 100 elements around the hole
hole_esize = np.pi * diameter / 100 # 0.0002
plate_esize = 0.01
# increased the density of the mesh at the center
mapdl.lsel("S", "LINE", vmin=5, vmax=8)
mapdl.lesize("ALL", hole_esize, kforc=1)
mapdl.lsel("ALL")
# Decrease the area mesh expansion. This ensures that the mesh
# remains fine nearby the hole
mapdl.mopt("EXPND", 0.7) # default 1
mapdl.esize(plate_esize)
mapdl.amesh(plate_with_hole_anum)
# Boundary Conditions
# ~~~~~~~~~~~~~~~~~~~
# Fix the left-hand side of the plate in the X direction
mapdl.nsel("S", "LOC", "X", 0)
mapdl.d("ALL", "UX")
# Fix a single node on the left-hand side of the plate in the Y direction
mapdl.nsel("R", "LOC", "Y", width / 2)
assert mapdl.mesh.n_node == 1
mapdl.d("ALL", "UY")
# Apply a force on the right-hand side of the plate. For this
# example, we select the right-hand side of the plate.
mapdl.nsel("S", "LOC", "X", length)
# Next, couple the DOF for these nodes
mapdl.cp(5, "UX", "ALL")
# Again, select a single node in this set and apply a force to it
mapdl.nsel("r", "loc", "y", width / 2)
mapdl.f("ALL", "FX", 1000)
# finally, be sure to select all nodes again to solve the entire solution
mapdl.allsel()
# Solve the Static Problem
# ~~~~~~~~~~~~~~~~~~~~~~~~
mapdl.run("/SOLU")
mapdl.antype("STATIC")
mapdl.solve()
# Post-Processing
# ~~~~~~~~~~~~~~~
# grab the stress from the result
result = mapdl.result
nnum, stress = result.principal_nodal_stress(0)
von_mises = stress[:, -1]
max_stress = np.nanmax(von_mises)
# compare to the "far field" stress by getting the mean value of the
# stress at the wall
mask = result.mesh.nodes[:, 0] == length
far_field_stress = np.nanmean(von_mises[mask])
# adjust by the cross sectional area at the hole
adj = width / (width - diameter)
stress_adj = far_field_stress * adj
model_time = time.time() - begin_time
mapdl.exit()
total_elapsed = time.time() - start
# finally, compute the stress concentration
return max_stress / stress_adj, mapdl_launch_time, model_time, total_elapsed
###############################################################################
# Run the batch and record the stress concentration
k_t_exp = []
times = {k:[] for k in ["MAPDL_Launch_Time", "Model_Time", "Total_Elapsed"]}
ratios = np.linspace(0.01, 0.5, 10)
print(" Ratio : Stress Concentration (K_t) : MAPDL Launch Time : Model Time : Total Elapsed")
for i,ratio in enumerate(ratios):
stress_con, mapdl_launch_time, model_time, total_elapsed = compute_stress_con(ratio)
print("%10.4f : %10.4f : %10.4f : %10.4f : %10.4f" % (ratio, stress_con, mapdl_launch_time, model_time, total_elapsed))
times["MAPDL_Launch_Time"].append(mapdl_launch_time)
times["Model_Time"].append(model_time)
times["Total_Elapsed"].append(total_elapsed)
k_t_exp.append(stress_con)
time_df = pd.DataFrame(times)
fig, ((ax0, ax1, ax2)) = plt.subplots(nrows=1, ncols=3)
len_data = len(time_df["MAPDL_Launch_Time"])
run_nb = [i for i in range(len_data)]
ax0.bar(run_nb,time_df["MAPDL_Launch_Time"])
ax0.set_title('MAPDL launch time')
ax1.bar(run_nb,time_df["Model_Time"])
ax1.set_title('Time for model run')
ax2.bar(run_nb,time_df["Total_Elapsed"])
ax2.set_title('Total elapsed time')
plt.show()
💻 Which Operating System are you using?
Windows
🐍 Which Python version are you using?
3.10
📝 PyMAPDL Report
PyMAPDL Software and Environment Report
Packages Requirements
Core packages
ansys.mapdl.core : 0.67.0 numpy : 1.26.2 platformdirs : 4.1.0 scipy : 1.11.4 grpc : Package not found ansys.api.mapdl.v0 : Package not found ansys.mapdl.reader : 0.53.0 google.protobuf : Package not found
Optional packages
matplotlib : 3.8.2 pyvista : 0.43.1 pyiges : 0.3.1 tqdm : 4.66.1
Ansys Installation
Version Location
192 C:\Program Files\ANSYS Inc\v192 195 C:\Program Files\ANSYS Inc\v195 202 C:\Program Files\ANSYS Inc\v202 212 C:\Program Files\ANSYS Inc\v212 221 C:\Program Files\ANSYS Inc\v221 222 C:\Program Files\ANSYS Inc\v222 231 C:\Program Files\ANSYS Inc\v231 232 C:\Program Files\ANSYS Inc\v232 241 C:\Program Files\ANSYS Inc\v241
Ansys Environment Variables
ANSYS192_DIR C:\Program Files\ANSYS Inc\v192\ANSYS ANSYS195_DIR C:\Program Files\ANSYS Inc\v195\ANSYS ANSYS202_DIR C:\Program Files\ANSYS Inc\v202\ANSYS ANSYS212_DIR C:\Program Files\ANSYS Inc\v212\ANSYS ANSYS221_DIR C:\Program Files\ANSYS Inc\v221\ANSYS ANSYS222_DIR C:\Program Files\ANSYS Inc\v222\ANSYS ANSYS231_DIR C:\Program Files\ANSYS Inc\v231\ANSYS ANSYS232_DIR C:\Program Files\ANSYS Inc\v232\ANSYS ANSYS241_DIR C:\Program Files\ANSYS Inc\v241\ANSYS ANSYSLIC_DIR C:\Program Files\ANSYS Inc\Shared Files\Licensing ANSYS_DPF_ACCEPT_LA Y ANSYS_SYSDIR winx64 ANSYS_SYSDIR32 win32 AWP_LOCALE192 en-us AWP_LOCALE195 en-us AWP_LOCALE202 en-us AWP_LOCALE212 en-us AWP_LOCALE221 en-us AWP_LOCALE222 en-us AWP_LOCALE231 en-us AWP_LOCALE232 en-us AWP_LOCALE241 en-us AWP_ROOT192 C:\Program Files\ANSYS Inc\v192 AWP_ROOT195 C:\Program Files\ANSYS Inc\v195 AWP_ROOT202 C:\Program Files\ANSYS Inc\v202 AWP_ROOT212 C:\Program Files\ANSYS Inc\v212 AWP_ROOT221 C:\Program Files\ANSYS Inc\v221 AWP_ROOT222 C:\Program Files\ANSYS Inc\v222 AWP_ROOT231 C:\Program Files\ANSYS Inc\v231 AWP_ROOT232 C:\Program Files\ANSYS Inc\v232 AWP_ROOT241 C:\Program Files\ANSYS Inc\v241 CADOE_LIBDIR192 C:\Program Files\ANSYS Inc\v192\CommonFiles\Language\en-us CADOE_LIBDIR195 C:\Program Files\ANSYS Inc\v195\CommonFiles\Language\en-us CADOE_LIBDIR202 C:\Program Files\ANSYS Inc\v202\CommonFiles\Language\en-us CADOE_LIBDIR212 C:\Program Files\ANSYS Inc\v212\CommonFiles\Language\en-us CADOE_LIBDIR221 C:\Program Files\ANSYS Inc\v221\CommonFiles\Language\en-us CADOE_LIBDIR222 C:\Program Files\ANSYS Inc\v222\CommonFiles\Language\en-us CADOE_LIBDIR231 C:\Program Files\ANSYS Inc\v231\CommonFiles\Language\en-us CADOE_LIBDIR232 C:\Program Files\ANSYS Inc\v232\CommonFiles\Language\en-us CADOE_LIBDIR241 C:\Program Files\ANSYS Inc\v241\CommonFiles\Language\en-us PYANSYS_PRIVATE_PYPI_PAT 65w37vhx6b2r7rweix54hxblqhrkm5u6aleiz76tyua53hqpafvq
📝 Installed packages
ansys-api-mapdl==0.5.1 ansys-api-platform-instancemanagement==1.0.0 ansys-mapdl-core==0.67.0 ansys-mapdl-reader==0.53.0 ansys-math-core==0.1.3 ansys-platform-instancemanagement==1.1.2 ansys-tools-path==0.4.0 appdirs==1.4.4 asttokens==2.4.1 certifi==2023.11.17 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 comm==0.2.0 contourpy==1.2.0 cycler==0.12.1 debugpy==1.8.0 decorator==5.1.1 exceptiongroup==1.2.0 executing==2.0.1 fonttools==4.46.0 geomdl==5.3.1 grpcio==1.60.0 idna==3.6 importlib-metadata==7.0.0 ipykernel==6.27.1 ipython==8.18.1 jedi==0.19.1 jupyter_client==8.6.0 jupyter_core==5.5.0 kiwisolver==1.4.5 matplotlib==3.8.2 matplotlib-inline==0.1.6 nest-asyncio==1.5.8 numpy==1.26.2 packaging==23.2 pandas==2.1.4 parso==0.8.3 Pillow==10.1.0 platformdirs==4.1.0 pooch==1.8.0 prompt-toolkit==3.0.43 protobuf==3.20.3 psutil==5.9.6 pure-eval==0.2.2 pyansys-tools-versioning==0.5.0 Pygments==2.17.2 pyiges==0.3.1 pyparsing==3.1.1 python-dateutil==2.8.2 pytz==2023.3.post1 pyvista==0.43.1 pywin32==306 pyzmq==25.1.2 requests==2.31.0 scipy==1.11.4 scooby==0.9.2 six==1.16.0 stack-data==0.6.3 tornado==6.4 tqdm==4.66.1 traitlets==5.14.0 tzdata==2023.3 urllib3==2.1.0 vtk==9.3.0 wcwidth==0.2.12 zipp==3.17.0
📝 Logger output file
No response
Another run, on same model, leads to different results:
This is very interesting. I would expect very similar times.
I would test adding some waiting time after the mapdl.exit
(of course do not count it towards the graph).
I'm assuming that the difference is because of the time to launch_mapdl
trying to find an available port, which might be still being used by the previous MAPDL instance. But I'm not 100% that is the reason.
Thanks for the suggestion @germa89 . I added time.sleep as follows:
There is now less difference for launch_mapdl, but all the values are now big :thinking:
You mean for launching MAPDL? ... I mean, launching could be reduced if you allocate less memory at the beginning:
mapdl = launch_mapdl(nproc=2, ram=100)
But I guess you will be transferring the time to the solving step. If the model needs more memory, it is going to increase its allocation (which takes time) or go slower during solving.
Yes, launch_mapdl() execution time seems to be:
- varying from a factor 1 to 4 for similar configuration and run
- be dependent on the rest of the code (like deleting files after solving and/or adding a time.sleep() after exiting to make sure port is released)
Thanks for the suggestion @germa89 . I added time.sleep as follows:
There is now less difference for launch_mapdl, but all the values are now big 🤔
I think this comment kind of justify what I thought. MAPDL takes quite a few seconds to start and be ready to connect to it.
I would say this is more an expected behaviour, and we cannot really fix it in PyMAPDL.
I'm closing the issue. Thank you for the feedback @pmaroneh !!
Hi @germa89 , I don't think this issue can be closed, as it is not solved nor the behavior explained. launch-mapdl() execution time:
- varies from a factor 1 to 4 for similar configuration and run
- is very dependent on the rest of the code - and can take (too) many seconds (note - the time spent in time.sleep was not counted in the above test, so if freeing the port had any positive impact it should have decreased the time to launch, not make it increase). Reopening again. @pthieffry FYI
@germa89 @pmaroneh I agree with Pernelle on not closing. The issue is not so much how much time a given session takes to start than the repeatability, all settings being the same. And changing memory allocation or number of cores is not the solution.
Hi @pmaroneh @pthieffry
I think I might be missing something from your comments. Probably this a #MondayIssue.
Regarding this graph:
(Assuming that the Y-axis is seconds)
There is now less difference for launch_mapdl, but all the values are now big 🤔
- Starting time: I think having a variance of 1-2 seconds in the MAPDL starting time (first graph), it is not too bad. Even if it is bad, it should be an issue to open in MAPDL TFS repo. I can ping @FredAns in case he wants to drop a comment or something.
- Time to run the model: We are talking about differences of half second. I think these differences are neglectable. It could be anything ... OS being busy writing in other processes, etc.
Regarding this:
Hi @germa89 , I don't think this issue can be closed, as it is not solved nor the behavior explained. launch-mapdl() execution time:
- varies from a factor 1 to 4 for similar configuration and run
- is very dependent on the rest of the code - and can take (too) many seconds (note - the time spent in time.sleep was not counted in the above test, so if freeing the port had any positive impact it should have decreased the time to launch, not make it increase). Reopening again. @pthieffry FYI
- varies from a factor 1 to 4 for similar configuration and run
- I do not see the 1-4 factor difference. Where? (I understand factor as 1-4x times, I dont see a 4 times variation). I guess you mean seconds? But then, where?
- is very dependent on the rest of the code - and can take (too) many seconds (note - the time spent in time.sleep was not counted in the above test, so if freeing the port had any positive impact it should have decreased the time to launch, not make it increase).
- What do you think it is taking many seconds? I mean, starting MAPDL is slow, definitely. But I don't think we can really trim that much. And "dependent on the rest of the code"??
By the way, I should mention that exiting MAPDL is not clear. It relies on "killing" the MAPDL process which is probably not very good way to proceed. That has been fixed in v241 though.
Hi @germa89 ! Let's only focus on time to execute launch_mapdl() command. Total run time of the model is not important.
- Factor 1 to 4: From 0.5s to 2.5s to excecute only launch_mapdl() (so it is actually 5 factor)
- Variability:
and
are two exact same run of the same code
- Dependent on rest of code:
and then:
when I added a time.sleep after code execution (and time in time.sleep is obvisouly not counted in the time to execute launch_mapdl). I do however agree that this is at the interface between PyMAPDL and MAPDL, maybe @FredAns can chime in. Thanks both!
I will try to run it locally today
Same issue, and sometimes it even leads to timeout error. I tested launching mapdl with LOG.setLevel("DEBUG") model, and it shows it takes more time for `launch_mapdl to find an available port when launching pymapdl the second time
An available workaround Releasing port 50052,50053,50054 and its process before launching pymapdl is useful.
# code for releaseing port(chatgpt)
import psutil
def find_and_terminate_process_by_port(port):
for process in psutil.process_iter(['pid', 'name', 'connections']):
try:
connections = process.info['connections']
for conn in connections:
if conn.laddr.port == port:
print(f"Found process {process.info['pid']} using port {port}. Terminating...")
process.terminate()
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
#The port you want to release
port_to_release = 50052
find_and_terminate_process_by_port(port_to_release)
Same issue, and sometimes it even leads to timeout error. I tested launching mapdl with LOG.setLevel("DEBUG") model, and it shows it takes more time for `launch_mapdl to find an available port when launching pymapdl the second time
This is very useful @js4561207. I was under the impression that it is the port which is not properly released after the process has died. There might be a way to "release" the port internally in MAPDL before exiting.
An available workaround Releasing port 50052,50053,50054 and its process before launching pymapdl is useful.
# code for releaseing port(chatgpt) import psutil def find_and_terminate_process_by_port(port): for process in psutil.process_iter(['pid', 'name', 'connections']): try: connections = process.info['connections'] for conn in connections: if conn.laddr.port == port: print(f"Found process {process.info['pid']} using port {port}. Terminating...") process.terminate() except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass #The port you want to release port_to_release = 50052 find_and_terminate_process_by_port(port_to_release)
Thank you for this. We do something very similar with the launch_mapdl stop
CLI: https://mapdl.docs.pyansys.com/version/dev/user_guide/cli.html#launch-mapdl-instances
It is only available at the moment if you are using PyMAPDL installed from github:
pip install git+https://github.com/ansys/pymapdl.git@main