Source code for pylife.strength.miner

# Copyright (c) 2019-2023 - for information on the respective copyright owner
# see the NOTICE file and/or the repository
# https://github.com/boschresearch/pylife
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Implementation of the miner rule for fatigue analysis
=====================================================

Currently, the following implementations are part of this module:

* Miner Elementary
* Miner Haibach

References
----------
* M. Wächter, C. Müller and A. Esderts, "Angewandter Festigkeitsnachweis nach {FKM}-Richtlinie"
  Springer Fachmedien Wiesbaden 2017, https://doi.org/10.1007/978-3-658-17459-0

* E. Haibach, "Betriebsfestigkeit", Springer-Verlag 2006, https://doi.org/10.1007/3-540-29364-7
"""

__author__ = "Cedric Philip Wagner"
__maintainer__ = "Johannes Mueller"

from abc import ABC, abstractmethod

import numpy as np
import pandas as pd

from pylife.strength.fatigue import Fatigue
from pylife.materiallaws.woehlercurve import WoehlerCurve

import pylife.strength.solidity as SOL


[docs] class MinerBase(WoehlerCurve, ABC): """Basic functions related to miner-rule (original). Uses the constructor of :class:`~pylife.materiallaws.WoehlerCurve`. """
[docs] def finite_life_factor(self, N): """Calculate *finite life factor* according to Waechter2017 (p. 96). Parameters ---------- N : float Collective range (sum of cycle numbers) of load collective """ return np.power(self.ND/N, 1./self.k_1)
[docs] def effective_damage_sum(self, collective): """Compute *effective damage sum* D_m. Refers to the formula given in Waechter2017, p. 99 Parameters ---------- collective : a load collective the multiple of the lifetime Returns ------- effective_damage_sum : float or :class:`pandas.Series` The effective damage sums for the collective """ A = self.lifetime_multiple(collective) return effective_damage_sum(A)
[docs] def gassner_cycles(self, collective): """Compute the cycles of the Gassner line for a certain load collective. Parameters ---------- collective : :class:`~pylife.stress.rainflow.LoadCollective` or similar The load collective Returns ------- cycles The cycles for the collective Note ---- The absolute load levels of the collective are important. """ return self.cycles(collective.amplitude.max()) * self.lifetime_multiple(collective)
[docs] @abstractmethod def lifetime_multiple(self, collective): """Compute the lifetime multiple according to the corresponding Miner rule. Needs to be implemented in the class implementing the Miner rule. Parameters ---------- collective : :class:`~pylife.stress.rainflow.LoadCollective` or similar The load collective Returns ------- lifetime_multiple : float > 0 lifetime multiple """ pass
[docs] @pd.api.extensions.register_series_accessor('gassner_miner_elementary') class MinerElementary(MinerBase): """Implementation of Miner Elementary according to Waechter2017."""
[docs] def gassner(self, collective): """Calculate the Gaßner shift according to Miner Elementary. Parameters ---------- collective : :class:`~pylife.stress.rainflow.LoadCollective` or similar The load collective Returns ------- gassner : :class:`~pylife.stength.Fatigue` The Gaßner shifted fatigue strength object. """ gassner = self.to_pandas().copy() gassner['ND'] = self.ND * self.lifetime_multiple(collective) return Fatigue(gassner)
[docs] def lifetime_multiple(self, collective): """Compute the lifetime multiple according to Miner Elementary. Described in Waechter2017 as "Lebensdauervielfaches, A_ele". Parameters ---------- collective : :class:`~pylife.stress.rainflow.LoadCollective` or similar The load collective Returns ------- lifetime_multiple : float > 0 lifetime multiple """ return 1. / SOL.haibach(collective, self.k_1)
[docs] @pd.api.extensions.register_series_accessor('gassner_miner_haibach') class MinerHaibach(MinerBase): """Miner-modified according to Haibach (2006). Warnings -------- Contrary to Miner Elementary, the lifetime multiple is not constant but dependent on the evaluated load level! That is why there is no method for the Gaßner shift. """
[docs] def lifetime_multiple(self, collective): """Compute the lifetime multiple for Miner-modified according to Haibach. Refer to Haibach (2006), p. 291 (3.21-61). The lifetime multiple can be expressed in respect to the maximum amplitude so that N_lifetime = N_Smax * A Parameters ---------- collective : :class:`~pylife.stress.rainflow.LoadCollective` or similar The load collective Returns ------- lifetime_multiple : float > 0 lifetime multiple return value is 'inf' if maximum collective amplitude < SD """ s_a = collective.amplitude max_amp = s_a.max() cycles = collective.cycles s_a = s_a / max_amp x_D = self.SD / max_amp i_full_damage = (s_a >= x_D) i_reduced_damage = (s_a < x_D) s_full_damage = s_a[i_full_damage] s_reduced_damage = s_a[i_reduced_damage] n_full_damage = cycles[i_full_damage] n_reduced_damage = cycles[i_reduced_damage] # first expression of the summation term in the denominator sum_1 = np.dot(n_full_damage, (s_full_damage**self.k_1)) sum_2 = x_D**(1 - self.k_1) * np.dot(n_reduced_damage, (s_reduced_damage**(2 * self.k_1 - 1))) return cycles.sum() / (sum_1 + sum_2)
[docs] def effective_damage_sum(lifetime_multiple): """Compute *effective damage sum*. Refers to the formula given in Waechter2017, p. 99 Parameters ---------- A : float or np.ndarray (with 1 element) the multiple of the lifetime Returns d_m : float the effective damage sum """ d_min = 0.3 # minimum as suggested by FKM d_max = 1.0 d_m_no_limits = 2. / (lifetime_multiple**(1./4.)) d_m = min( max(d_min, d_m_no_limits), d_max ) return d_m