Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into develop
Browse files Browse the repository at this point in the history
Signed-off-by: Johannes Mueller <johannes.mueller4@de.bosch.com>
  • Loading branch information
johannes-mueller committed Oct 9, 2024
2 parents 7d2be41 + d3328a4 commit 6bec198
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 24 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
In this file noteworthy changes of new releases of pyLife are documented since
2.0.0.

## unreleased

### Improvements

* Sanitize checks for Wöhler analysis (#108)

### Bugfixes

* `MeshSignal` now allows for additional indeces (#111)


## pylife-2.1.1

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ based on pyLife code.

## Status

pyLife-2.0.3 has been released. That means that for the time being we hope
pyLife-2.1.1 has been released. That means that for the time being we hope
that we will not introduce *breaking* changes. That does not mean that the
release is stable finished and perfect. We will do small improvements,
especially with respect to documentation in the upcoming months and release
them as 2.0.x releases. Once we have noticeable feature additions we will come
them as 2.1.x releases. Once we have noticeable feature additions we will come
up with a 2.x.0 release. No ETA about that.

## Contents
Expand Down
22 changes: 18 additions & 4 deletions src/pylife/materialdata/woehler/elementary.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,16 @@ def analyze(self, **kwargs):
Arguments to be passed to the derived class
"""
if len(self._fd.load.unique()) < 2:
raise ValueError("Need at least two different load levels in the finite zone to do a Wöhler slope analysis.")
elif len(self._fd.finite_zone.load.unique()) < 2:
warnings.warn(UserWarning("Need at least two different load levels in the finite zone to do a Wöhler slope analysis."
" "))
raise ValueError(
"Need at least two different load levels in the finite zone to do a Wöhler slope analysis."
)
self._raise_if_no_cycle_variance_in_finite_zone()
if len(self._fd.finite_zone.load.unique()) < 2:
warnings.warn(
UserWarning(
"Need at least two different load levels in the finite zone to do a Wöhler slope analysis."
)
)
if len(self._fd.finite_zone.load.unique()) == 1:
wc = pd.Series({
'k_1': np.nan,
Expand Down Expand Up @@ -106,6 +112,14 @@ def analyze(self, **kwargs):

return wc

def _raise_if_no_cycle_variance_in_finite_zone(self):
finite_zone = self._fd.finite_zone
finite_fractures_cycles = finite_zone.loc[finite_zone['fracture'], 'cycles']
if finite_fractures_cycles.max() == finite_fractures_cycles.min():
raise ValueError(
"Cycle numbers must spread in finite zone to do a Wöhler slope analysis."
)

def _common_analysis(self):
self._slope, self._lg_intercept = self._fit_slope()
TN, TS = self._pearl_chain_method()
Expand Down
8 changes: 6 additions & 2 deletions src/pylife/materialdata/woehler/fatigue_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,20 @@

@pd.api.extensions.register_dataframe_accessor('fatigue_data')
class FatigueData(PylifeSignal):
''' class for fatigue data
"""Class for fatigue data
Mandatory keys are
* ``load`` : float, the load level
* ``cycles`` : float, the cycles of failure or runout
* ``fracture``: bool, ``True`` iff the test is a fracture
'''
"""

def _validate(self):
self.fail_if_key_missing(['load', 'cycles', 'fracture'])
if not self._obj.fracture.any():
raise ValueError("Need at least one fracture.")
if self.fractures.cycles.max() == self.fractures.cycles.min():
raise ValueError("There must be a variance in fracture cycles.")
self._finite_infinite_transition = None

@property
Expand Down
6 changes: 4 additions & 2 deletions src/pylife/mesh/meshsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,10 @@ class Mesh(PlainMesh):
def _validate(self):
super()._validate()
self._cached_element_groups = None
if set(self._obj.index.names) != set(['element_id', 'node_id']):
raise AttributeError("A mesh needs a pd.MultiIndex with the names `element_id` and `node_id`")
if not set(self._obj.index.names).issuperset(['element_id', 'node_id']):
raise AttributeError(
"A mesh needs a pd.MultiIndex with the names `element_id` and `node_id`"
)


@property
Expand Down
65 changes: 56 additions & 9 deletions tests/materialdata/woehler/test_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def test_woehler_elementary_no_runouts():

def test_woehler_elementary_only_one_load_level():
data = pd.DataFrame(np.array([[350.0, 1e7], [350.0, 1e6]]), columns=['load', 'cycles'])
fd = woehler.determine_fractures(data, 1e7).fatigue_data
fd = woehler.determine_fractures(data, 1e8).fatigue_data
with pytest.raises(ValueError, match=r"Need at least two different load levels in the finite zone to do a Wöhler slope analysis."):
woehler.Elementary(fd).analyze().sort_index()

Expand All @@ -264,9 +264,15 @@ def test_woehler_elementary_only_one_load_level_in_finite_region():
'failure_probability': 0.5
}).sort_index()

data = pd.DataFrame(np.array([[350.0, 1e7], [350.0, 1e6], [360.0, 1e6]]), columns=['load', 'cycles'])
data = pd.DataFrame(
np.array([[350.0, 1e7], [350.0, 2e6], [360.0, 1e6], [360, 5e5]]),
columns=['load', 'cycles'],
)
fd = woehler.determine_fractures(data, 1e7).fatigue_data
with pytest.warns(UserWarning, match=r"Need at least two different load levels in the finite zone to do a Wöhler slope analysis."):
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.Elementary(fd).analyze().sort_index()
pd.testing.assert_series_equal(wc, expected)

Expand All @@ -281,9 +287,15 @@ def test_woehler_elementary_no_load_level_in_finite_region():
'failure_probability': 0.5
}).sort_index()

data = pd.DataFrame(np.array([[350.0, 1e7], [350.0, 1e6], [360.0, 1e7]]), columns=['load', 'cycles'])
data = pd.DataFrame(
np.array([[350.0, 1e7], [350.0, 1e6], [350.0, 2e6], [360.0, 1e7]]),
columns=['load', 'cycles'],
)
fd = woehler.determine_fractures(data, 1e7).fatigue_data
with pytest.warns(UserWarning, match=r"Need at least two different load levels in the finite zone to do a Wöhler slope analysis."):
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.Elementary(fd).analyze().sort_index()
pd.testing.assert_series_equal(wc, expected)

Expand All @@ -297,7 +309,11 @@ def test_woehler_elementary_set_finite_infinite_transition_low():
'TS': 1.21,
'failure_probability': 0.5
}).sort_index()
fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=350.)
fd = (
woehler.determine_fractures(data, 1e7)
.sort_index()
.fatigue_data.set_finite_infinite_transition(finite_infinite_transition=350.0)
)
wc = woehler.Elementary(fd).analyze().sort_index()
pd.testing.assert_series_equal(wc, expected, rtol=1e-1)

Expand All @@ -311,7 +327,11 @@ def test_woehler_elementary_set_finite_infinite_transition_high():
'TS': 1.22,
'failure_probability': 0.5
}).sort_index()
fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.)
fd = (
woehler.determine_fractures(data, 1e7)
.sort_index()
.fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.0)
)
wc = woehler.Elementary(fd).analyze().sort_index()
pd.testing.assert_series_equal(wc, expected, rtol=1e-1)

Expand Down Expand Up @@ -341,7 +361,11 @@ def test_woehler_probit_set_finite_infinite_transition():
'failure_probability': 0.5
}).sort_index()

fd = woehler.determine_fractures(data, 1e7).sort_index().fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.)
fd = (
woehler.determine_fractures(data, 1e7)
.sort_index()
.fatigue_data.set_finite_infinite_transition(finite_infinite_transition=376.0)
)
wc = woehler.Probit(fd).analyze().sort_index()
pd.testing.assert_series_equal(wc, expected, rtol=1e-1)

Expand Down Expand Up @@ -576,7 +600,7 @@ def assert_positive_or_nan_but_not_zero(x):
def test_max_likelihood_min_three_fractures_on_two_load_levels(invalid_data):
fd = woehler.determine_fractures(invalid_data, 1e7).fatigue_data
ml = woehler.MaxLikeFull(fatigue_data=fd)
with pytest.raises(ValueError, match=r"^.*[N|n]eed at least.*" ):
with pytest.raises(ValueError, match=r"^.*[N|n]eed at least.*"):
ml.analyze()


Expand Down Expand Up @@ -640,3 +664,26 @@ def test_drop_irreverent_pure_runout_levels_no_data_change():
wc = woehler.Probit(fd).analyze()
num_data_after = len(fd._obj)
assert num_data_before == num_data_after


# GH-108
def test_at_least_one_fracture():
data = pd.DataFrame({'cycles': [1e7, 1e7], 'load': [320, 360]})
with pytest.raises(ValueError, match=r"^.*[N|n]eed at least one fracture."):
woehler.determine_fractures(data, 1e7).fatigue_data


# GH-108
def test_fracture_cycle_spread():
data = pd.DataFrame({'cycles': [1e5, 1e5], 'load': [320, 360]})
with pytest.raises(ValueError, match=r"There must be a variance in fracture cycles."):
woehler.determine_fractures(data, 1e7).fatigue_data


def test_fracture_finite_zone_spread():
data = pd.DataFrame(
{'cycles': [1e7, 1e7, 3e6, 1e5, 1e5], 'load': [280, 280, 280, 360, 380]}
)
fd = woehler.determine_fractures(data, 1e7).fatigue_data
with pytest.raises(ValueError, match=r"Cycle numbers must spread in finite zone"):
woehler.Elementary(fd).analyze()
41 changes: 36 additions & 5 deletions tests/mesh/test_meshsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,61 @@ def test_plain_mesh_fail():
def test_mesh_3d():
mi = pd.MultiIndex.from_tuples([(1, 1)], names=['element_id', 'node_id'])
df = pd.DataFrame({'x': [1.0], 'y': [2.0], 'z': [3.0], 'a': [9.9]}).set_index(mi)
pd.testing.assert_frame_equal(df.mesh.coordinates,
pd.DataFrame({'x': [1.0], 'y': [2.0], 'z': [3.0]}).set_index(mi))
pd.testing.assert_frame_equal(
df.mesh.coordinates,
pd.DataFrame({'x': [1.0], 'y': [2.0], 'z': [3.0]}).set_index(mi),
)


def test_mesh_2d():
mi = pd.MultiIndex.from_tuples([(1, 1)], names=['element_id', 'node_id'])
df = pd.DataFrame({'x': [1.0], 'y': [2.0], 'c': [3.0], 'a': [9.9]}).set_index(mi)
pd.testing.assert_frame_equal(df.mesh.coordinates,
pd.DataFrame({'x': [1.0], 'y': [2.0]}).set_index(mi))
pd.testing.assert_frame_equal(
df.mesh.coordinates, pd.DataFrame({'x': [1.0], 'y': [2.0]}).set_index(mi)
)


# GH-111
def test_mesh_additional_index():
mi = pd.MultiIndex.from_tuples([(1, 1, 1)], names=['element_id', 'node_id', 'additional'])
df = pd.DataFrame({'x': [1.0], 'y': [2.0], 'c': [3.0], 'a': [9.9]}).set_index(mi)
pd.testing.assert_frame_equal(
df.mesh.coordinates, pd.DataFrame({'x': [1.0], 'y': [2.0]}).set_index(mi)
)


# GH-111
def test_mesh_fail_coordinates():
mi = pd.MultiIndex.from_tuples([(1, 1)], names=['element_id', 'node_id'])
df = pd.DataFrame({'x': [1.0], 'e': [2.0], 'c': [3.0], 'a': [9.9]}).set_index(mi)
with pytest.raises(AttributeError, match=r'Mesh.*Missing y'):
df.mesh.coordinates


def test_mesh_fail_index():
def test_mesh_fail_index_missing_both():
df = pd.DataFrame({'x': [1.0], 'y': [2.0], 'z': [3.0], 'a': [9.9]})
with pytest.raises(AttributeError, match=r'.*element_id.*'):
df.mesh.coordinates


def test_mesh_fail_index_missing_element():
df = pd.DataFrame(
{'x': [1.0], 'y': [2.0], 'z': [3.0], 'a': [9.9]},
index=pd.Index([1], name="node_id"),
)
with pytest.raises(AttributeError, match=r'.*element_id.*'):
df.mesh.coordinates


def test_mesh_fail_index_missing_node():
df = pd.DataFrame(
{'x': [1.0], 'y': [2.0], 'z': [3.0], 'a': [9.9]},
index=pd.Index([1], name="element_id"),
)
with pytest.raises(AttributeError, match=r'.*element_id.*'):
df.mesh.coordinates


def test_connectivity():
mi = pd.MultiIndex.from_tuples([(1, 2), (1, 1), (1, 3),
(2, 5), (2, 4), (2, 6)], names=['element_id', 'node_id'])
Expand Down

0 comments on commit 6bec198

Please sign in to comment.