From 42f123eb5802538827e42d17d611e9a7df7e3a21 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 09:41:54 +0100 Subject: [PATCH 01/15] Update installation instructions Signed-off-by: Johannes Mueller --- INSTALLATION.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/INSTALLATION.md b/INSTALLATION.md index bbdcc21f..6352fc3c 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -15,16 +15,17 @@ You need a python installation e.g. a virtual environment with `pip` a recent (brand new ones might not work) python versions installed. There are several ways to achieve that. -#### Using anaconda +#### Using miniconda or anaconda -Install anaconda or miniconda [http://anaconda.com] on your computer and create -a virtual environment with the package `pip` installed. See the [conda +Install [miniconda](https://conda.io/miniconda.html) or +[anaconda](http://anaconda.com) on your computer and create a virtual +environment with python installed. See the [conda documentation](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) on how to do that. The newly created environment must be activated. The following command lines should do it ``` -conda create -n pylife-env python=3.9 pip --yes +conda create -n pylife-env python=3.11 --yes conda activate pylife-env ``` @@ -50,7 +51,6 @@ That installs pyLife with all the dependencies to use pyLife in python programs. You might want to install some further packages like `jupyter` in order to work with jupyter notebooks. - There is no conda package as of now, unfortunately. @@ -75,7 +75,7 @@ Create an environment – usually a good idea to use a prefixed environment in your pyLife working directory and activate it. ``` -conda create -p .venv python=3.9 pip --yes +conda create -p .venv python=3.11 pip --yes conda activate ./.venv ``` From 468f30c80fb8a30aeb8076214b6ceb415cfc5bc4 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 12:19:09 +0100 Subject: [PATCH 02/15] Uplift numpy version pin for odbclient Signed-off-by: Johannes Mueller --- tools/odbclient/setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odbclient/setup.cfg b/tools/odbclient/setup.cfg index 3e41bd05..728ac6f1 100644 --- a/tools/odbclient/setup.cfg +++ b/tools/odbclient/setup.cfg @@ -42,7 +42,7 @@ package_dir = install_requires = importlib-metadata; python_version<"3.8" pandas - numpy==1.23.5 + numpy [options.packages.find] where = src From 89ca1fcff69f73e9eef34b7d2918ec41858046a8 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 12:19:23 +0100 Subject: [PATCH 03/15] Restrict pymc option to numpy<1.26 https://github.com/pymc-devs/pymc/issues/7043#issuecomment-1837141888 Signed-off-by: Johannes Mueller --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 61b98f61..4c376fb2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,11 +98,11 @@ analysis = tsfresh = tsfresh - numpy>=1.23 pymc = pymc bambi + numpy<1.26 # https://github.com/pymc-devs/pymc/issues/7043#issuecomment-1837141888 all = %(tsfresh)s %(pymc)s From 99de6b6351197a05afe41d1ab88515aa2cfe1baf Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 15:06:29 +0100 Subject: [PATCH 04/15] Fix some docs build details Signed-off-by: Johannes Mueller --- docs/3rd-party-licenses.md | 5 ----- docs/3rd-party-licenses.txt | 5 +++++ docs/conf.py | 8 +++++++- 3 files changed, 12 insertions(+), 6 deletions(-) delete mode 100644 docs/3rd-party-licenses.md create mode 100644 docs/3rd-party-licenses.txt diff --git a/docs/3rd-party-licenses.md b/docs/3rd-party-licenses.md deleted file mode 100644 index 5a39b9ae..00000000 --- a/docs/3rd-party-licenses.md +++ /dev/null @@ -1,5 +0,0 @@ -# 3rd Party Licenses - -```{literalinclude} ../3rd-party-licenses.txt -:language: text -``` diff --git a/docs/3rd-party-licenses.txt b/docs/3rd-party-licenses.txt new file mode 100644 index 00000000..2cb0ad01 --- /dev/null +++ b/docs/3rd-party-licenses.txt @@ -0,0 +1,5 @@ +3rd Party Licenses +================== + +.. literalinclude:: ../3rd-party-licenses.txt + :language: text diff --git a/docs/conf.py b/docs/conf.py index fa63ceed..686f15b1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,8 @@ vdisplay = Xvfb() vdisplay.start() +os.environ['PYDEVD_DISABLE_FILE_VALIDATION'] = "1" + ipython_dir = os.path.join(__projectdir__, "_build", "ipythondir") os.environ['IPYTHONDIR'] = ipython_dir @@ -128,7 +130,11 @@ templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = [".rst", ".md", ".txt", ".ipynb"] +source_suffix = { + '.rst': 'restructuredtext', + '.txt': 'restructuredtext', + '.md': 'markdown', +} # The encoding of source files. # source_encoding = 'utf-8-sig' From 7a7305a3aceb3f3c6f3a167af3809be178d8cde6 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 15:15:13 +0100 Subject: [PATCH 05/15] Inject code to set pyvista backend into notebooks during docs build https://github.com/pyvista/pyvista/discussions/4809 Signed-off-by: Johannes Mueller --- docs/conf.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 686f15b1..ce3306e7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,6 +49,29 @@ ep.preprocess(nb, {'metadata': {'path': os.path.dirname(fn)}}) +# Inject code to set pyvista jupyter backend into relevant notebooks +# +import nbsphinx +original_from_notebook_node = nbsphinx.Exporter.from_notebook_node + +from nbconvert.preprocessors import Preprocessor + +def from_notebook_node(self, nb, resources, **kwargs): + + class InjectPyvistaBackendPreprocessor(Preprocessor): + def preprocess(self, nb, resources): + for index, cell in enumerate(nb.cells): + if cell['cell_type'] == 'code' and 'import pyvista as pv' in cell['source'] : + cell['source'] += "\npv.set_jupyter_backend('html')\n" + break + return nb, resources + + nb, resources = InjectPyvistaBackendPreprocessor().preprocess(nb, resources=resources) + return original_from_notebook_node(self, nb, resources, **kwargs) + +nbsphinx.Exporter.from_notebook_node = from_notebook_node + + # Don't try to use C-code for the pytensor stuff when building the docs. # Otherwise the demo notebooks fail on readthedocs os.environ['PYTHONWARNINGS'] = 'ignore' From a589c752d104c3b638ad41c368b17a256d9bdc4b Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 16:37:11 +0100 Subject: [PATCH 06/15] Move or replicate the tutorial demo notbooks to tutorial docs Mitigates #53 Signed-off-by: Johannes Mueller --- docs/tutorials.rst | 10 +++++----- docs/tutorials/fkm_nonlinear.nblink | 3 +++ docs/tutorials/fkm_nonlinear_full.nblink | 3 +++ docs/{demos => tutorials}/load_collective.nblink | 0 docs/{demos => tutorials}/stress_strength.nblink | 0 docs/{demos => tutorials}/woehler_curve.nblink | 0 6 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 docs/tutorials/fkm_nonlinear.nblink create mode 100644 docs/tutorials/fkm_nonlinear_full.nblink rename docs/{demos => tutorials}/load_collective.nblink (100%) rename docs/{demos => tutorials}/stress_strength.nblink (100%) rename docs/{demos => tutorials}/woehler_curve.nblink (100%) diff --git a/docs/tutorials.rst b/docs/tutorials.rst index b05febde..e714d81a 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -14,8 +14,8 @@ computer, you can use `MyBinder .. toctree:: :maxdepth: 1 - demos/woehler_curve.nblink - demos/load_collective.nblink - demos/stress_strength.nblink - demos/fkm_nonlinear.nblink - demos/fkm_nonlinear_full.nblink + tutorials/woehler_curve.nblink + tutorials/load_collective.nblink + tutorials/stress_strength.nblink + tutorials/fkm_nonlinear.nblink + tutorials/fkm_nonlinear_full.nblink diff --git a/docs/tutorials/fkm_nonlinear.nblink b/docs/tutorials/fkm_nonlinear.nblink new file mode 100644 index 00000000..0148abcb --- /dev/null +++ b/docs/tutorials/fkm_nonlinear.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../demos/fkm_nonlinear/fkm_nonlinear.ipynb" +} diff --git a/docs/tutorials/fkm_nonlinear_full.nblink b/docs/tutorials/fkm_nonlinear_full.nblink new file mode 100644 index 00000000..c2110a28 --- /dev/null +++ b/docs/tutorials/fkm_nonlinear_full.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../demos/fkm_nonlinear/fkm_nonlinear_full.ipynb" +} diff --git a/docs/demos/load_collective.nblink b/docs/tutorials/load_collective.nblink similarity index 100% rename from docs/demos/load_collective.nblink rename to docs/tutorials/load_collective.nblink diff --git a/docs/demos/stress_strength.nblink b/docs/tutorials/stress_strength.nblink similarity index 100% rename from docs/demos/stress_strength.nblink rename to docs/tutorials/stress_strength.nblink diff --git a/docs/demos/woehler_curve.nblink b/docs/tutorials/woehler_curve.nblink similarity index 100% rename from docs/demos/woehler_curve.nblink rename to docs/tutorials/woehler_curve.nblink From 8c71cc2500cb20249ba8bc8a72fcfb641b77cfa1 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 16:38:29 +0100 Subject: [PATCH 07/15] Use nblink extra_media for data files instead of symlinks Gets rid of a lot of warnings during docs build. Signed-off-by: Johannes Mueller --- docs/demos/data | 1 - docs/demos/import_mesh_ansys.nblink | 3 ++- docs/demos/import_mesh_vmap.nblink | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) delete mode 120000 docs/demos/data diff --git a/docs/demos/data b/docs/demos/data deleted file mode 120000 index c8d8174b..00000000 --- a/docs/demos/data +++ /dev/null @@ -1 +0,0 @@ -../../demos/data/ \ No newline at end of file diff --git a/docs/demos/import_mesh_ansys.nblink b/docs/demos/import_mesh_ansys.nblink index f3ab923b..d03adfc7 100644 --- a/docs/demos/import_mesh_ansys.nblink +++ b/docs/demos/import_mesh_ansys.nblink @@ -1,3 +1,4 @@ { - "path": "../../demos/import_mesh_ansys.ipynb" + "path": "../../demos/import_mesh_ansys.ipynb", + "extra-media": ["../../demos/data"] } diff --git a/docs/demos/import_mesh_vmap.nblink b/docs/demos/import_mesh_vmap.nblink index 253f90a5..f1a5eb23 100644 --- a/docs/demos/import_mesh_vmap.nblink +++ b/docs/demos/import_mesh_vmap.nblink @@ -1,3 +1,4 @@ { - "path": "../../demos/import_mesh_vmap.ipynb" + "path": "../../demos/import_mesh_vmap.ipynb", + "extra-media": ["../../demos/data/"] } From 5d684933ef7f225c5f9682cd93ea1e993e1281b5 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 16:59:13 +0100 Subject: [PATCH 08/15] Export Gradient3D and Surface3D in mesh/__init__.py Signed-off-by: Johannes Mueller --- src/pylife/mesh/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pylife/mesh/__init__.py b/src/pylife/mesh/__init__.py index 0afffa35..9eb8a6b9 100644 --- a/src/pylife/mesh/__init__.py +++ b/src/pylife/mesh/__init__.py @@ -18,14 +18,17 @@ __maintainer__ = __author__ from .meshsignal import PlainMesh, Mesh -from .gradient import Gradient +from .gradient import Gradient, Gradient3D from .hotspot import HotSpot from .meshmapping import Meshmapper +from .surface import Surface3D __all__ = [ 'PlainMesh', 'Mesh', 'Gradient', + 'Gradient3D', 'HotSpot', - 'Meshmapper' + 'Meshmapper', + 'Surface3D' ] From 52bdcc9051d3baa4f41afdf7f4e4759c298a0793 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 16:59:49 +0100 Subject: [PATCH 09/15] Use stress-strength.rst rather than notebook in tutorials Signed-off-by: Johannes Mueller --- docs/tutorials.rst | 2 +- docs/tutorials/stress_strength.nblink | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 docs/tutorials/stress_strength.nblink diff --git a/docs/tutorials.rst b/docs/tutorials.rst index e714d81a..fda56de8 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -16,6 +16,6 @@ computer, you can use `MyBinder tutorials/woehler_curve.nblink tutorials/load_collective.nblink - tutorials/stress_strength.nblink + tutorials/stress-strength.rst tutorials/fkm_nonlinear.nblink tutorials/fkm_nonlinear_full.nblink diff --git a/docs/tutorials/stress_strength.nblink b/docs/tutorials/stress_strength.nblink deleted file mode 100644 index 04b9129b..00000000 --- a/docs/tutorials/stress_strength.nblink +++ /dev/null @@ -1,4 +0,0 @@ -{ - "path": "../../demos/stress_strength.ipynb", - "extra-media": ["../../demos/plate_with_hole.vmap"] -} From 1d72dbeca1079fa23e5497da5972070bab559501 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 17:10:25 +0100 Subject: [PATCH 10/15] Fix indentation and whitespace cleanup Signed-off-by: Johannes Mueller --- src/pylife/strength/fkm_load_distribution.py | 184 +++++++++---------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/src/pylife/strength/fkm_load_distribution.py b/src/pylife/strength/fkm_load_distribution.py index 11cfb180..3071cf7d 100644 --- a/src/pylife/strength/fkm_load_distribution.py +++ b/src/pylife/strength/fkm_load_distribution.py @@ -20,7 +20,7 @@ Given a pandas Series of load values, return a scaled version where the safety has been incorporated. The series is scaled by a constant value :math:`\gamma_L`, which models the distribution of the load, the severity of the failure (modeled by :math:`P_A`) and the considered load probability of either - :math:`P_L=2.5 \%` or :math:`P_L=50 \%`. +:math:`P_L=2.5 \%` or :math:`P_L=50 \%`. The FKM nonlinear guideline defines three possible methods to consider the statistical distribution of the load: @@ -28,7 +28,7 @@ * a logarithmic-normal distribution with given standard deviation $LSD_s$ * an unknown distribution, use the constant factor :math:`\gamma_L=1.1` for $P_L = 2.5\%$ -For these three methods, there exist the three accessors `fkm_safety_normal_from_stddev`, `fkm_safety_lognormal_from_stddev`, +For these three methods, there exist the three accessors `fkm_safety_normal_from_stddev`, `fkm_safety_lognormal_from_stddev`, and `fkm_safety_blanket`. The resulting scaling factor can be retrieved with ``.gamma_L(input_parameters)``, the scaled load series can be obtained @@ -38,7 +38,7 @@ -------- Assuming ``load_sequence`` is a pandas Series of load values and ``input_parameters`` is a series containing the required parameters, apply the three possible scaling methods as follows: - + >>> # uses input_parameters.s_L, input_parameters.P_L, input_parameters.P_A >>> scaled_load_sequence = load_sequence.fkm_safety_normal_from_stddev.scaled_load_sequence(input_parameters) @@ -60,34 +60,34 @@ @pd.api.extensions.register_dataframe_accessor("fkm_load_sequence") @pd.api.extensions.register_series_accessor("fkm_load_sequence") class FKMLoadSequence(PylifeSignal): - '''Base class used by the safety scaling method. It is used to compute the beta parameter + '''Base class used by the safety scaling method. It is used to compute the beta parameter and to scale the load sequence by a constant gamma_L. - + This class can be used from user code to scale a load sequence, potentially on a mesh with other fields set for every node. In such a case, the other fields are not modified. - + Example ------- .. jupyter-execute:: - - + + import pandas as pd import pylife.strength.fkm_load_distribution - + # create an example load sequence with stress (S_v) and an arbitrary other column (col2) mesh = pd.DataFrame( - index=pd.MultiIndex.from_product([range(2), range(4)], names=["load_step", "node_id"]), + index=pd.MultiIndex.from_product([range(2), range(4)], names=["load_step", "node_id"]), data={ "S_v": [10, 20, -10, -20, 30, 60, 40, 80], "col2": [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] }) print(mesh) - + # scale the load sequence by the factor 2, note that col2 is not scaled mesh.fkm_load_sequence.scaled_by_constant(2) ''' - + def scaled_by_constant(self, gamma_L): """ Scales the load sequence by the given constant gamma_L. This method basically computes @@ -96,7 +96,7 @@ def scaled_by_constant(self, gamma_L): In the case of a Series or only one column, it simply scales all values by the factor gamma_L. In the case of a DataFrame with multiple columns, it only scales the first column by the factor gamma_L and keeps the other columns unchanged. - + Returns a scaled copy of the data. Parameters @@ -108,25 +108,25 @@ def scaled_by_constant(self, gamma_L): ------- The scaled load sequence. """ - + # `self._obj` can be either a pd.Series or a pd.DataFrame. A gradient can only be included if we have a pd.DataFrame if isinstance(self._obj, pd.DataFrame): - + # if the number of columns in the DataFrame is at least two (for the stress and the gradient) if len(self._obj.columns) >= 2: - + # only scale the first column result = self._obj.copy() result.iloc[:,0] = result.iloc[:,0] * gamma_L return result - + return self._obj * gamma_L - + def maximum_absolute_load(self, max_load_independently_for_nodes=False): """ Get the maximum absolute load over all nodes and load steps. - - This is implemented for pd.Series (where the index is just the index of the load step), + + This is implemented for pd.Series (where the index is just the index of the load step), pd.DataFrame with one column (where the index is a MultiIndex of load_step and node_id), and pd.DataFrame with multiple columns with the load is given in the first column. @@ -145,11 +145,11 @@ def maximum_absolute_load(self, max_load_independently_for_nodes=False): The maximum absolute load. """ - + # if the load sequence is a pd.Series if isinstance(self._obj, pd.Series): L_max = max(abs(self._obj)) - + # if the load sequence is a pd.DataFrame else: # if we have a multi-indexed DataFrame with (load_step, node_id) @@ -158,31 +158,31 @@ def maximum_absolute_load(self, max_load_independently_for_nodes=False): else: levels = list(range(self._obj.index.nlevels - 1)) # all but the last level L_max = self._obj.abs().groupby(level=levels).max() - + # if L_max is still a DataFrame, take the maximum if isinstance(L_max, pd.DataFrame): - + # if there are multiple columns, select the first one if len(L_max.columns) > 1: L_max = L_max.iloc[:,0] - + if max_load_independently_for_nodes: return L_max - + # take maximum over all load steps L_max = L_max.max() - + if isinstance(L_max, pd.DataFrame) or isinstance(L_max, pd.Series): L_max = L_max.squeeze() - + return float(L_max) - + def _validate(self): if len(self._obj) == 0: raise AttributeError("Load series is empty.") - + def _validate_parameters(self, input_parameters, required_parameters): - + for required_parameter in required_parameters: if required_parameter not in input_parameters: raise ValueError(f"Given parameters have to include \"{required_parameter}\".") @@ -212,7 +212,7 @@ def _get_beta(self, input_parameters): The value of the beta parameter. """ - + # list of predefined P_A and beta values P_A_beta_list = [(1e-7, 5.20), (1e-6, 4.75), (1e-5, 4.27), (7.2e-5, 3.8), (1e-3, 3.09), (2.3e-1, 0.739), (0.5, 0)] @@ -220,45 +220,45 @@ def _get_beta(self, input_parameters): for P_A, beta in P_A_beta_list: if np.isclose(input_parameters.P_A, P_A): return beta - + # raise error if the given P_A value is not among the known ones P_A_list = [str(P_A) for P_A,_ in P_A_beta_list] raise ValueError(f"P_A={input_parameters.P_A} has to be one of "+"{"+", ".join(P_A_list)+"}.") - + @pd.api.extensions.register_dataframe_accessor("fkm_safety_normal_from_stddev") @pd.api.extensions.register_series_accessor("fkm_safety_normal_from_stddev") class FKMLoadDistributionNormal(FKMLoadSequence): - r'''Series accessor to get a scaled up load series, i.e., a list of load values with included load safety, - as used in FKM nonlinear lifetime assessments. - + r'''Series accessor to get a scaled up load series, i.e., a list of load values with included load safety, + as used in FKM nonlinear lifetime assessments. + The loads are assumed to follow a **normal distribution** with standard deviation :math:`s_L`. To incorporate safety, reduce the values of the load series from :math:`P_L = 50\%` up to the given load probability :math:`P_L` and the given failure probability :math:`P_A`. - + For more information, see 2.3.2.1 of the FKM nonlinear guideline. See also -------- :class:`AbstractFKMLoadDistribution`: accesses meshes with connectivity information - ''' + ''' def gamma_L(self, input_parameters): - r"""Compute the scaling factor :math:`\gamma_L=(L_\text{max} + \alpha_L) / L_\text{max}`. - Note that for load sequences on multiple nodes (i.e. on a full mesh), :math:`L_\text{max}` + r"""Compute the scaling factor :math:`\gamma_L=(L_\text{max} + \alpha_L) / L_\text{max}`. + Note that for load sequences on multiple nodes (i.e. on a full mesh), :math:`L_\text{max}` is the maximum load over all nodes and load steps, not different for different nodes. Parameters ---------- input_parameters : pandas Series The parameters to specify the upscaling method. - + * ``input_parameters.s_L``: standard deviation of the normal distribution * ``input_parameters.P_L``: probability in [%] of the load for which to do the assessment, one of {2.5, 50} * ``input_parameters.P_A``: probability in [%], one of {1e-7, 1e-6, 1e-5, 7.2e-5, 1e-3, 2.3e-1, 0.5} (de: Ausfallwahrscheinlichkeit) - * ``input_parameters.max_load_independently_for_nodes``: optional, whether the scaling should be performed + * ``input_parameters.max_load_independently_for_nodes``: optional, whether the scaling should be performed independently at every node (True), or uniformly over all nodes (False). The default value is False. Raises @@ -275,33 +275,33 @@ def gamma_L(self, input_parameters): self._validate_parameters(input_parameters, required_parameters=["P_L", "s_L", "P_A"]) beta = self._get_beta(input_parameters) - + # eq. 2.3-4 if np.isclose(input_parameters.P_L, 2.5): alpha_L = (0.7 * beta - 2) * input_parameters.s_L else: alpha_L = 0.7 * beta * input_parameters.s_L - + # eq. 2.3-5 if "max_load_independently_for_nodes" not in input_parameters: input_parameters["max_load_independently_for_nodes"] = False - - L_max = self.maximum_absolute_load(input_parameters.max_load_independently_for_nodes) - + + L_max = self.maximum_absolute_load(input_parameters.max_load_independently_for_nodes) + gamma_L = (L_max + alpha_L) / L_max - + return gamma_L - + def scaled_load_sequence(self, input_parameters): r"""The scaled load sequence according to the given parameters. - + The following parameters are used: s_L, P_L, P_A. - + Parameters ---------- input_parameters : pandas Series The parameters to specify the upscaling method. - + * ``input_parameters.s_L``: standard deviation of the normal distribution * ``input_parameters.P_L``: probability in [%] of the load for which to do the assessment, one of {2.5, 50} * ``input_parameters.P_A``: probability in [%], one of {1e-7, 1e-6, 1e-5, 7.2e-5, 1e-3, 2.3e-1, 0.5} @@ -315,33 +315,33 @@ def scaled_load_sequence(self, input_parameters): Returns ------- pandas Series - The input series where all values have been scaled by :math:`\gamma_L`, + The input series where all values have been scaled by :math:`\gamma_L`, see 2.3.2.1 of the FKM nonlinear guideline. """ gamma_L = self.gamma_L(input_parameters) - + return self.scaled_by_constant(gamma_L) - + @pd.api.extensions.register_dataframe_accessor("fkm_safety_lognormal_from_stddev") @pd.api.extensions.register_series_accessor("fkm_safety_lognormal_from_stddev") class FKMLoadDistributionLognormal(FKMLoadSequence): - r'''Series accessor to get a scaled up load series, i.e., a list of load values with included load safety, - as used in FKM nonlinear lifetime assessments. - + r'''Series accessor to get a scaled up load series, i.e., a list of load values with included load safety, + as used in FKM nonlinear lifetime assessments. + The loads are assumed to follow a **lognormal distribution** with standard deviation :math:`LSD_s`. To incorporate safety, reduce the values of the load series from :math:`P_L = 50\%` up to the given load probability :math:`P_L` and the given failure probability :math:`P_A`. - + For more information, see 2.3.2.2 of the FKM nonlinear guideline. See also -------- :class:`AbstractFKMLoadDistribution`: accesses meshes with connectivity information - ''' - + ''' + def gamma_L(self, input_parameters): r"""Compute the scaling factor :math:`\gamma_L`. @@ -349,7 +349,7 @@ def gamma_L(self, input_parameters): ---------- input_parameters : pandas Series The parameters to specify the upscaling method. - + * ``input_parameters.LSD_s``: standard deviation of the lognormal distribution * ``input_parameters.P_L``: probability in [%] of the load for which to do the assessment, one of {2.5, 50} * ``input_parameters.P_A``: probability in [%], one of {1e-7, 1e-6, 1e-5, 7.2e-5, 1e-3, 2.3e-1, 0.5} @@ -369,28 +369,28 @@ def gamma_L(self, input_parameters): self._validate_parameters(input_parameters, required_parameters=["P_L", "LSD_s", "P_A"]) beta = self._get_beta(input_parameters) - + # eq. 2.3-6 if np.isclose(input_parameters.P_L, 2.5): alpha_LSD = (0.7 * beta - 2) * input_parameters.LSD_s else: alpha_LSD = 0.7 * beta * input_parameters.LSD_s - + # eq. 2.3-7 gamma_L = max(1, 10 ** alpha_LSD) - + return gamma_L - + def scaled_load_sequence(self, input_parameters): r"""The scaled load sequence according to the given parameters. - + The following parameters are used: LSD_s, P_L, P_A. - + Parameters ---------- input_parameters : pandas Series The parameters to specify the upscaling method. - + * ``input_parameters.LSD_s``: standard deviation of the lognormal distribution * ``input_parameters.P_L``: probability in [%] of the load for which to do the assessment, one of {2.5, 50} * ``input_parameters.P_A``: probability in [%], one of {1e-7, 1e-6, 1e-5, 7.2e-5, 1e-3, 2.3e-1, 0.5} @@ -404,33 +404,33 @@ def scaled_load_sequence(self, input_parameters): Returns ------- pandas Series - The input series where all values have been scaled by :math:`\gamma_L`, + The input series where all values have been scaled by :math:`\gamma_L`, see 2.3.2.2 of the FKM nonlinear guideline. """ gamma_L = self.gamma_L(input_parameters) - + return self.scaled_by_constant(gamma_L) - - + + @pd.api.extensions.register_dataframe_accessor("fkm_safety_blanket") @pd.api.extensions.register_series_accessor("fkm_safety_blanket") class FKMLoadDistributionBlanket(FKMLoadSequence): - r'''Series accessor to get a scaled up load series, i.e., a list of load values with included load safety, - as used in FKM nonlinear lifetime assessments. - + r'''Series accessor to get a scaled up load series, i.e., a list of load values with included load safety, + as used in FKM nonlinear lifetime assessments. + The distribution of loads is unknown, therefore a scaling factor of :math:`\gamma_L` = 1.1 is assumed. - This is only used for :math:`P_L = 2.5\%`. + This is only used for :math:`P_L = 2.5\%`. As an alternative, we can use no scaling for the load distribution at all, corresponding to :math:`\gamma_L` = 1 and :math:`P_L = 50\%` - + For more information, see 2.3.2.3 of the FKM nonlinear guideline. See also -------- :class:`AbstractFKMLoadDistribution`: accesses meshes with connectivity information - ''' - + ''' + def gamma_L(self, input_parameters): r"""Compute the scaling factor :math:`\gamma_L`. @@ -438,8 +438,8 @@ def gamma_L(self, input_parameters): ---------- input_parameters : pandas Series The parameters to specify the upscaling method. - - * ``input_parameters.P_L``: probability in [%] of the load for which to do the assessment, + + * ``input_parameters.P_L``: probability in [%] of the load for which to do the assessment, has to be on of {2.5, 50}. (de: Ausfallwahrscheinlichkeit) Raises @@ -455,7 +455,7 @@ def gamma_L(self, input_parameters): """ self._validate_parameters(input_parameters, required_parameters=["P_L"]) - + if np.isclose(input_parameters.P_L, 2.5): # eq. 2.3-8 gamma_L = 1.1 @@ -464,19 +464,19 @@ def gamma_L(self, input_parameters): else: raise ValueError("fkm_safety_blanket is only possible for P_L=2.5 % " \ f"or P_L=50 %, not P_L={input_parameters.P_L} %") - + return gamma_L - + def scaled_load_sequence(self, input_parameters): r"""The scaled load sequence according to the given parameters. - + The following parameters are used: LSD_s, P_L, P_A. - + Parameters ---------- input_parameters : pandas Series The parameters to specify the upscaling method. - + * ``input_parameters.P_L``: probability in [%] of the load for which to do the assessment, one of {2.5, 50} Raises @@ -487,11 +487,11 @@ def scaled_load_sequence(self, input_parameters): Returns ------- pandas Series - The input series where all values have been scaled by :math:`\gamma_L`, + The input series where all values have been scaled by :math:`\gamma_L`, see 2.3.2.2 of the FKM nonlinear guideline. """ gamma_L = self.gamma_L(input_parameters) - + # eq. 2.3-5 - return self.scaled_by_constant(gamma_L) \ No newline at end of file + return self.scaled_by_constant(gamma_L) From d8664db26c0dac17909687d64d39bbd6e0e11e53 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 17:10:42 +0100 Subject: [PATCH 11/15] Remove unused nblink file Signed-off-by: Johannes Mueller --- docs/demos/fkm_nonlinear_mesh.nblink | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 docs/demos/fkm_nonlinear_mesh.nblink diff --git a/docs/demos/fkm_nonlinear_mesh.nblink b/docs/demos/fkm_nonlinear_mesh.nblink deleted file mode 100644 index c2110a28..00000000 --- a/docs/demos/fkm_nonlinear_mesh.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../demos/fkm_nonlinear/fkm_nonlinear_full.ipynb" -} From 6165e39c679fd07f705d27945eb8ee2118322997 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 17:31:35 +0100 Subject: [PATCH 12/15] Simplify setting of jupyter backend Signed-off-by: Johannes Mueller --- docs/conf.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 485ebc9c..fc2eaff3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,33 +49,14 @@ ep.preprocess(nb, {'metadata': {'path': os.path.dirname(fn)}}) -# Inject code to set pyvista jupyter backend into relevant notebooks -# -import nbsphinx -original_from_notebook_node = nbsphinx.Exporter.from_notebook_node - -from nbconvert.preprocessors import Preprocessor - -def from_notebook_node(self, nb, resources, **kwargs): - - class InjectPyvistaBackendPreprocessor(Preprocessor): - def preprocess(self, nb, resources): - for index, cell in enumerate(nb.cells): - if cell['cell_type'] == 'code' and 'import pyvista as pv' in cell['source'] : - cell['source'] += "\npv.set_jupyter_backend('html')\n" - break - return nb, resources - - nb, resources = InjectPyvistaBackendPreprocessor().preprocess(nb, resources=resources) - return original_from_notebook_node(self, nb, resources, **kwargs) - -nbsphinx.Exporter.from_notebook_node = from_notebook_node # Don't try to use C-code for the pytensor stuff when building the docs. # Otherwise the demo notebooks fail on readthedocs os.environ['PYTHONWARNINGS'] = 'ignore' +os.environ["PYVISTA_JUPYTER_BACKEND"] = 'html' + import asyncio # See https://bugs.python.org/issue37373 :( From ca014654786740738b325302618011ef16515221 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Thu, 7 Dec 2023 17:32:01 +0100 Subject: [PATCH 13/15] Add pylife-odbclient into docs build environment Signed-off-by: Johannes Mueller --- .github/workflows/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 03be2c8f..83661e12 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,6 +27,7 @@ jobs: python -m pip install --upgrade pip pip install -U setuptools setuptools_scm wheel pip install -e .[all,docs] + pip install -e ./tools/odbclient sudo apt-get install pandoc - name: Build docs run: sphinx-build -b html docs _build/html From 67a4aa679c5e4d7a0eca584a184a7fe19066503d Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Fri, 8 Dec 2023 09:11:06 +0100 Subject: [PATCH 14/15] Indentation fix and white space cleanup Signed-off-by: Johannes Mueller --- .../fkm_nonlinear/parameter_calculations.py | 322 +++++++++--------- 1 file changed, 161 insertions(+), 161 deletions(-) diff --git a/src/pylife/strength/fkm_nonlinear/parameter_calculations.py b/src/pylife/strength/fkm_nonlinear/parameter_calculations.py index 95e2f867..5beb069b 100644 --- a/src/pylife/strength/fkm_nonlinear/parameter_calculations.py +++ b/src/pylife/strength/fkm_nonlinear/parameter_calculations.py @@ -42,21 +42,21 @@ def calculate_cyclic_assessment_parameters(assessment_parameters_): - """Calculate the values of :math:`n', K'`, and :math:`E`, used to + """Calculate the values of :math:`n', K'`, and :math:`E`, used to describe the cyclic material behavior (Sec. 2.5.3 of FKM nonlinear). - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_cyclic_assessment_parameters(assessment_parameters) Parameters ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``MatGroupFKM``: Which material group, one of ``Steel``, ``SteelCast``, ``Al_wrought`` * ``R_m``: The ultimate tensile strength of the material, :math:`R_m`. @@ -64,7 +64,7 @@ def calculate_cyclic_assessment_parameters(assessment_parameters_): ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``E``: Young's modulus, constant estimated according to the material group * ``n_prime``: parameter for the Ramberg-Osgood material law * ``K_prime``: parameter for the Ramberg-Osgood material law @@ -72,39 +72,39 @@ def calculate_cyclic_assessment_parameters(assessment_parameters_): """ assessment_parameters = assessment_parameters_.copy() assert "R_m" in assessment_parameters - + # select set of constants according to given material group constants = pylife.strength.fkm_nonlinear.constants.for_material_group(assessment_parameters) - + # use constant values for n' and E assessment_parameters["n_prime"] = constants.n_prime assessment_parameters["E"] = constants.E - - # for FKM nonlinear, R_m is used to estimate material data + + # for FKM nonlinear, R_m is used to estimate material data # compute K' according to eq. (2.5-13) assessment_parameters["K_prime"] = constants.a_sigma * assessment_parameters.R_m ** constants.b_sigma \ / (np.minimum(constants.epsilon_grenz, constants.a_epsilon * assessment_parameters.R_m ** constants.b_epsilon)) \ ** constants.n_prime - + return assessment_parameters def calculate_material_woehler_parameters_P_RAM(assessment_parameters_): """Calculate the parameters of the material damage Woehler curve for the P_RAM damage parameter (Sec. 2.5.5 of FKM nonlinear). - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_material_woehler_parameters_P_RAM(assessment_parameters) Parameters ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``MatGroupFKM``: Which material group, one of ``Steel``, ``SteelCast``, ``Al_wrought`` * ``R_m``: The ultimate tensile strength of the material, :math:`R_m`. * ``P_A``: The failure probability @@ -113,7 +113,7 @@ def calculate_material_woehler_parameters_P_RAM(assessment_parameters_): ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``P_RAM_Z_WS``: First sampling point ("knee") of the material damage Woehler curve at N=1e3 * ``P_RAM_D_WS``: Damage threshold for infinite life, the second "knee" of the material damage Woehler curve * ``d_1``: first slope of the material damage Woehler curve @@ -121,28 +121,28 @@ def calculate_material_woehler_parameters_P_RAM(assessment_parameters_): """ assessment_parameters = assessment_parameters_.copy() - + assert "P_A" in assessment_parameters assert "R_m" in assessment_parameters - + # select set of constants according to given material group constants = pylife.strength.fkm_nonlinear.constants.for_material_group(assessment_parameters) - + # add an empty "notes" entry in assessment_parameters if "notes" not in assessment_parameters: assessment_parameters["notes"] = "" - # for standard FKM nonlinear, R_m is used to estimate material data + # for standard FKM nonlinear, R_m is used to estimate material data # computations for P_RAM # compute sampling point "Z" according to eq. (2.5-22) assessment_parameters["P_RAM_Z_WS"] = constants.a_PZ_RAM \ * assessment_parameters.R_m ** constants.b_PZ_RAM - + # compute sampling point "D" according to eq. (2.5-23) assessment_parameters["P_RAM_D_WS"] = constants.a_PD_RAM \ - * assessment_parameters.R_m ** constants.b_PD_RAM - + * assessment_parameters.R_m ** constants.b_PD_RAM + # depending on P_A, add the factor f_2.5%, as described in eqs. (2.5-22), (2.5-23) if np.isclose(assessment_parameters.P_A, 0.5): @@ -160,26 +160,26 @@ def calculate_material_woehler_parameters_P_RAM(assessment_parameters_): # use constant values for d_1 and d_2 assessment_parameters["d_1"] = constants.d_1 assessment_parameters["d_2"] = constants.d_2 - + return assessment_parameters def calculate_material_woehler_parameters_P_RAJ(assessment_parameters_): """Calculate the parameters of the material damage Woehler curve for the P_RAJ damage parameter (Sec. 2.9.4 of FKM nonlinear). - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_material_woehler_parameters_P_RAJ(assessment_parameters) Parameters ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``MatGroupFKM``: Which material group, one of ``Steel``, ``SteelCast``, ``Al_wrought`` * ``R_m``: The ultimate tensile strength of the material, :math:`R_m`. * ``P_A``: The failure probability @@ -188,36 +188,36 @@ def calculate_material_woehler_parameters_P_RAJ(assessment_parameters_): ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``P_RAJ_Z_WS``: First sampling point of the material damage Woehler curve for N=1 (not N=1e3 as for P_RAM!) * ``P_RAJ_D_WS``: Damage threshold for infinite life, the "knee" of the material damage Woehler curve * ``d_RAJ``: slope of the material damage Woehler curve """ assessment_parameters = assessment_parameters_.copy() - + assert "P_A" in assessment_parameters assert "R_m" in assessment_parameters - + # select set of constants according to given material group constants = pylife.strength.fkm_nonlinear.constants.for_material_group(assessment_parameters) - + # add an empty "notes" entry in assessment_parameters if "notes" not in assessment_parameters: assessment_parameters["notes"] = "" - - # for standard FKM nonlinear, R_m is used to estimate material data + + # for standard FKM nonlinear, R_m is used to estimate material data # computations for P_RAJ # compute first sampling point for N=1 according to eq. (2.8-20), (2.9-12) assessment_parameters["P_RAJ_Z_WS"] = constants.a_PZ_RAJ \ * assessment_parameters.R_m ** constants.b_PZ_RAJ - + # compute second sampling point, the infinite life threshold according to eq. (2.8-21), note the error in (2.9-13) (should be P_RAJ,D,WS) assessment_parameters["P_RAJ_D_WS"] = constants.a_PD_RAJ \ * assessment_parameters.R_m ** constants.b_PD_RAJ - - + + # depending on P_A, add the factor f_2.5%, as described in eqs. (2.5-22), (2.5-23) if np.isclose(assessment_parameters.P_A, 0.5): @@ -234,21 +234,21 @@ def calculate_material_woehler_parameters_P_RAJ(assessment_parameters_): # use constant value for d assessment_parameters["d_RAJ"] = constants.d_RAJ - + return assessment_parameters def calculate_roughness_material_woehler_parameters_P_RAM(assessment_parameters_): """ - For FKM nonlinear roughness & surface layer, calculate the additional parameters in the P_RAM Woehler curve - that model the dependency on the roughness. The resulting woehler curve is still the material woehler curve + For FKM nonlinear roughness & surface layer, calculate the additional parameters in the P_RAM Woehler curve + that model the dependency on the roughness. The resulting woehler curve is still the material woehler curve but with roughness effects. The other assessment factors are yet missing. - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_roughness_material_woehler_parameters_P_RAM(assessment_parameters) assessment_parameters = calculate_roughness_component_woehler_parameters_P_RAM(assessment_parameters) @@ -256,7 +256,7 @@ def calculate_roughness_material_woehler_parameters_P_RAM(assessment_parameters_ ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``P_RAM_Z_WS``: First sampling point ("knee") of the material damage Woehler curve at N=1e3 * ``P_RAM_D_WS``: Damage threshold for infinite life, the second "knee" of the material damage Woehler curve * ``d_2``: The second slope of the material damage Woehler curve without roughness effect @@ -266,7 +266,7 @@ def calculate_roughness_material_woehler_parameters_P_RAM(assessment_parameters_ ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``P_RAM_D_WS_rau``: Damage threshold for infinite life, the lower "knee" of the material damage Woehler curve * ``d2_RAM_rau``: second slope of the material damage Woehler curve, adjusted by roughness factor K_R,P @@ -286,29 +286,29 @@ def calculate_roughness_material_woehler_parameters_P_RAM(assessment_parameters_ assessment_parameters["d2_RAM_rau"] = assessment_parameters["d_2"] \ * (np.log(assessment_parameters["P_RAM_Z_WS"])-np.log(assessment_parameters["P_RAM_D_WS_rau"])) \ / (np.log(assessment_parameters["P_RAM_Z_WS"])-np.log(assessment_parameters["P_RAM_D_WS"])) - + # alternative calculation via N_D # (N_D: Eckschwingspielzahl zur Dauerfestigkeit) # this equation does the same as fatigue_life_limit in woehler_fkm_nonlinear - N_D = 1e3 * (assessment_parameters["P_RAM_D_WS"] / assessment_parameters["P_RAM_Z_WS"]) ** (1/assessment_parameters["d_2"]) - + N_D = 1e3 * (assessment_parameters["P_RAM_D_WS"] / assessment_parameters["P_RAM_Z_WS"]) ** (1/assessment_parameters["d_2"]) + assessment_parameters["d2_RAM_rau_alternative"] = np.log(assessment_parameters["P_RAM_D_WS_rau"] \ / assessment_parameters["P_RAM_Z_WS"]) / np.log(N_D/1e3) - + return assessment_parameters def calculate_roughness_material_woehler_parameters_P_RAJ(assessment_parameters_): """ - For FKM nonlinear roughness & surface layer, calculate the additional parameters in the P_RAJ Woehler curve - that model the dependency on the roughness. The resulting woehler curve is still the material woehler curve + For FKM nonlinear roughness & surface layer, calculate the additional parameters in the P_RAJ Woehler curve + that model the dependency on the roughness. The resulting woehler curve is still the material woehler curve but with roughness effects. The other assessment factors are yet missing. - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_roughness_material_woehler_parameters_P_RAJ(assessment_parameters) assessment_parameters = calculate_roughness_component_woehler_parameters_P_RAJ(assessment_parameters) @@ -316,7 +316,7 @@ def calculate_roughness_material_woehler_parameters_P_RAJ(assessment_parameters_ ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``P_RAJ_Z_WS``: Point at N=1 of the material damage Woehler curve (not N=1e3 as for P_RAM!) * ``P_RAJ_D_WS``: Damage threshold for infinite life, the second "knee" of the material damage Woehler curve * ``d_RAJ``: The slope of the material damage Woehler curve without roughness effect @@ -326,14 +326,14 @@ def calculate_roughness_material_woehler_parameters_P_RAJ(assessment_parameters_ ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``P_RAJ_Z_1e3``: The upper "knee" of the material damage Woehler curve at N=1e3. * ``P_RAJ_D_WS_rau``: Damage threshold for infinite life, the lower "knee" of the material damage Woehler curve * ``d_RAJ_2_rau``: The second slope of the material damage Woehler curve adjusted by roughness factor K_R,P """ assessment_parameters = assessment_parameters_.copy() - + assessment_parameters["P_RAJ_Z_1e3"] = assessment_parameters["P_RAJ_Z_WS"]*np.power(1e3, assessment_parameters["d_RAJ"]) assessment_parameters["P_RAJ_D_WS_rau"] = assessment_parameters["P_RAJ_D_WS"] * assessment_parameters["K_RP"]**2. @@ -344,29 +344,29 @@ def calculate_roughness_material_woehler_parameters_P_RAJ(assessment_parameters_ # alternative calculation via N_D # N_D: Eckschwingspielzahl zur Dauerfestigkeit # this equation does the same as fatigue_life_limit in woehler_fkm_nonlinear - N_D = (assessment_parameters["P_RAJ_D_WS"] / assessment_parameters["P_RAJ_Z_WS"]) ** (1/assessment_parameters["d_RAJ"]) - + N_D = (assessment_parameters["P_RAJ_D_WS"] / assessment_parameters["P_RAJ_Z_WS"]) ** (1/assessment_parameters["d_RAJ"]) + # P_RAJ_D*K**2=P_RAJ_Z_1e3 * (N_D/1e3)**r_rau assessment_parameters["d_RAJ_2_rau_alternative"] = np.log(assessment_parameters["P_RAJ_D_WS_rau"] \ / assessment_parameters["P_RAJ_Z_1e3"]) / np.log(N_D/1e3) - + return assessment_parameters def calculate_roughness_component_woehler_parameters_P_RAM(assessment_parameters_, include_n_P): """Calculate the component woehler curve from the material woehler curve - (Sec. 2.5.6 of FKM nonlinear), but with the special roughness consideration - described in the extension surface layer & roughness. + (Sec. 2.5.6 of FKM nonlinear), but with the special roughness consideration + described in the extension surface layer & roughness. This involves multiplying the appropriate factors to the point P_RAM_Z_WS and P_RAM_D_WS_rau in the material woehler curve to obtain the points P_RAM_Z and P_RAM_D in the component Woehler curve. The "appropriate factors" consist of ``gamma_M`` which relates to the failure probability and optionally ``n_P``, which describes the supporting effect of the notch (fracture mechanical number and statistic number). The roughness has already been incorporated during `calculate_roughness_material_woehler_parameters_P_RAM`. - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: assessment_parameters = calculate_roughness_parameter(assessment_parameters) @@ -379,9 +379,9 @@ def calculate_roughness_component_woehler_parameters_P_RAM(assessment_parameters ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``gamma_M_RAM``: The factor for the standard deviation of the capacity to withstand stresses of the component. - * ``n_P``: The factor for nonlocal effects, can be computed by ``calculate_nonlocal_parameters``. + * ``n_P``: The factor for nonlocal effects, can be computed by ``calculate_nonlocal_parameters``. This is only needed if ``include_n_P`` is `True`. * ``P_RAM_Z_WS``: The point at N=1e3 in the material Woehler curve. * ``P_RAM_D_WS_rau``: The point in the material Woehler curve. @@ -393,7 +393,7 @@ def calculate_roughness_component_woehler_parameters_P_RAM(assessment_parameters ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``P_RAM_Z``: The "Zeitfestigkeit" point in the component Woehler curve. * ``P_RAM_D``: The "Dauerfestigkeit" point in the component Woehler curve, i.e., the fatigue strength limit, the P_RAM value below which we have infinite life @@ -403,16 +403,16 @@ def calculate_roughness_component_woehler_parameters_P_RAM(assessment_parameters assert "gamma_M_RAM" in assessment_parameters assert "P_RAM_Z_WS" in assessment_parameters assert "P_RAM_D_WS_rau" in assessment_parameters - + # set n_P only if it should be added (for the surface point in FKM nonlinear roughness & surface layer) n_P = 1 if include_n_P: assert "n_P" in assessment_parameters - n_P = assessment_parameters.n_P + n_P = assessment_parameters.n_P # calculate first knee point of component Woehler curve, eq. (2.5-25) in the FKM nonlinear guideline without roughness assessment_parameters["P_RAM_Z"] = n_P / assessment_parameters.gamma_M_RAM * assessment_parameters.P_RAM_Z_WS - + # calculate fatigue strength limit of the component, i.e., the P_RAM value below which we have infinite life, rhs of eq. (2.6-88) assessment_parameters["P_RAM_D"] = n_P / assessment_parameters.gamma_M_RAM * assessment_parameters.P_RAM_D_WS_rau @@ -421,21 +421,21 @@ def calculate_roughness_component_woehler_parameters_P_RAM(assessment_parameters def calculate_roughness_component_woehler_parameters_P_RAJ(assessment_parameters_, include_n_P): """Calculate the component woehler curve from the material woehler curve - (Sec. 2.8.6 of FKM nonlinear), but with the special roughness consideration - described in the extension surface layer & roughness. - This involves multiplying the appropriate factors to the points P_RAJ_Z_WS - and P_RAJ_D_WS_rau in the material woehler curve to obtain the + (Sec. 2.8.6 of FKM nonlinear), but with the special roughness consideration + described in the extension surface layer & roughness. + This involves multiplying the appropriate factors to the points P_RAJ_Z_WS + and P_RAJ_D_WS_rau in the material woehler curve to obtain the points P_RAJ_Z and P_RAJ_D in the component Woehler curve. - + The "appropriate factors" consist of ``gamma_M`` which relates to the failure probability and optionally ``n_P``, which describes the supporting effect of the notch (fracture mechanical number and statistic number). The roughness has already been incorporated during `calculate_roughness_material_woehler_parameters_P_RAJ`. - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_roughness_parameter(assessment_parameters) assessment_parameters = calculate_roughness_material_woehler_parameters_P_RAJ(assessment_parameters) assessment_parameters = calculate_nonlocal_parameters(assessment_parameters) # include this line if include_n_P is True @@ -446,9 +446,9 @@ def calculate_roughness_component_woehler_parameters_P_RAJ(assessment_parameters ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``gamma_M_RAJ``: The factor for the standard deviation of the capacity to withstand stresses of the component. - * ``n_P``: The factor for nonlocal effects, can be computed by ``calculate_nonlocal_parameters``. + * ``n_P``: The factor for nonlocal effects, can be computed by ``calculate_nonlocal_parameters``. This is only needed if ``include_n_P`` is `True`. * ``P_RAJ_Z_WS``: The point at N=1 in the material Woehler curve (note that for P_RAJ it is not N=1e3 as for P_RAM!) * ``P_RAJ_D_WS_rau``: The threshold for infinite life in the material Woehler curve. @@ -460,7 +460,7 @@ def calculate_roughness_component_woehler_parameters_P_RAJ(assessment_parameters ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``P_RAJ_Z``: The first point in the component Woehler curve at N=1 (not N=1e3 as for P_RAM!). * ``P_RAJ_D_0`` and ``P_RAJ_D``: The infinite life threshold of the component Woehler curve. @@ -470,49 +470,49 @@ def calculate_roughness_component_woehler_parameters_P_RAJ(assessment_parameters assert "gamma_M_RAJ" in assessment_parameters assert "P_RAJ_Z_WS" in assessment_parameters assert "P_RAJ_D_WS_rau" in assessment_parameters - + # set n_P only if it should be added (for the surface point in FKM nonlinear roughness & surface layer) n_P = 1 if include_n_P: assert "n_P" in assessment_parameters - n_P = assessment_parameters.n_P + n_P = assessment_parameters.n_P # calculations for P_RAJ of component Woehler curve # eq. (2.8-23), (2.9-25) assessment_parameters["P_RAJ_Z"] = n_P**2 / assessment_parameters.gamma_M_RAJ * assessment_parameters.P_RAJ_Z_WS - + # also shift the knee point of the roughness P_RAJ woehler curve assessment_parameters["P_RAJ_Z_1e3"] = n_P**2 / assessment_parameters.gamma_M_RAJ * assessment_parameters["P_RAJ_Z_1e3"] # eq. (2.8-24), (2.9-26). Note that there is also eq. 2.9-13, but this is errorneous and not relevant here. assessment_parameters["P_RAJ_D_0"] = n_P**2 / assessment_parameters.gamma_M_RAJ * assessment_parameters.P_RAJ_D_WS_rau - + # eq. (2.8-25), (2.9-27) assessment_parameters["P_RAJ_D"] = assessment_parameters.P_RAJ_D_0 - + return assessment_parameters def calculate_nonlocal_parameters(assessment_parameters_): """Calculate the factors for the nonlocal effects on the component lifetime. - (Sec. 2.5.6.1 of FKM nonlinear). This includes the statistic factor and the + (Sec. 2.5.6.1 of FKM nonlinear). This includes the statistic factor and the fracture mechanics factor. - + The calculation procedure is the same for P_RAM and P_RAJ damage parameters. For P_RAJ, the equivalent formulas are presented in chapter 2.8.6.1 of FKM nonlinear. - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_nonlocal_parameters(assessment_parameters) Parameters ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``MatGroupFKM``: Which material group, one of ``Steel``, ``SteelCast``, ``Al_wrought`` * ``A_ref``: Reference surface area of the highly loaded area, usually set to 500 [mm^2]. * ``A_sigma``: Surface area of the highly loaded area of the component (in [mm^2]). @@ -523,62 +523,62 @@ def calculate_nonlocal_parameters(assessment_parameters_): ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``n_st``: The statistic factor (de: Statistische Stützzahl) * ``n_bm``: The fracture mechanic factor (de: bruchmechanische Stützzahl) * ``n_P``: The total material factor as the product of ``n_st`` and ``n_bm`` (de: werkstoffmechanische Stützzahl) """ assessment_parameters = assessment_parameters_.copy() - + assert "A_ref" in assessment_parameters assert "A_sigma" in assessment_parameters assert "G" in assessment_parameters assert "R_m" in assessment_parameters - + # select set of constants according to given material group constants = pylife.strength.fkm_nonlinear.constants.for_material_group(assessment_parameters) - + # calculate statistic coefficient, eq. (2.5-28) assessment_parameters["n_st"] = (assessment_parameters.A_ref / assessment_parameters.A_sigma) \ ** (1 / constants.k_st) - + # eq. (2.5-32) k_ = 5 * assessment_parameters.n_st + assessment_parameters.R_m / constants.R_m_bm \ * np.sqrt((7.5 + np.sqrt(assessment_parameters.G)) / (1 + 0.2*np.sqrt(assessment_parameters.G))) - + # eq. (2.5-31) assessment_parameters["n_bm_"] = (5 + np.sqrt(assessment_parameters.G)) / k_ - + # eq. (2.5-30) assessment_parameters["n_bm"] = np.maximum(assessment_parameters.n_bm_, 1) - + # calculate total coefficient, eq. (2.5-27) assessment_parameters["n_P"] = assessment_parameters.n_bm * assessment_parameters.n_st - + return assessment_parameters def calculate_roughness_parameter(assessment_parameters_): """Calculate the roughness factor K_R,P (Sec. 2.5.6.2 of FKM nonlinear). - + If the factor assessment_parameters["K_RP"] is already set, this function does nothing. - + The calculation is the same for P_RAM and P_RAJ damage parameters. For P_RAJ, the equivalent formulas are presented in chapter 2.8.6.2 of FKM nonlinear. - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_roughness_parameter(assessment_parameters) Parameters ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``MatGroupFKM``: Which material group, one of ``Steel``, ``SteelCast``, ``Al_wrought`` * ``R_m``: The ultimate tensile strength of the material, :math:`R_m`. * ``R_z``: Only if K_RP is not yet given: The surface roughness of the component, :math:`R_z`. @@ -587,40 +587,39 @@ def calculate_roughness_parameter(assessment_parameters_): ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``K_RP``: The roughness factor K_R,P. """ assessment_parameters = assessment_parameters_.copy() - + # if K_RP is already set (e.g., manually set to 1), do nothing if "K_RP" in assessment_parameters: print(f"The parameter `K_RP` is already set to {assessment_parameters.K_RP}, not using the FKM formula.") return assessment_parameters - + assert "R_m" in assessment_parameters assert "R_z" in assessment_parameters - + # select set of constants according to given material group constants = pylife.strength.fkm_nonlinear.constants.for_material_group(assessment_parameters) - + # calculate roughness factor, eq. (2.5-37) if assessment_parameters.R_z > 1: assessment_parameters["K_RP"] = (1 - constants.a_RP * np.log10(assessment_parameters.R_z) \ * np.log10(2 * assessment_parameters.R_m / constants.R_m_N_min)) ** constants.b_RP - + else: assessment_parameters["K_RP"] = 1. - + return assessment_parameters def compute_beta(P_A): """Calculates the beta parameter ("damage index"), - which is an intermediate value for - the lifetime assessment factor gamma_M. Note that the FKM nonlinear guideline - does not list the formula for beta, they assume that the beta value is - known. + which is an intermediate value for the lifetime assessment factor gamma_M. + Note that the FKM nonlinear guideline does not list the formula for beta, + they assume that the beta value is known. Parameters ---------- @@ -631,28 +630,29 @@ def compute_beta(P_A): ------- float The parameter beta. - """ """""" + + """ sigma = 1 result = scipy.optimize.root(lambda x: abs(scipy.stats.norm.cdf(x, 0, sigma)-P_A), x0=-0.6, tol=1e-10) if not result.success: raise RuntimeError(f"Could not compute the value of beta for P_A={P_A}, " "the optimizer did not find a solution.") - + return -result.x[0] / sigma def calculate_failure_probability_factor_P_RAM(assessment_parameters_): - """Calculate the factor for the failure probability of the component, i.e., the factor + """Calculate the factor for the failure probability of the component, i.e., the factor for the standard deviation of the capacity to withstand stresses of the component. This calculation is for use with the P_RAM damage parameter. - - If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) + + If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) is computed from the damage index ``beta``. Otherwise, it is derived from the failure probability assessment_parameters.P_A - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: assessment_parameters = calculate_failure_probability_factor_P_RAM(assessment_parameters) @@ -661,19 +661,19 @@ def calculate_failure_probability_factor_P_RAM(assessment_parameters_): ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * Either the failure probability, ``P_A``, or the damage index, ``beta``. Returns ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``gamma_M_RAM``: The factor for the standard deviation of the capacity to withstand stresses of the component. """ assessment_parameters = assessment_parameters_.copy() - + if "beta" not in assessment_parameters: assert "P_A" in assessment_parameters @@ -685,7 +685,7 @@ def calculate_failure_probability_factor_P_RAM(assessment_parameters_): if "beta" in assessment_parameters: # eq. (2.5-38) assessment_parameters["gamma_M_RAM"] = np.max([10**((0.8*assessment_parameters.beta - 2)*0.08), 1.1]) - + # set to 1 for P_A = 0.5 if np.isclose(assessment_parameters.P_A, 0.5): assessment_parameters["gamma_M_RAM"] = 1 @@ -694,16 +694,16 @@ def calculate_failure_probability_factor_P_RAM(assessment_parameters_): def calculate_failure_probability_factor_P_RAJ(assessment_parameters_): - """Calculate the factor for the failure probability of the component, i.e., the factor + """Calculate the factor for the failure probability of the component, i.e., the factor for the standard deviation of the capacity to withstand stresses of the component. This calculation is for use with the P_RAJ damage parameter. - - If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) + + If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) is computed from the damage index ``beta``. Otherwise, it is derived from the failure probability assessment_parameters.P_A - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: assessment_parameters = calculate_failure_probability_factor_P_RAJ(assessment_parameters) @@ -712,14 +712,14 @@ def calculate_failure_probability_factor_P_RAJ(assessment_parameters_): ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * Either the failure probability, ``P_A``, or the damage index, ``beta``. Returns ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``gamma_M_RAJ``: The factor for the standard deviation of the capacity to withstand stresses of the component. """ @@ -736,7 +736,7 @@ def calculate_failure_probability_factor_P_RAJ(assessment_parameters_): if "beta" in assessment_parameters: # eq. (2.8-38) assessment_parameters["gamma_M_RAJ"] = np.max([10**((0.8*assessment_parameters.beta - 2)*0.155), 1.2]) - + # set to 1 for P_A = 0.5 if np.isclose(assessment_parameters.P_A, 0.5): assessment_parameters["gamma_M_RAJ"] = 1 @@ -747,15 +747,15 @@ def calculate_failure_probability_factor_P_RAJ(assessment_parameters_): def calculate_component_woehler_parameters_P_RAM(assessment_parameters_): """Calculate the component woehler curve from the material woehler curve (Sec. 2.5.6 of FKM nonlinear). This involves multiplying the appropriate - factors to the point P_RAM_Z_WS in the material woehler curve to obtain the + factors to the point P_RAM_Z_WS in the material woehler curve to obtain the point P_RAM_Z in the component Woehler curve. - - If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) + + If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) is computed from the damage index ``beta``. Otherwise, it is derived from the failure probability assessment_parameters.P_A - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: assessment_parameters = calculate_material_woehler_parameters_P_RAM(assessment_parameters) @@ -768,7 +768,7 @@ def calculate_component_woehler_parameters_P_RAM(assessment_parameters_): ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``gamma_M_RAM``: The factor for the standard deviation of the capacity to withstand stresses of the component. * ``n_P``: The factor for nonlocal effects, can be computed by ``calculate_nonlocal_parameters``. * ``K_RP``: The roughness factor K_R,P. @@ -780,7 +780,7 @@ def calculate_component_woehler_parameters_P_RAM(assessment_parameters_): ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``f_RAM``: The factor to map between component and material Woehler curves. * ``P_RAM_Z``: The "Zeitfestigkeit" point in the component Woehler curve. * ``P_RAM_D``: The "Dauerfestigkeit" point in the component Woehler curve, i.e., the fatigue strength limit, the P_RAM value below which we have infinite life @@ -792,33 +792,33 @@ def calculate_component_woehler_parameters_P_RAM(assessment_parameters_): assert "n_P" in assessment_parameters assert "K_RP" in assessment_parameters assert "P_RAM_Z_WS" in assessment_parameters - + # eq. (2.5-24) assessment_parameters["f_RAM"] = assessment_parameters.gamma_M_RAM / (assessment_parameters.n_P * assessment_parameters.K_RP) - + # calculate knee point of component Woehler curve, eq. (2.5-25) assessment_parameters["P_RAM_Z"] = 1 / assessment_parameters.f_RAM * assessment_parameters.P_RAM_Z_WS - + # calculate fatigue strength limit of the component, i.e., the P_RAM value below which we have infinite life, rhs of eq. (2.6-88) assessment_parameters["P_RAM_D"] = 1 / assessment_parameters.f_RAM * assessment_parameters.P_RAM_D_WS - + return assessment_parameters def calculate_component_woehler_parameters_P_RAJ(assessment_parameters_): """Calculate the component woehler curve from the material woehler curve (Sec. 2.8.6 of FKM nonlinear). This involves multiplying the appropriate - factors to the points P_RAJ_Z_WS and P_RAJ_D_WS in the material woehler curve to obtain the + factors to the points P_RAJ_Z_WS and P_RAJ_D_WS in the material woehler curve to obtain the points P_RAJ_Z and P_RAJ_D in the component Woehler curve. - - If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) + + If assessment_parameters.beta is set, the safety factor gamma_M for the capacity to withstand stresses of the component (de: Beanspruchbarkeit) is computed from the damage index ``beta``. Otherwise, it is derived from the failure probability assessment_parameters.P_A - + The calculated values will be set in a copy of the input series. The intended use is as follows: - + .. code:: - + assessment_parameters = calculate_material_woehler_parameters_P_RAJ(assessment_parameters) assessment_parameters = calculate_nonlocal_parameters(assessment_parameters) assessment_parameters = calculate_roughness_parameter(assessment_parameters) @@ -829,7 +829,7 @@ def calculate_component_woehler_parameters_P_RAJ(assessment_parameters_): ---------- assessment_parameters : pandas Series The named material parameters. This Series has to include at least the following values: - + * ``gamma_M_RAJ``: The factor for the standard deviation of the capacity to withstand stresses of the component. * ``n_P``: The factor for nonlocal effects, can be computed by ``calculate_nonlocal_parameters``. * ``K_RP``: The roughness factor K_R,P. @@ -841,7 +841,7 @@ def calculate_component_woehler_parameters_P_RAJ(assessment_parameters_): ------- pandas DataFrame A copy of ``assessment_parameters`` with the following additional items set: - + * ``f_RAJ``: The factor to map between component and material Woehler curves. * ``P_RAJ_Z``: The first point in the component Woehler curve. * ``P_RAJ_D_0`` and ``P_RAJ_D``: The infinite life threshold of the component Woehler curve. @@ -854,18 +854,18 @@ def calculate_component_woehler_parameters_P_RAJ(assessment_parameters_): assert "K_RP" in assessment_parameters assert "P_RAJ_Z_WS" in assessment_parameters assert "P_RAJ_D_WS" in assessment_parameters - + # eq. (2.9-24) or eq. (2.8-22) assessment_parameters["f_RAJ"] = assessment_parameters.gamma_M_RAJ / (assessment_parameters.n_P**2 * assessment_parameters.K_RP**2) - + # calculations for P_RAJ of component Woehler curve # eq. (2.8-23), (2.9-25) assessment_parameters["P_RAJ_Z"] = 1 / assessment_parameters.f_RAJ * assessment_parameters.P_RAJ_Z_WS - + # eq. (2.8-24), (2.9-26). Note that there is also eq. 2.9-13, but this is errorneous and not relevant here. assessment_parameters["P_RAJ_D_0"] = 1 / assessment_parameters.f_RAJ * assessment_parameters.P_RAJ_D_WS - + # eq. (2.8-25), (2.9-27) assessment_parameters["P_RAJ_D"] = assessment_parameters.P_RAJ_D_0 - + return assessment_parameters From 8c2af129cc9b56450460a92b3a45bf878bd76df0 Mon Sep 17 00:00:00 2001 From: Johannes Mueller Date: Fri, 8 Dec 2023 09:37:48 +0100 Subject: [PATCH 15/15] Fixing variable markup Signed-off-by: Johannes Mueller --- src/pylife/materiallaws/rambgood.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pylife/materiallaws/rambgood.py b/src/pylife/materiallaws/rambgood.py index 1d4dcdbf..85a278ca 100644 --- a/src/pylife/materiallaws/rambgood.py +++ b/src/pylife/materiallaws/rambgood.py @@ -26,13 +26,12 @@ class RambergOsgood: Parameters ---------- - E : float Young's Modulus K : float - The strength coefficient, usually named `K'` or ``K_prime`̀ in FKM nonlinear related formulas. + The strength coefficient, usually named ``K'`` or ``K_prime`` in FKM nonlinear related formulas. n : float - The strain hardening coefficient, usually named `n'` or ``n_prime`̀ in FKM nonlinear related formulas. + The strain hardening coefficient, usually named ``n'`` or ``n_prime`` in FKM nonlinear related formulas. Notes ----- @@ -156,7 +155,7 @@ def dresiduum(stress): abs_stress = optimize.newton( func=residuum, x0=stress0, - fprime=dresiduum, + fprime=dresiduum, rtol=rtol, tol=tol ) return abs_stress * sign_strain