diff --git a/demos/woehler_analyzer.ipynb b/demos/woehler_analyzer.ipynb index eae4abd6..187f2522 100644 --- a/demos/woehler_analyzer.ipynb +++ b/demos/woehler_analyzer.ipynb @@ -114,9 +114,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Before we do the actual analysis, we make some preparations like, guessing the initial `fatigue_limit`, distinguishing between `runouts` and `fractures` and between `infinite_zone` and `finite_zone`." + "Before we do the actual analysis, we make some preparations like, guessing the initial `finite_infinite_transition` load level, distinguishing between `runouts` and `fractures` and between `infinite_zone` and `finite_zone`." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, { "cell_type": "code", "execution_count": null, @@ -124,7 +129,7 @@ "outputs": [], "source": [ "fatigue_data = df.fatigue_data\n", - "fatigue_data.fatigue_limit" + "fatigue_data.finite_infinite_transition" ] }, { @@ -146,7 +151,7 @@ "go.Figure([\n", " go.Scatter(x=finite_zone.cycles, y=finite_zone.load, mode='markers', name='finite'),\n", " go.Scatter(x=infinite_zone.cycles, y=infinite_zone.load, mode='markers', name='infinite'),\n", - " go.Scatter(x=[df.cycles.min(), df.cycles.max()], y=[fatigue_data.fatigue_limit]*2, mode='lines', name='fatigue limit')\n", + " go.Scatter(x=[df.cycles.min(), df.cycles.max()], y=[fatigue_data.finite_infinite_transition]*2, mode='lines', name='fatigue limit')\n", "]).update_xaxes(type='log').update_yaxes(type='log').update_layout(xaxis_title='Cycles', yaxis_title='Load')" ] }, @@ -169,7 +174,7 @@ "fig = go.Figure([\n", " go.Scatter(x=fractures.cycles, y=fractures.load, mode='markers', name='fractures'),\n", " go.Scatter(x=runouts.cycles, y=runouts.load, mode='markers', name='runouts'),\n", - " go.Scatter(x=[df.cycles.min(), df.cycles.max()], y=[fatigue_data.fatigue_limit]*2, mode='lines', name='fatigue limit')\n", + " go.Scatter(x=[df.cycles.min(), df.cycles.max()], y=[fatigue_data.finite_infinite_transition]*2, mode='lines', name='fatigue limit')\n", "]).update_xaxes(type='log').update_yaxes(type='log').update_layout(xaxis_title='Cycles', yaxis_title='Load')\n", "fig" ] @@ -207,9 +212,9 @@ "elementary_fig = copy.deepcopy(fig)\n", "\n", "elementary_fig.add_scatter(x=cycles, y=wc.basquin_load(cycles), mode='lines', name='Elementary 50%')\n", - "elementary_fig.add_scatter(x=cycles, y=wc.basquin_load(cycles, failure_probability=0.1), \n", + "elementary_fig.add_scatter(x=cycles, y=wc.basquin_load(cycles, failure_probability=0.1),\n", " mode='lines', name='Elementary 10%')\n", - "elementary_fig.add_scatter(x=cycles, y=wc.basquin_load(cycles, failure_probability=0.9), \n", + "elementary_fig.add_scatter(x=cycles, y=wc.basquin_load(cycles, failure_probability=0.9),\n", " mode='lines', name='Elementary 90%')\n", "\n", "elementary_fig" @@ -376,13 +381,6 @@ "\n", "maxlike_fixed_fig" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/src/pylife/materialdata/woehler/elementary.py b/src/pylife/materialdata/woehler/elementary.py index 84a9b96a..6ac1528b 100644 --- a/src/pylife/materialdata/woehler/elementary.py +++ b/src/pylife/materialdata/woehler/elementary.py @@ -111,8 +111,8 @@ def _common_analysis(self): TN, TS = self._pearl_chain_method() return pd.Series({ 'k_1': -self._slope, - 'ND': self._transition_cycles(self._fd.fatigue_limit), - 'SD': self._fd.fatigue_limit, + 'ND': self._transition_cycles(self._fd.finite_infinite_transition), + 'SD': self._fd.finite_infinite_transition, 'TN': TN, 'TS': TS }) @@ -148,11 +148,11 @@ def _fit_slope(self): return slope, lg_intercept - def _transition_cycles(self, fatigue_limit): - # FIXME Elementary means fatigue_limit == 0 -> np.inf - if fatigue_limit == 0: - fatigue_limit = 0.1 - return 10**(self._lg_intercept + self._slope * (np.log10(fatigue_limit))) + def _transition_cycles(self, finite_infinite_transition): + # FIXME Elementary means finite_infinite_transition == 0 -> np.inf + if finite_infinite_transition == 0: + finite_infinite_transition = 0.1 + return 10**(self._lg_intercept + self._slope * (np.log10(finite_infinite_transition))) def _pearl_chain_method(self): self._pearl_chain_estimator = PearlChainProbability(self._finite_fractures, self._slope) diff --git a/src/pylife/materialdata/woehler/fatigue_data.py b/src/pylife/materialdata/woehler/fatigue_data.py index 60ac7ac7..aa086d88 100644 --- a/src/pylife/materialdata/woehler/fatigue_data.py +++ b/src/pylife/materialdata/woehler/fatigue_data.py @@ -36,7 +36,7 @@ class FatigueData(PylifeSignal): def _validate(self): self.fail_if_key_missing(['load', 'cycles', 'fracture']) - self._fatigue_limit = None + self._finite_infinite_transition = None @property def num_tests(self): @@ -74,7 +74,7 @@ def cycles(self): return self._obj.cycles @property - def fatigue_limit(self): + def finite_infinite_transition(self): '''The start value of the load endurance limit. It is determined by searching for the lowest load level before the @@ -82,22 +82,22 @@ def fatigue_limit(self): runout appears. Then the median of the two load levels is the start value. ''' - if self._fatigue_limit is None: - self._calc_fatigue_limit() - return self._fatigue_limit + if self._finite_infinite_transition is None: + self._calc_finite_infinite_transition() + return self._finite_infinite_transition @property def finite_zone(self): - '''All the tests with load levels above ``fatigue_limit``, i.e. the finite zone''' - if self._fatigue_limit is None: - self._calc_fatigue_limit() + '''All the tests with load levels above ``finite_infinite_transition``, i.e. the finite zone''' + if self._finite_infinite_transition is None: + self._calc_finite_infinite_transition() return self._finite_zone @property def infinite_zone(self): - '''All the tests with load levels below ``fatigue_limit``, i.e. the infinite zone''' - if self._fatigue_limit is None: - self._calc_fatigue_limit() + '''All the tests with load levels below ``finite_infinite_transition``, i.e. the infinite zone''' + if self._finite_infinite_transition is None: + self._calc_finite_infinite_transition() return self._infinite_zone @property @@ -120,7 +120,7 @@ def mixed_loads(self): def pure_runout_loads(self): return np.setxor1d(self.runout_loads, self.mixed_loads) - def conservative_fatigue_limit(self): + def conservative_finite_infinite_transition(self): """ Sets a lower fatigue limit that what is expected from the algorithm given by Mustafa Kassem. For calculating the fatigue limit, all amplitudes where runouts and fractures are present are collected. @@ -141,26 +141,26 @@ def conservative_fatigue_limit(self): amps_to_consider = np.concatenate((amps_to_consider, [self.non_fractured_loads.max()])) if len(amps_to_consider) > 0: - self._fatigue_limit = amps_to_consider.mean() + self._finite_infinite_transition = amps_to_consider.mean() self._calc_finite_zone() return self - def set_fatigue_limit(self, fatigue_limit): + def set_finite_infinite_transition(self, finite_infinite_transition): """ Allows the user to set an arbitrary fatigue limit. Parameters ---------- - fatigue_limit : float + finite_infinite_transition : float The fatigue limit for separating the finite and infinite zone is set. Returns ------- self """ - self._fatigue_limit = fatigue_limit - self._calc_finite_zone_manual(fatigue_limit) + self._finite_infinite_transition = finite_infinite_transition + self._calc_finite_zone_manual(finite_infinite_transition) return self @@ -178,9 +178,9 @@ def irrelevant_runouts_dropped(self): def max_runout_load(self): return self.runouts.load.max() - def _calc_fatigue_limit(self): + def _calc_finite_infinite_transition(self): self._calc_finite_zone() - self._fatigue_limit = 0.0 if len(self.runouts) == 0 else self._half_level_above_highest_runout() + self._finite_infinite_transition = 0.0 if len(self.runouts) == 0 else self._half_level_above_highest_runout() def _half_level_above_highest_runout(self): if len(self._finite_zone) > 0: diff --git a/src/pylife/materialdata/woehler/maxlike.py b/src/pylife/materialdata/woehler/maxlike.py index 0dcf1aee..36844700 100644 --- a/src/pylife/materialdata/woehler/maxlike.py +++ b/src/pylife/materialdata/woehler/maxlike.py @@ -63,7 +63,7 @@ def fail_if_less_than_three_fractures(): fail_if_less_than_two_mixed_levels() fail_if_less_than_three_fractures() - SD_start = self._fd.fatigue_limit + SD_start = self._fd.finite_infinite_transition TS_start = 1.2 var_opt = optimize.fmin(lambda p: -self._lh.likelihood_infinite(p[0], p[1]), diff --git a/tests/materialdata/woehler/test_analyzer.py b/tests/materialdata/woehler/test_analyzer.py index bac8eed7..de3816d0 100644 --- a/tests/materialdata/woehler/test_analyzer.py +++ b/tests/materialdata/woehler/test_analyzer.py @@ -81,10 +81,10 @@ def test_fatigue_data_finite_infinite_zone(data, finite_zone_expected, infinite_ no_runouts_infinite_expected) ]) def test_fatigue_data_finite_infinite_zone_conservative(data, finite_zone_expected, infinite_zone_expected): - fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.conservative_fatigue_limit() + fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.conservative_finite_infinite_transition() pd.testing.assert_frame_equal(sort_fatigue_data(fd.finite_zone)[['load', 'cycles']], sort_fatigue_data(finite_zone_expected)) - fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.conservative_fatigue_limit() + fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.conservative_finite_infinite_transition() pd.testing.assert_frame_equal(sort_fatigue_data(fd.infinite_zone)[['load', 'cycles']], sort_fatigue_data(infinite_zone_expected)) @@ -103,7 +103,7 @@ def test_fatigue_data_finite_infinite_zone_conservative(data, finite_zone_expect fat_limit_min) ]) def test_fatigue_data_finite_infinite_zone_manual_setting(data, finite_zone_expected, infinite_zone_expected, fat_limit): - fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_fatigue_limit(fatigue_limit=fat_limit) + fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=fat_limit) pd.testing.assert_frame_equal(sort_fatigue_data(fd.finite_zone)[['load', 'cycles']], sort_fatigue_data(finite_zone_expected)) pd.testing.assert_frame_equal(sort_fatigue_data(fd.infinite_zone)[['load', 'cycles']], @@ -157,35 +157,35 @@ def test_woehler_fracture_determination_infered(): pd.testing.assert_frame_equal(test, expected) -@pytest.mark.parametrize("data, fatigue_limit_expected", [ +@pytest.mark.parametrize("data, finite_infinite_transition_expected", [ (data, 362.5), (data_no_mixed_horizons, 362.5), (data_pure_runout_horizon_and_mixed_horizons, 362.5), (data_no_runouts, 0.0), (data_only_runout_levels, 362.5) ]) -def test_woehler_endur_zones(data, fatigue_limit_expected): +def test_woehler_endur_zones(data, finite_infinite_transition_expected): fd = woehler.determine_fractures(data, 1e7).fatigue_data - assert fd.fatigue_limit == fatigue_limit_expected + assert fd.finite_infinite_transition == finite_infinite_transition_expected -@pytest.mark.parametrize("data, fatigue_limit_expected", [ +@pytest.mark.parametrize("data, finite_infinite_transition_expected", [ (data, 325.0), (data_no_mixed_horizons, 350.0), (data_pure_runout_horizon_and_mixed_horizons, 325.0), (data_no_runouts, 0.0), (data_only_runout_levels, 325.0) ]) -def test_woehler_endur_zones_conservative(data, fatigue_limit_expected): +def test_woehler_endur_zones_conservative(data, finite_infinite_transition_expected): fd = woehler.determine_fractures(data, 1e7).fatigue_data - fd = fd.conservative_fatigue_limit() - assert fd.fatigue_limit == fatigue_limit_expected + fd = fd.conservative_finite_infinite_transition() + assert fd.finite_infinite_transition == finite_infinite_transition_expected def test_woehler_endure_zones_no_runouts(): df = data[data.cycles < 1e7] fd = woehler.determine_fractures(df, 1e7).fatigue_data - assert fd.fatigue_limit == 0.0 + assert fd.finite_infinite_transition == 0.0 def test_woehler_elementary(): @@ -287,7 +287,7 @@ def test_woehler_elementary_no_load_level_in_finite_region(): pd.testing.assert_series_equal(wc, expected) -def test_woehler_elementary_set_fatigue_limit_low(): +def test_woehler_elementary_set_finite_infinite_transition_low(): expected = pd.Series({ 'SD': 350.0, 'k_1': 9.88, @@ -296,12 +296,12 @@ def test_woehler_elementary_set_fatigue_limit_low(): 'TS': 1.21, 'failure_probability': 0.5 }).sort_index() - fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_fatigue_limit(fatigue_limit=350.) + fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=350.) wc = woehler.Elementary(fd).analyze().sort_index() pd.testing.assert_series_equal(wc, expected, rtol=1e-1) -def test_woehler_elementary_set_fatigue_limit_high(): +def test_woehler_elementary_set_finite_infinite_transition_high(): expected = pd.Series({ 'SD': 376., 'k_1': 7.07, @@ -310,7 +310,7 @@ def test_woehler_elementary_set_fatigue_limit_high(): 'TS': 1.22, 'failure_probability': 0.5 }).sort_index() - fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_fatigue_limit(fatigue_limit=376.) + fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.) wc = woehler.Elementary(fd).analyze().sort_index() pd.testing.assert_series_equal(wc, expected, rtol=1e-1) @@ -330,7 +330,7 @@ def test_woehler_probit(): pd.testing.assert_series_equal(wc, expected, rtol=1e-1) -def test_woehler_probit_set_fatigue_limit(): +def test_woehler_probit_set_finite_infinite_transition(): expected = pd.Series({ 'SD': 340.4, 'TS': 1.21, @@ -340,7 +340,7 @@ def test_woehler_probit_set_fatigue_limit(): 'failure_probability': 0.5 }).sort_index() - fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_fatigue_limit(fatigue_limit=376.) + fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.) wc = woehler.Probit(fd).analyze().sort_index() pd.testing.assert_series_equal(wc, expected, rtol=1e-1) @@ -398,7 +398,7 @@ def test_woehler_probit_no_finite_zone_data(): }).sort_index() data_no_finite_zone = data[data['load']<376.].copy(deep=True) - fd = woehler.determine_fractures(data_no_finite_zone, 1e7).sort_index().fatigue_data.set_fatigue_limit(fatigue_limit=376.) + fd = woehler.determine_fractures(data_no_finite_zone, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.) with pytest.warns(UserWarning, match=r"Need at least two different load levels in the finite zone to do a Wöhler slope analysis."): wc = woehler.Probit(fd).analyze().sort_index() pd.testing.assert_series_equal(wc, expected, rtol=1e-1) @@ -419,7 +419,7 @@ def test_woehler_max_likelihood_inf_limit(): pd.testing.assert_series_equal(wc, expected, rtol=1e-1) -def test_woehler_max_likelihood_inf_limit_set_fatigue_limit(): +def test_woehler_max_likelihood_inf_limit_set_finite_infinite_transition(): expected = pd.Series({ 'SD': 333.6, 'TS': 1.22, @@ -429,7 +429,7 @@ def test_woehler_max_likelihood_inf_limit_set_fatigue_limit(): 'failure_probability': 0.5 }).sort_index() - fd = woehler.determine_fractures(data, 1e7).fatigue_data.set_fatigue_limit(fatigue_limit=376.) + fd = woehler.determine_fractures(data, 1e7).fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.) wc = woehler.MaxLikeInf(fd).analyze().sort_index() pd.testing.assert_series_equal(wc, expected, rtol=1e-1) @@ -445,7 +445,7 @@ def test_woehler_max_likelihood_inf_limit_no_finite_zone_data(): }).sort_index() data_no_finite_zone = data[data['load']<376.].copy(deep=True) - fd = woehler.determine_fractures(data_no_finite_zone, 1e7).sort_index().fatigue_data.set_fatigue_limit(fatigue_limit=376.) + fd = woehler.determine_fractures(data_no_finite_zone, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.) with pytest.warns(UserWarning, match=r"Need at least two different load levels in the finite zone to do a Wöhler slope analysis."): wc = woehler.MaxLikeInf(fd).analyze().sort_index() pd.testing.assert_series_equal(wc, expected, rtol=1e-1)