From 48f58c0a05ba9b10d89fb2e55ac285e53b61c60e Mon Sep 17 00:00:00 2001 From: PProfizi <100710998+PProfizi@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:38:53 +0100 Subject: [PATCH] Add example cycles_to_failure from McMunich (#591) * Functioning cyclic_to_failure examples * Update style * Add test_examples::test_download_cycles_to_failure * Comments from Jenna Signed-off-by: paul.profizi * Update title of the example * Apply suggestions from code review * Remove section separation for a visually better example * Rework formatting of the explanation paragraph * Update examples/02-modal-harmonic/06-cycles_to_failure.py * Fix formatting of the description * Fix formatting of the description * Revert "Remove section separation for a visually better example" This reverts commit 52d1b1d443da2f904fdcc440b8df57145484ff33. * Reformat sections * Reformat * Reformat * Reformat * Reformat * Reformat * Apply suggestions from code review Signed-off-by: paul.profizi --- ansys/dpf/core/examples/downloads.py | 109 ++++++++++----- .../02-modal-harmonic/06-cycles_to_failure.py | 124 ++++++++++++++++++ tests/test_examples.py | 5 + 3 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 examples/02-modal-harmonic/06-cycles_to_failure.py diff --git a/ansys/dpf/core/examples/downloads.py b/ansys/dpf/core/examples/downloads.py index 0b67b5e115..3702937924 100644 --- a/ansys/dpf/core/examples/downloads.py +++ b/ansys/dpf/core/examples/downloads.py @@ -79,8 +79,8 @@ def download_transient_result(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download an example transient result file and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -118,8 +118,8 @@ def download_all_kinds_of_complexity(should_upload: bool = True, server=None , return_local_path=False) -> str: """Download an example static result and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -158,8 +158,8 @@ def download_all_kinds_of_complexity_modal(should_upload: bool = True, server=No return_local_path=False) -> str: """Download an example result file from a static modal analysis and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -197,8 +197,8 @@ def download_all_kinds_of_complexity_modal(should_upload: bool = True, server=No def download_pontoon(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download an example result file from a static modal analsys and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -236,8 +236,8 @@ def download_multi_harmonic_result(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download an example multi-harmonic result file and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -273,10 +273,10 @@ def download_multi_harmonic_result(should_upload: bool = True, server=None, def download_multi_stage_cyclic_result(should_upload: bool = True, server=None, return_local_path=False) -> str: - """Download an example multi stage result file and return the + """Download an example multi-stage result file and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -313,8 +313,8 @@ def download_multi_stage_cyclic_result(should_upload: bool = True, server=None, def download_sub_file(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download an example .sub result file containing matrices and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -352,8 +352,8 @@ def download_msup_files_to_dict(should_upload: bool = True, server=None, return_local_path=False) -> dict: """Download all the files necessary for a msup expansion and return the download paths available server side into a dictionary extension->path. - If the server is remote (or doesn't share the memory), the files are uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -397,8 +397,8 @@ def download_distributed_files(should_upload: bool = True, server=None, return_local_path=False) -> dict: """Download distributed rst files and return the download paths into a dictionary domain id->path. - If the server is remote (or doesn't share the memory), the files are uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -439,8 +439,8 @@ def download_distributed_files(should_upload: bool = True, server=None, def download_fluent_files(should_upload: bool = True, server=None, return_local_path=False) -> dict: """Download the cas and dat file of a fluent analysis and return the download paths into a dictionary extension->path. - If the server is remote (or doesn't share the memory), the files are uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -481,9 +481,9 @@ def download_fluent_files(should_upload: bool = True, server=None, return_local_ def download_extrapolation_3d_result(should_upload: bool = True, server=None, return_local_path=False) -> dict: """Download example static results of reference and integrated points - for extrapolation of 3d-element and return return the dictionary of 2 download paths. - If the server is remote (or doesn't share the memory), the files are uploaded or made available - server side. + for extrapolation of 3d-element and return the dictionary of 2 download paths. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -532,8 +532,8 @@ def download_extrapolation_2d_result(should_upload: bool = True, server=None, return_local_path=False) -> dict: """Download example static results of reference and integrated points for extrapolation of 2d-element and return the dictionary of 2 download paths. - If the server is remote (or doesn't share the memory), the files are uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -581,8 +581,8 @@ def download_extrapolation_2d_result(should_upload: bool = True, server=None, def download_hemisphere(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download an example result file from a static analysis and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -620,8 +620,8 @@ def download_example_asme_result(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download an example result file from a static analysis and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -657,8 +657,8 @@ def download_example_asme_result(should_upload: bool = True, server=None, def download_crankshaft(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download the result file of an example of a crankshaft under load and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -695,8 +695,8 @@ def download_crankshaft(should_upload: bool = True, server=None, return_local_pa def download_piston_rod(should_upload: bool = True, server=None, return_local_path=False) -> str: """Download the result file of an example of a piston rod under load and return the download path available server side. - If the server is remote (or doesn't share the memory), the file is uploaded or made available - server side. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. Examples files are downloaded to a persistent cache to avoid re-downloading the same file twice. @@ -846,3 +846,44 @@ def download_binout_glstat(should_upload: bool = True, server=None, return_local """ return _download_file("binout", "binout_glstat", should_upload, server, return_local_path) + + +def download_cycles_to_failure(should_upload: bool = True, + server=None, + return_local_path=False) -> str: + """Download an example result file from a cyclic analysis and + return the download path. + If the server is remote (or doesn't share memory), the file is uploaded or made available + on the server side. + + Examples files are downloaded to a persistent cache to avoid + re-downloading the same file twice. + + Parameters + ---------- + should_upload : bool, optional (default True) + Whether the file should be uploaded server side when the server is remote. + server : server.DPFServer, optional + Server with channel connected to the remote or local instance. When + ``None``, attempts to use the global server. + return_local_path: bool, optional + If ``True``, the local path is returned as is, without uploading, nor searching + for mounted volumes. + + Returns + ------- + str + Path to the example file. + + Examples + -------- + Download an example result file and return the path of the file + + >>> from ansys.dpf.core import examples + >>> path = examples.download_cycles_to_failure() + >>> path + 'C:/Users/user/AppData/local/temp/cycles_to_failure.rst' + + """ + return _download_file("cyclic", "cyclic_to_failure.rst", + should_upload, server, return_local_path) diff --git a/examples/02-modal-harmonic/06-cycles_to_failure.py b/examples/02-modal-harmonic/06-cycles_to_failure.py new file mode 100644 index 0000000000..58f404a43b --- /dev/null +++ b/examples/02-modal-harmonic/06-cycles_to_failure.py @@ -0,0 +1,124 @@ +""" +.. _ref_cycles_to_failure: + +Calculate the number of cycles to fatigue failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows how to generate and use a result file to calculate the +cycles to failure result for a simple model. + +Material data is manually imported, Structural Steel from Ansys Mechanical: + +- Youngs Modulus (youngsSteel) +- Poisson's Ratio (prxySteel) +- SN curve (sn_data) + +The first step is to generate a simple model with high stress and save the +results .rst file locally to myDir (default is "C:\\\\temp"). +For this, we provide a short pyMAPDL script. + +.. line-block:: + The second step uses PyDPF-Core to generate the cycles to failure result: + The locally saved .rst file is imported and plotted. + Then the von Mises stress is generated and plotted with DPF operators. + The NumPy python package is then used to interpolate the cycles to failure values. + The nodal von Mises equivalent stress value is used in the interpolation. + (Note that the cycles to failure data must be manipulated to use NumPy interpolation) + An empty field is then created and filled with the resulting cycles to failure values. + The cycles to failure result is finally plotted. + +The cycles to failure result is the (interpolated) negative of the stress result. +The higher the stress result, the lower the number of cycles to failure. +""" + +from ansys.dpf import core as dpf +from ansys.dpf.core import examples +import numpy as np + +############################################################################### +# The first step is to generate a simple model with high stress + +# # Material parameters from Ansys Mechanical Structural Steel +youngsSteel = 200e9 +prxySteel = 0.3 +sn_data = np.empty((11, 2)) # initialize empty np matrix +sn_data[:, 0] = [10, 20, 50, 100, 200, 2000, 10000, 20000, 1e5, 2e5, 1e6] +sn_data[:, 1] = [3.999e9, 2.8327e9, 1.896e9, 1.413e9, 1.069e9, 4.41e8, 2.62e8, 2.14e8, 1.38e8, + 1.14e8, 8.62e7] + +############################################################################### +# The .rst file used is already available, but can be obtained using the short pyMAPDL code below: + +# # ### Launch pymapdl to generate rst file in myDir +# from ansys.mapdl.core import launch_mapdl +# import os +# +# +# mapdl = launch_mapdl() +# mapdl.prep7() +# # Model +# mapdl.cylind(0.5, 0, 10, 0) +# mapdl.mp("EX", 1, youngsSteel) +# mapdl.mp("PRXY", 1, prxySteel) +# mapdl.mshape(key=1, dimension='3d') +# mapdl.et(1, "SOLID186") +# mapdl.esize(0.3) +# mapdl.vmesh('ALL') +# +# # #### Boundary Conditions: fixed constraint +# mapdl.nsel(type_='S', item='LOC', comp='Z', vmin=0) +# mapdl.d("all", "all") +# mapdl.nsel(type_='S', item='LOC', comp='Z', vmin=10) +# nnodes = mapdl.get("NumNodes", "NODE", 0, "COUNT") +# mapdl.f(node="ALL", lab="fy", value=-13e6 / nnodes) +# mapdl.allsel() +# +# # #### Solve +# mapdl.run("/SOLU") +# sol_output = mapdl.solve() +# rst = os.path.join(mapdl.directory, 'file.rst') +# mapdl.exit() +# print('apdl model solved.') + +############################################################################### +# PyDPF-Core is then used to post-process the .rst file to estimate the cycles to failure. + +# Comment the following line if solving the MAPDL problem first. +rst = examples.download_cycles_to_failure() + +# Import the result as a DPF Model object. +model = dpf.Model(rst) +print(model) + +############################################################################### +# Get the von mises equivalent stress, requires an operator. +s_eqv_op = dpf.operators.result.stress_von_mises(data_sources=model) +vm_stress_fc = s_eqv_op.eval() +vm_stress_field = vm_stress_fc[0] +vm_stress_field.plot(text="VM stress field") + +############################################################################### +# Use NumPy to interpolate the results. + +# Inverse the sn_data +x_values = sn_data[:, 1][::-1] # the x values are the stress ranges in ascending order +y_values = sn_data[:, 0][::-1] # y values are inverted cycles to failure + +# Interpolate cycles to failure for the VM values +cycles_to_failure = np.interp(vm_stress_field.data, x_values, y_values) + +############################################################################### +# Generate a cycles_to_failure DPF Field + +# Create an empty field +cycles_to_failure_field = dpf.Field(nentities=len(vm_stress_field.scoping), + nature=dpf.natures.scalar, + location=dpf.locations.nodal) +# Populate the field +cycles_to_failure_field.scoping = vm_stress_field.scoping +cycles_to_failure_field.meshed_region = vm_stress_field.meshed_region +cycles_to_failure_field.data = cycles_to_failure + +# Plot the field +sargs = dict(title="cycles", fmt="%.2e") +cycles_to_failure_field.plot(text="Cycles to failure", scalar_bar_args=sargs) diff --git a/tests/test_examples.py b/tests/test_examples.py index df6714e1ff..8f194878a0 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -34,6 +34,11 @@ def test_download_piston_rod(): assert isinstance(Model(path), Model) +def test_download_cycles_to_failure(): + path = examples.download_cycles_to_failure() + assert isinstance(Model(path), Model) + + list_examples = [ "simple_bar", "static_rst",