Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Very variable time for launch_mapdl() #2564

Open
4 tasks done
pmaroneh opened this issue Dec 14, 2023 · 14 comments
Open
4 tasks done

Very variable time for launch_mapdl() #2564

pmaroneh opened this issue Dec 14, 2023 · 14 comments

Comments

@pmaroneh
Copy link
Contributor

🤓 Before submitting the issue

🔍 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:
image
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

@pmaroneh
Copy link
Contributor Author

Another run, on same model, leads to different results:
image

@germa89
Copy link
Collaborator

germa89 commented Dec 14, 2023

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.

@pmaroneh
Copy link
Contributor Author

Thanks for the suggestion @germa89 . I added time.sleep as follows:
image
There is now less difference for launch_mapdl, but all the values are now big 🤔
image

@germa89
Copy link
Collaborator

germa89 commented Dec 14, 2023

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.

@pmaroneh
Copy link
Contributor Author

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)

@germa89
Copy link
Collaborator

germa89 commented Dec 15, 2023

Thanks for the suggestion @germa89 . I added time.sleep as follows: image There is now less difference for launch_mapdl, but all the values are now big 🤔 image

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 !!

@germa89 germa89 closed this as completed Dec 15, 2023
@pmaroneh
Copy link
Contributor Author

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

@pmaroneh pmaroneh reopened this Dec 18, 2023
@pthieffry
Copy link

@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.

@germa89
Copy link
Collaborator

germa89 commented Dec 18, 2023

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 🤔 image

  • 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.

@pmaroneh
Copy link
Contributor Author

pmaroneh commented Dec 18, 2023

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)
    image
  • Variability:
    image
    and
    image
    are two exact same run of the same code
  • Dependent on rest of code:
    image
    and then:
    image
    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!

@germa89
Copy link
Collaborator

germa89 commented Dec 18, 2023

I will try to run it locally today

@js4561207
Copy link

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

debuglog.txt

@js4561207
Copy link

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)

@germa89
Copy link
Collaborator

germa89 commented Jan 29, 2024

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

debuglog.txt

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants