From ae8145e7b85f1ab62b66b3f7f03c654537ad3afc Mon Sep 17 00:00:00 2001 From: Dominic Olveda Date: Fri, 27 Sep 2024 12:50:34 +0200 Subject: [PATCH 1/2] Add function to use only highest pure runout level for analysis Signed-off-by: Dominic Olveda --- src/pylife/materialdata/woehler/elementary.py | 2 + .../materialdata/woehler/fatigue_data.py | 21 ++++++++++ tests/materialdata/woehler/test_analyzer.py | 40 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/pylife/materialdata/woehler/elementary.py b/src/pylife/materialdata/woehler/elementary.py index 49894b40..84a9b96a 100644 --- a/src/pylife/materialdata/woehler/elementary.py +++ b/src/pylife/materialdata/woehler/elementary.py @@ -61,6 +61,8 @@ def _get_fatigue_data(self, fatigue_data): params = fatigue_data else: raise ValueError("fatigue_data of type {} not understood: {}".format(type(fatigue_data), fatigue_data)) + params = params.irrelevant_runouts_dropped() + return params def analyze(self, **kwargs): diff --git a/src/pylife/materialdata/woehler/fatigue_data.py b/src/pylife/materialdata/woehler/fatigue_data.py index 301ea0c3..619cdeb4 100644 --- a/src/pylife/materialdata/woehler/fatigue_data.py +++ b/src/pylife/materialdata/woehler/fatigue_data.py @@ -116,6 +116,10 @@ def non_fractured_loads(self): def mixed_loads(self): return np.intersect1d(self.runout_loads, self.fractured_loads) + @property + def pure_runout_loads(self): + return np.setxor1d(self.runout_loads, self.mixed_loads) + def conservative_fatigue_limit(self): """ Sets a lower fatigue limit that what is expected from the algorithm given by Mustafa Kassem. @@ -160,6 +164,18 @@ def set_fatigue_limit(self, fatigue_limit): return self + def irrelevant_runouts_dropped(self): + '''Checks if data has several pure runout load levels and drops all except max pure runout level + + ''' + if len(self.pure_runout_loads) <= 1: + return self + if self.pure_runout_loads.max() < self.fractured_loads.min(): + df = self._obj[~(self._obj.load < self.pure_runout_loads.max())] + return FatigueData(df) + else: + return self + @property def max_runout_load(self): return self.runouts.load.max() @@ -189,6 +205,10 @@ def _calc_finite_zone_manual(self, limit): self._infinite_zone = self._obj[self._obj.load <= limit] + + + + def determine_fractures(df, load_cycle_limit=None): '''Adds a fracture column according to defined load cycle limit @@ -213,3 +233,4 @@ def determine_fractures(df, load_cycle_limit=None): ret = df.copy() ret['fracture'] = df.cycles < load_cycle_limit return ret + diff --git a/tests/materialdata/woehler/test_analyzer.py b/tests/materialdata/woehler/test_analyzer.py index 7ab44882..25fb9ae4 100644 --- a/tests/materialdata/woehler/test_analyzer.py +++ b/tests/materialdata/woehler/test_analyzer.py @@ -595,3 +595,43 @@ def test_max_likelihood_one_mixed_horizon(): wc = ml.analyze().sort_index() bic = ml.bayesian_information_criterion() pd.testing.assert_series_equal(wc, expected, rtol=1e-1) + + +def test_irrelevant_runouts_dropped(): + fd = woehler.determine_fractures(data_pure_runout_horizon_and_mixed_horizons, 1e7).fatigue_data + num_data_before = len(fd._obj) + num_tests_lowest_pure_runout_load_level = len(fd.load[fd.load==fd.pure_runout_loads.min()]) + fd = fd.irrelevant_runouts_dropped() + num_data_after = len(fd._obj) + assert num_data_before-num_tests_lowest_pure_runout_load_level == num_data_after + + +def test_irrelevant_runouts_dropped_no_change(): + data_pure_runout_horizon_and_mixed_horizons + data_extend = data_pure_runout_horizon_and_mixed_horizons.copy(deep=True) + new_row = {'load': 2.75e+02, 'cycles': 1.00e+06} + data_extend = pd.concat([data_extend, pd.DataFrame([new_row])])#, ignore_index=True) + fd = woehler.determine_fractures(data_extend, 1e7).fatigue_data + num_data_before = len(fd._obj) + fd = fd.irrelevant_runouts_dropped() + num_data_after = len(fd._obj) + assert num_data_before == num_data_after + + +def test_drop_irreverent_pure_runout_levels_no_data_change(): + expected = pd.Series({ + 'SD': 339.23834, + 'TS': 1.211044, + 'k_1': 9.880429, + 'ND': 804501, + 'TN': 6.779709, + 'failure_probability': 0.5 + }).sort_index() + + fd = woehler.determine_fractures(data_pure_runout_horizon_and_mixed_horizons, 1e7).fatigue_data + num_data_before = len(fd._obj) + wc = woehler.Probit(fd).analyze().sort_index() + num_data_after = len(fd._obj) + pd.testing.assert_series_equal(wc, expected, rtol=1e-1) + assert num_data_before == num_data_after + From 36fc5ad32b9041794e005d9fa57411c0d549a00d Mon Sep 17 00:00:00 2001 From: Dominic Olveda <167217875+old2rng@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:49:12 +0200 Subject: [PATCH 2/2] Add requested changes Signed-off-by: Johannes Mueller --- .../materialdata/woehler/fatigue_data.py | 10 +--------- tests/materialdata/woehler/test_analyzer.py | 18 +++++++++++------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/pylife/materialdata/woehler/fatigue_data.py b/src/pylife/materialdata/woehler/fatigue_data.py index 619cdeb4..60ac7ac7 100644 --- a/src/pylife/materialdata/woehler/fatigue_data.py +++ b/src/pylife/materialdata/woehler/fatigue_data.py @@ -165,9 +165,7 @@ def set_fatigue_limit(self, fatigue_limit): return self def irrelevant_runouts_dropped(self): - '''Checks if data has several pure runout load levels and drops all except max pure runout level - - ''' + '''Make a copy of the instance with irrelevant pure runout levels dropped. ''' if len(self.pure_runout_loads) <= 1: return self if self.pure_runout_loads.max() < self.fractured_loads.min(): @@ -204,11 +202,6 @@ def _calc_finite_zone_manual(self, limit): self._finite_zone = self.fractures[self.fractures.load > limit] self._infinite_zone = self._obj[self._obj.load <= limit] - - - - - def determine_fractures(df, load_cycle_limit=None): '''Adds a fracture column according to defined load cycle limit @@ -233,4 +226,3 @@ def determine_fractures(df, load_cycle_limit=None): ret = df.copy() ret['fracture'] = df.cycles < load_cycle_limit return ret - diff --git a/tests/materialdata/woehler/test_analyzer.py b/tests/materialdata/woehler/test_analyzer.py index 25fb9ae4..bac8eed7 100644 --- a/tests/materialdata/woehler/test_analyzer.py +++ b/tests/materialdata/woehler/test_analyzer.py @@ -599,8 +599,8 @@ def test_max_likelihood_one_mixed_horizon(): def test_irrelevant_runouts_dropped(): fd = woehler.determine_fractures(data_pure_runout_horizon_and_mixed_horizons, 1e7).fatigue_data - num_data_before = len(fd._obj) - num_tests_lowest_pure_runout_load_level = len(fd.load[fd.load==fd.pure_runout_loads.min()]) + num_data_before = 48 + num_tests_lowest_pure_runout_load_level = 9 fd = fd.irrelevant_runouts_dropped() num_data_after = len(fd._obj) assert num_data_before-num_tests_lowest_pure_runout_load_level == num_data_after @@ -610,7 +610,7 @@ def test_irrelevant_runouts_dropped_no_change(): data_pure_runout_horizon_and_mixed_horizons data_extend = data_pure_runout_horizon_and_mixed_horizons.copy(deep=True) new_row = {'load': 2.75e+02, 'cycles': 1.00e+06} - data_extend = pd.concat([data_extend, pd.DataFrame([new_row])])#, ignore_index=True) + data_extend = pd.concat([data_extend, pd.DataFrame([new_row])]) fd = woehler.determine_fractures(data_extend, 1e7).fatigue_data num_data_before = len(fd._obj) fd = fd.irrelevant_runouts_dropped() @@ -618,7 +618,7 @@ def test_irrelevant_runouts_dropped_no_change(): assert num_data_before == num_data_after -def test_drop_irreverent_pure_runout_levels_no_data_change(): +def test_drop_irreverent_pure_runout_levels_for_evaluation(): expected = pd.Series({ 'SD': 339.23834, 'TS': 1.211044, @@ -629,9 +629,13 @@ def test_drop_irreverent_pure_runout_levels_no_data_change(): }).sort_index() fd = woehler.determine_fractures(data_pure_runout_horizon_and_mixed_horizons, 1e7).fatigue_data - num_data_before = len(fd._obj) wc = woehler.Probit(fd).analyze().sort_index() - num_data_after = len(fd._obj) pd.testing.assert_series_equal(wc, expected, rtol=1e-1) - assert num_data_before == num_data_after + +def test_drop_irreverent_pure_runout_levels_no_data_change(): + fd = woehler.determine_fractures(data_pure_runout_horizon_and_mixed_horizons, 1e7).fatigue_data + num_data_before = len(fd._obj) + wc = woehler.Probit(fd).analyze() + num_data_after = len(fd._obj) + assert num_data_before == num_data_after