Source code for pylife.strength.meanstress

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

"""
Meanstress transformation
=========================

Meanstress transformation is used to take the influence of the meanstress of a
load hysteresis into account.  The fatigue of a cyclically loaded component is
not only influenced by the amplitude but also by the mean stress.  The usual
way to take this into account is to compute a substitute amplitude that has the
same fatigue result for the known mean stress.  The mean stress sensitivity
is usually defined by a haigh diagram, depending on the R-value regime.

In pyLife we have the class :class:`~pylife.strength.meanstress.HaighDiagram`
that lets you define an arbitrary haigh diagram, i.e. a mean stress sensitivity
for each interval of `R`.  There are also convenience functions
:class:`~pylife.strength.meanstress.HaighDiagram.fkm_goodman` and
:class:`~pylife.strength.meanstress.HaighDiagram.five_segment` that lets you
define the more common haigh diagrams more easily.

* FKM Goodman
* Five Segment Correction

"""

__author__ = "Johannes Mueller, Lena Rapp"
__maintainer__ = "Johannes Mueller"

from collections.abc import Iterable
import operator as op

import numpy as np
import pandas as pd

from pylife import PylifeSignal, Broadcaster
import pylife.stress.collective as CL


[docs] @pd.api.extensions.register_series_accessor("haigh_diagram") class HaighDiagram(PylifeSignal): """Model for a Haigh diagram in order to perform meanstress transformations. A Haigh diagram a set of meanstress sensitivity slopes $M$ that is changing with the R-values. The values of the ```pd.Series`` represents that slopes $M$ and the `pd.IntervalIndex` represents the R-ranges. """
[docs] @classmethod def from_dict(cls, segments_dict): """Create a Haigh diagram from a dict. Parameters ---------- segments_dict : dict dict resolving the R-value intervals to the meanstress slope Example ------- >>> HaighDiagram.from_dict({ ... (1.0, np.inf): 0.0, ... (-np.inf, 0.0): 0.5, ... (0.0, 1.0): 0.167 ... }).to_pandas() R (1.0, inf] 0.000 (-inf, 0.0] 0.500 (0.0, 1.0] 0.167 dtype: float64 sets up a FKM Goodman like Haigh diagram. """ vals = np.array(list(segments_dict.values())) idx = pd.IntervalIndex.from_tuples(list(segments_dict.keys()), name="R") return HaighDiagram(pd.Series(vals, index=idx))
[docs] @classmethod def fkm_goodman(cls, haigh_fkm_goodman): """Create a Haigh diagram according to FKM Goodman. Parameters ---------- haigh_fkm_goodman : pd.Series or pd.DataFrame a series containing one or a dataframe containing multiple values for `M` and optionally `M2`. Notes ----- The Haigh diagram according to FKM Goodman comes with the slope ``M`` which is valid between ``R==-inf`` and ``R==0``. Beyond ``R==0`` the slope is ``M2` if ``M2`` is given or ``M/3`` if not. Examples -------- A FKM Goodman diagram with default ``M2`` >>> HaighDiagram.fkm_goodman(pd.Series({"M": 0.5})).to_pandas() R (1.0, inf] 0.000000 (-inf, 0.0] 0.500000 (0.0, 1.0] 0.166667 dtype: float64 A FKM Goodman diagram with manual ``M2`` >>> HaighDiagram.fkm_goodman(pd.Series({"M": 0.5, "M2": 0.2})).to_pandas() R (1.0, inf] 0.0 (-inf, 0.0] 0.5 (0.0, 1.0] 0.2 dtype: float64 >>> collective = pd.DataFrame( ... { ... "range": [600.0, 300.0, 500.0], ... "mean": [100.0, 50.0, 80.0], ... "cycles": [1.0, 10.0, 100.0], ... } ... ) >>> HaighDiagram.fkm_goodman(pd.Series({"M": 0.5})).transform(collective, -1.0) range mean cycles 0 700.0 0.0 1.0 1 350.0 0.0 10.0 2 580.0 0.0 100.0 """ if "M2" not in haigh_fkm_goodman: haigh_fkm_goodman["M2"] = haigh_fkm_goodman["M"] / 3.0 M = haigh_fkm_goodman.M M2 = haigh_fkm_goodman.M2 interval_index = pd.IntervalIndex.from_tuples( [(1.0, np.inf), (-np.inf, 0.0), (0.0, 1.0)], name="R" ) if isinstance(haigh_fkm_goodman, pd.Series): haigh_index = interval_index dummy_index = pd.Index([0, 1, 2], name="R") else: haigh_frame, _ = Broadcaster(haigh_fkm_goodman.index.to_frame()).broadcast( interval_index.to_frame() ) haigh_index = haigh_frame.index dummy_index = pd.Index([0, 1, 2] * len(haigh_fkm_goodman), name="R") haigh = pd.Series(0.0, index=dummy_index) R_index = haigh.index.get_level_values("R") haigh.iloc[R_index.get_indexer_for([1])] = M haigh.iloc[R_index.get_indexer_for([2])] = M2 haigh.index = haigh_index return cls(haigh)
[docs] @classmethod def five_segment(cls, five_segment_haigh_diagram): """Create a five segment slope Haigh diagram. Parameters ---------- five_segment_haigh_diagram : :class:`pandas.Series` or :class:`pandas.DataFrame` The five segment meanstress slope data. Notes ----- ``five_segment_hagih_diagram`` has to provide the following keys: * ``M0``: the mean stress sensitivity between ``R==-inf`` and ``R==0`` * ``M1``: the mean stress sensitivity between ``R==0`` and ``R==R12`` * ``M2``: the mean stress sensitivity betwenn ``R==R12`` and ``R==R23`` * ``M3``: the mean stress sensitivity between ``R==R23`` and ``R==1`` * ``M4``: the mean stress sensitivity beyond ``R==1`` * ``R12``: R-value between ``M1`` and ``M2`` * ``R23``: R-value between ``M2`` and ``M3`` Examples -------- >>> haigh = HaighDiagram.five_segment( ... pd.Series( ... {"M0": 0.5, "M1": 0.25, "M2": 0.125, "M3": 1.0, "M4": -2.0, "R12": 0.2, "R23": 0.8} ... ) ... ) >>> haigh.to_pandas() R (1.0, inf] -2.000 (-inf, 0.0] 0.500 (0.0, 0.2] 0.250 (0.2, 0.8] 0.125 (0.8, 1.0] 1.000 dtype: float64 >>> collective = pd.DataFrame( ... { ... "range": [600.0, 300.0, 500.0], ... "mean": [400.0, -150.0, 0.0], ... "cycles": [1.0, 10.0, 100.0], ... } ... ) >>> haigh.transform(collective, 0.0) range mean cycles 0 640.000000 320.000000 1.0 1 100.000000 50.000000 10.0 2 333.333333 166.666667 100.0 """ was_series = isinstance(five_segment_haigh_diagram, pd.Series) if was_series: five_segment_haigh_diagram = pd.DataFrame(five_segment_haigh_diagram).T index_names = five_segment_haigh_diagram.index.names + ["R"] def make_index(h): orig_index = [h.name] if not isinstance(h.name, Iterable) else list(h.name) return pd.MultiIndex.from_tuples( [ tuple(orig_index + [pd.Interval(1.0, np.inf)]), tuple(orig_index + [pd.Interval(-np.inf, 0.0)]), tuple(orig_index + [pd.Interval(0.0, h.R12)]), tuple(orig_index + [pd.Interval(h.R12, h.R23)]), tuple(orig_index + [pd.Interval(h.R23, 1.0)]), ], names=index_names, ).to_frame() haigh_index = pd.concat( list(five_segment_haigh_diagram.apply(make_index, axis=1)) ).index haigh = pd.Series(0.0, index=haigh_index) R_index = haigh.index.get_level_values("R") h, _ = Broadcaster(haigh).broadcast(five_segment_haigh_diagram) M4_locs = R_index.get_indexer_for([pd.Interval(1.0, np.inf)]) M0_locs = R_index.get_indexer_for([pd.Interval(-np.inf, 0.0)]) M1_locs = R_index.get_indexer_for([pd.Interval(0.0, R12) for R12 in h.R12]) M2_locs = R_index.get_indexer_for( [pd.Interval(R12, R23) for R12, R23 in zip(h.R12, h.R23)] ) M3_locs = R_index.get_indexer_for([pd.Interval(R23, 1.0) for R23 in h.R23]) haigh.iloc[M4_locs] = h.M4.iloc[M4_locs] haigh.iloc[M0_locs] = h.M0.iloc[M0_locs] haigh.iloc[M1_locs] = h.M1.iloc[M1_locs] haigh.iloc[M2_locs] = h.M2.iloc[M2_locs] haigh.iloc[M3_locs] = h.M3.iloc[M3_locs] if was_series: haigh = haigh.xs(0) return cls(haigh)
[docs] def transform(self, collective, R_goal): """Transform a load collective to defined R-value. Parameters ---------- collective : pd.DataFrame The load collective data to transform containing either `range` and `mean` or `from` and `to` columns to describe the load cycles. All other columns e.g. `cycles` are copied to the result. R_goal : float The target R-value for the transformation. Returns ------- pd.DataFrame DataFrame with columns: - 'range': transformed amplitude range - 'mean': transformed mean value - 'cycles': copied unchanged from the input if present Examples -------- >>> collective = pd.DataFrame( ... { ... "from": [300.0, -150.0, -250.0], ... "to": [-300.0, 150.0, 250.0], ... "cycles": [1.0, 10.0, 100.0], ... } ... ) >>> HaighDiagram.from_dict({(-np.inf, np.inf): 0.5}).transform(collective, 0.0) range mean cycles 0 400.000000 200.000000 1.0 1 200.000000 100.000000 10.0 2 333.333333 166.666667 100.0 >>> collective = pd.DataFrame( ... { ... "range": [600.0, 300.0, 500.0], ... "mean": [100.0, 50.0, 80.0], ... "cycles": [1.0, 10.0, 100.0], ... } ... ) >>> HaighDiagram.from_dict({(-np.inf, np.inf): 0.5}).transform(collective, -1.0) range mean cycles 0 700.0 0.0 1.0 1 350.0 0.0 10.0 2 580.0 0.0 100.0 """ broadcasted_coll, haigh = self.broadcast(collective, droplevel=["R"]) coll = CL.LoadCollective(broadcasted_coll) transformer = _SegmentTransformer(coll, haigh, self._R_index, R_goal) for interval in transformer.segments_left_from_R_goal(): interval_boundary = ( interval.right if interval.right < 1.0 else interval.left ) transformer.transform_cycles_in_interval(interval, interval_boundary) for interval in transformer.segments_right_from_R_goal(): transformer.transform_cycles_in_interval(interval, interval.left) for interval in transformer.segments_containing_R_goal(): transformer.transform_cycles_in_interval(interval, R_goal) transformed_cycles = transformer.transformed_cycles res = pd.DataFrame( { "range": 2.0 * transformed_cycles.amplitude, "mean": transformed_cycles.amplitude * ((1.0 + transformed_cycles.R) / (1.0 - transformed_cycles.R)).fillna( -1.0 ), }, index=broadcasted_coll.index, ) for col in collective: if col not in coll.columns: res[col] = collective[col] return res
def _validate(self): def has_gaps(idx): if len(idx) <= 1: return False return ( pd.DataFrame({"l": idx.left[1:], "r": idx.right[:-1]}) .apply( lambda r: r.l != r.r and not (r.l == -np.inf and r.r == np.inf), axis=1, ) .any() ) self._R_index = self._find_R_index() if self._check_if_R_index(lambda idx: idx.is_overlapping): raise AttributeError( "The intervals of the 'R' IntervalIndex must not overlap." ) if self._check_if_R_index(has_gaps): raise AttributeError( "The intervals of the 'R' IntervalIndex must not have gaps." ) def _find_R_index(self): if "R" not in self._obj.index.names: raise AttributeError("A Haigh Diagram needs an index level 'R'.") if isinstance(self._obj.index, pd.MultiIndex): R_index = self._obj.index.unique("R") else: R_index = self._obj.index if not isinstance(R_index, pd.IntervalIndex): raise AttributeError("The 'R' index must be an IntervalIndex.") return R_index def _check_if_R_index(self, check_func): if isinstance(self._obj.index, pd.IntervalIndex): return check_func(self._obj.index) all_but_R = [n or 0 for n in self._obj.index.names if n != "R"] return ( self._obj.index.to_frame(index=False) .groupby(all_but_R) .apply(lambda g: check_func(g.set_index("R").index), include_groups=False) .any() )
class _SegmentTransformer: def __init__(self, collective, haigh, R_segments, R_goal): self.transformed_cycles = pd.DataFrame( {"amplitude": collective.amplitude, "R": collective.R}, index=collective.to_pandas().index, ) self._haigh = haigh self._R_index = R_segments self._R_goal = R_goal self._distances = self._distance_from_R_goal() def segments_left_from_R_goal(self): return self._distances[self._distances < 0.0].sort_values(ascending=True).index def segments_right_from_R_goal(self): return self._distances[self._distances > 0.0].sort_values(ascending=False).index def segments_containing_R_goal(self): goal_segments = self._R_index.contains(self._R_goal) if not goal_segments.any(): goal_segments = self._R_index.set_closed("left").contains(self._R_goal) return self._R_index[goal_segments] def _distance_from_R_goal(self): def fake_meanstress(R): return (1.0 + R) / (1.0 - R) meanstress = fake_meanstress(self._R_index.mid).fillna(-1.0) meanstress_goal = ( -1.0 if self._R_goal == -np.inf else fake_meanstress(self._R_goal) ) return pd.Series(meanstress.values - meanstress_goal, index=self._R_index) def transform_cycles_in_interval(self, interval, R_goal): def push_over_flipping_point(R): if R == -np.inf and R_goal > 1.0: return np.inf if R == np.inf and R_goal < 1.0: return -np.inf return R def cycles_in_current_interval(): R = self.transformed_cycles.R.apply(push_over_flipping_point) test_interval = pd.Interval(interval.left, interval.right, closed="both") return R.apply(lambda R: R in test_interval) def cycles_in_current_segments(in_test_interval, segments_index): in_segments_index = pd.Series(False, index=self.transformed_cycles.index) in_segments_index[segments_index] = True return in_test_interval & in_segments_index def meanstress_sensitivity_segments_of_current_interval(): return self._haigh.xs(interval, level="R") def transformed_amplitude(): rf = self.transformed_cycles.loc[to_shift] amp = rf.amplitude mean = amp * (1.0 + rf.R) / (1.0 - rf.R) mean[rf.R == -np.inf] = -amp[rf.R == -np.inf] mean[rf.R == 1.0] = -amp[rf.R == 1.0] if R_goal == -np.inf: trans_amp = (amp + M * mean) / (1.0 - M) else: trans_amp = ( (1.0 - R_goal) * (amp + M * mean) / (1.0 - R_goal + M * (1.0 + R_goal)) ) return trans_amp.fillna(0.0) to_shift = cycles_in_current_interval() if not to_shift.any(): return M = meanstress_sensitivity_segments_of_current_interval() to_shift = cycles_in_current_segments(to_shift, M.index) if not to_shift.any(): return if R_goal == 1.0: R_goal = -np.inf self.transformed_cycles.loc[to_shift, "amplitude"] = transformed_amplitude() self.transformed_cycles.loc[to_shift, "R"] = R_goal
[docs] def experimental_mean_stress_sensitivity(sn_curve_R0, sn_curve_Rn1, N_c=np.inf): r"""Estimate the mean stress sensitivity from two `FiniteLifeCurve` objects for the same amount of cycles `N_c`. The formula for calculation is taken from: "Betriebsfestigkeit", Haibach, 3. Auflage 2006 Formula (2.1-24): .. math:: M_{\sigma} = {S_a}^{R=-1}(N_c) / {S_a}^{R=0}(N_c) - 1 Alternatively the mean stress sensitivity is calculated based on both SD values (if N_c is not given). Parameters ---------- sn_curve_R0: pylife.strength.sn_curve.FiniteLifeCurve Instance of FiniteLifeCurve for R == 0 sn_curve_Rn1: pylife.strength.sn_curve.FiniteLifeCurve Instance of FiniteLifeCurve for R == -1 N_c: float, (default=np.inf) Amount of cycles where the amplitudes should be compared. If N_c is higher than a fatigue transition point (ND) for the SN-Curves, SD is taken. If N_c is None, SD values are taken as stress amplitudes instead. Returns ------- float Mean stress sensitivity M_sigma Raises ------ ValueError if the resulting M_sigma doesn't lie in the range from 0 to 1 a ValueError is raised, as this value would suggest higher strength with additional loads. """ S_a_R0 = ( sn_curve_R0.woehler.basquin_load(N_c) if N_c < sn_curve_R0.ND else sn_curve_R0.SD ) S_a_Rn1 = ( sn_curve_Rn1.woehler.basquin_load(N_c) if N_c < sn_curve_Rn1.ND else sn_curve_Rn1.SD ) M_sigma = S_a_Rn1 / S_a_R0 - 1 if not 0 <= M_sigma <= 1: raise ValueError( "M_sigma: %.2f exceeds the interval [0, 1] which is not plausible." % M_sigma ) return M_sigma
[docs] @pd.api.extensions.register_dataframe_accessor("meanstress_transform") class MeanstressTransformCollective(CL.LoadCollective): """Meanstress transformer class for a load collective."""
[docs] def fkm_goodman(self, goodman, R_goal): """ Perform a FKM Goodman transformation on a load collective Parameters ---------- goodman: pd.Series or pd.DataFrame The meanstress sensitivity data needs `M` and optionally `M2` R_goal: float The R-value to transform to Returns ------- transformed_collective: LoadCollective The transformed load collective Examples -------- >>> collective = pd.DataFrame( ... { ... "from": [300.0, -150.0, -250.0], ... "to": [-300.0, 150.0, 250.0], ... "cycles": [1.0, 10.0, 100.0], ... } ... ) >>> collective.meanstress_transform.fkm_goodman(pd.Series({"M": 0.5}), 0.0).amplitude 0 200.000000 1 100.000000 2 166.666667 Name: amplitude, dtype: float64 """ res = HaighDiagram.fkm_goodman(goodman).transform(self._obj, R_goal) return res.load_collective
[docs] def five_segment(self, five_segment, R_goal): """ Perform a Five segment transformation on a load collective Parameters ---------- five_segment: pd.Series or pd.DataFrame The meanstress sensitivities and R transition values (see :meth:`HaighDiagram.five_segment`) R_goal: float The R-value to transform to Returns ------- transformed_collective: LoadCollective The transformed load collective. After the meanstress transformation, the resulting ``(range, mean)`` interval bins may no longer be continuous. Examples -------- >>> collective = pd.DataFrame( ... { ... "range": [600.0, 300.0, 500.0], ... "mean": [400.0, -150.0, 0.0], ... "cycles": [1.0, 10.0, 100.0], ... } ... ) >>> haigh = pd.Series( ... {"M0": 0.5, "M1": 0.25, "M2": 0.125, "M3": 1.0, "M4": -2.0, "R12": 0.2, "R23": 0.8} ... ) >>> transformed = collective.meanstress_transform.five_segment(haigh, 0.0) >>> transformed.amplitude 0 320.000000 1 50.000000 2 166.666667 Name: amplitude, dtype: float64 >>> transformed.meanstress 0 320.000000 1 50.000000 2 166.666667 Name: meanstress, dtype: float64 """ hd = HaighDiagram.five_segment(five_segment) res = hd.transform(self._obj, R_goal) return res.load_collective
[docs] @pd.api.extensions.register_series_accessor("meanstress_transform") class MeanstressTransformMatrix(CL.LoadHistogram): """Meanstress transformer class for a load histogram.""" def _validate(self): super()._validate() if set(self._obj.index.names).issuperset({"from", "to"}): f = self._obj.index.get_level_values("from").mid t = self._obj.index.get_level_values("to").mid self._Sa = np.abs(f - t) / 2.0 self._Sm = (f + t) / 2.0 self._binsize_x = self._obj.index.get_level_values("from").length.min() self._binsize_y = self._obj.index.get_level_values("to").length.min() self._remaining_names = list( filter(lambda n: n not in ["from", "to"], self._obj.index.names) ) else: self._Sa = self._obj.index.get_level_values("range").mid / 2.0 self._Sm = self._obj.index.get_level_values("mean").mid self._binsize_x = self._obj.index.get_level_values("range").length.min() self._binsize_y = self._obj.index.get_level_values("mean").length.min() self._remaining_names = list( filter(lambda n: n not in ["range", "mean"], self._obj.index.names) )
[docs] def fkm_goodman(self, goodman, R_goal): """ Perform a FKM Goodman transformation on a load histogram Parameters ---------- goodman: pd.Series or pd.DataFrame The meanstress sensitivity data needs `M` and optionally `M2`. R_goal: float The R-value to transform to Returns ------- transformed_histogram: LoadHistogram The transformed load histogram. After the meanstress transformation, the resulting ``(range, mean)`` interval bins may no longer be continuous. Notes ----- If continuous bins are required afterwards, e.g. for visualization, the transformed histogram can optionally be rebinned. Be aware that such a rebinning is a post-processing step for presentation purposes and may reduce the accuracy of the transformed histogram. Examples -------- >>> histogram = pd.Series( ... [10, 90, 900, 9000, 90000, 900000], ... index=pd.MultiIndex.from_arrays( ... [ ... pd.IntervalIndex.from_arrays( ... [650, 550, 450, 350, 250, 150], [750, 650, 550, 450, 350, 250] ... ), ... pd.IntervalIndex.from_arrays([0, 0, 0, 0 ,0, 0], [0, 0, 0, 0, 0, 0]), ... ], ... names=["range", "mean"], ... ), ... name="cycles" ... ) >>> transformed = histogram.meanstress_transform.fkm_goodman(pd.Series({"M": 0.0}), 0) >>> transformed.amplitude range mean (650.0, 750.0] (325.0, 375.0] 350.0 (550.0, 650.0] (275.0, 325.0] 300.0 (450.0, 550.0] (225.0, 275.0] 250.0 (350.0, 450.0] (175.0, 225.0] 200.0 (250.0, 350.0] (125.0, 175.0] 150.0 (150.0, 250.0] (75.0, 125.0] 100.0 Name: amplitude, dtype: float64 """ transformer = HaighDiagram.fkm_goodman(goodman) return self._perform_transformation(transformer, R_goal)
[docs] def five_segment(self, five_segment, R_goal): """ Perform a Five segment transformation on a load histogram Parameters ---------- five_segment: pd.Series or pd.DataFrame The meanstress sensitivities and R transition values (see :meth:`HaighDiagram.five_segment`) R_goal: float The R-value to transform to Returns ------- transformed_histogram: LoadHistogram The transformed load histogram. After the meanstress transformation, the resulting ``(range, mean)`` interval bins may no longer be continuous. Notes ----- If continuous bins are required afterwards, e.g. for visualization, the transformed histogram can optionally be rebinned. Be aware that such a rebinning is a post-processing step for presentation purposes and may reduce the accuracy of the transformed histogram. Examples -------- >>> histogram = pd.Series( ... [10, 90, 900, 9000, 90000, 900000], ... index=pd.MultiIndex.from_arrays( ... [ ... pd.IntervalIndex.from_arrays( ... [650, 550, 450, 350, 250, 150], [750, 650, 550, 450, 350, 250] ... ), ... pd.IntervalIndex.from_arrays([0, 0, 0, 0 ,0, 0], [0, 0, 0, 0, 0, 0]), ... ], ... names=["range", "mean"], ... ), ... name="cycles" ... ) >>> five_segments = pd.Series( ... { ... "M0": 0.5, ... "M1": 0.2, ... "M2": 0.1, ... "M3": 1.0, ... "M4": 2.0, ... "R12": 0.2, ... "R23": 0.8, ... } ... ) >>> transformed = histogram.meanstress_transform.five_segment(five_segments, 0) >>> transformed.amplitude range mean (433.3333333333333, 500.0] (216.66666666666666, 250.0] 233.333333 (366.6666666666667, 433.3333333333333] (183.33333333333334, 216.66666666666666] 200.000000 (300.0, 366.6666666666667] (150.0, 183.33333333333334] 166.666667 (233.33333333333334, 300.0] (116.66666666666667, 150.0] 133.333333 (166.66666666666669, 233.33333333333334] (83.33333333333334, 116.66666666666667] 100.000000 (100.0, 166.66666666666669] (50.0, 83.33333333333334] 66.666667 Name: amplitude, dtype: float64 """ transformer = HaighDiagram.five_segment(five_segment) return self._perform_transformation(transformer, R_goal)
def _perform_transformation(self, transformer, R_goal): mean_left = self.use_class_left().meanstress.reset_index(drop=True) mean_right = self.use_class_right().meanstress.reset_index(drop=True) range_index = self.amplitude_histogram.index range_left = 2.0 * range_index.left.to_series().reset_index(drop=True) range_right = 2.0 * range_index.right.to_series().reset_index(drop=True) orig_lele = pd.DataFrame({"range": range_left, "mean": mean_left}) orig_lere = pd.DataFrame({"range": range_left, "mean": mean_right}) orig_rele = pd.DataFrame({"range": range_right, "mean": mean_left}) orig_rere = pd.DataFrame({"range": range_right, "mean": mean_right}) transformed_lele = transformer.transform(orig_lele, R_goal) transformed_lere = transformer.transform(orig_lere, R_goal) transformed_rele = transformer.transform(orig_rele, R_goal) transformed_rere = transformer.transform(orig_rere, R_goal) transformed = self._obj.to_frame() have_additional_indeces = len(transformed.index.names) > 2 if have_additional_indeces: transformed.index = transformed.index.droplevel(self.index_levels) range = pd.DataFrame( { 0: transformed_lele["range"], 1: transformed_lere["range"], 2: transformed_rele["range"], 3: transformed_rere["range"], } ) transformed["range"] = pd.IntervalIndex.from_arrays( range.min(axis=1), range.max(axis=1), name="range" ) mean = pd.DataFrame( { 0: transformed_lele["mean"], 1: transformed_lere["mean"], 2: transformed_rele["mean"], 3: transformed_rere["mean"], } ) transformed["mean"] = pd.IntervalIndex.from_arrays( mean.min(axis=1), mean.max(axis=1), name="mean" ) result = transformed.set_index(["range", "mean"], append=have_additional_indeces, drop=True).iloc[:, 0] new_names = self._obj.index.to_frame().rename(columns={"from": "range", "to": "mean"}).columns result.index = result.index.reorder_levels(new_names) result.name = self._obj.name return CL.LoadHistogram(result)
[docs] def fkm_goodman(amplitude, meanstress, M, M2, R_goal): cycles = pd.DataFrame({"range": 2.0 * amplitude, "mean": meanstress}) haigh_fkm_goodman = pd.Series({"M": M, "M2": M2}) hd = HaighDiagram.fkm_goodman(haigh_fkm_goodman) res = hd.transform(cycles, R_goal) return res.load_collective.amplitude.to_numpy()
[docs] def five_segment_correction( amplitude, meanstress, M0, M1, M2, M3, M4, R12, R23, R_goal ): """Performs a mean stress transformation to R_goal according to the Five Segment Mean Stress Correction :param Sa: the stress amplitude :param Sm: the mean stress :param Rgoal: the R-value to transform to :param M: the mean stress sensitivity between R=-inf and R=0 :param M1: the mean stress sensitivity between R=0 and R=R12 :param M2: the mean stress sensitivity betwenn R=R12 and R=R23 :param M3: the mean stress sensitivity between R=R23 and R=1 :param M4: the mean stress sensitivity beyond R=1 :param R12: R-value between M1 and M2 :param R23: R-value between M2 and M3 :returns: the transformed stress range """ cycles = pd.DataFrame({"range": 2.0 * amplitude, "mean": meanstress}) haigh_five_segment = pd.Series( {"M0": M0, "M1": M1, "M2": M2, "M3": M3, "M4": M4, "R12": R12, "R23": R23} ) hd = HaighDiagram.five_segment(haigh_five_segment) res = hd.transform(cycles, R_goal) return res.load_collective.amplitude.to_numpy()