Uniaxial equivalent stress amplitude functionality#55
Uniaxial equivalent stress amplitude functionality#55KarasTomas wants to merge 11 commits intofaberorg:mainfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
typo in the file name iniaxial
| from numpy.typing import ArrayLike, NDArray | ||
|
|
||
|
|
||
| def _validate_stress_inputs( |
There was a problem hiding this comment.
This function seems to implement DRY principle, but to me seems a bit clunky and hard to extend
reason might be, that it is trying to do multiple things, so it does not have 1 responsibility only, thus it branches in its logic and is harder to fit to use cases.
think it through if it could not be improved
| for compressive-dominated loading conditions. | ||
|
|
||
| """ | ||
| stress_amp_arr, mean_stress_arr = _validate_stress_inputs(stress_amp, mean_stress) |
There was a problem hiding this comment.
I would convert ArrayLike to np.ndarray here and check for positive values
It is more explicit and does not create too much boilerplate code
In this case validate function seems as overkill to me
There was a problem hiding this comment.
I am not saying that we have to do that. Just going through cases of usage of validate function
| "appropriate for compressive-dominated loading conditions." | ||
| ) | ||
|
|
||
| return np.sqrt(stress_amp_arr * (mean_stress_arr + stress_amp_arr)) |
There was a problem hiding this comment.
** 0.5 might be more readable than np.sqrt but it does not matter as much, just is more consistent with the mathematical formulas
| ratio = abs_mean / material_param_arr | ||
|
|
||
| if np.any(ratio >= 1.0): | ||
| raise ValueError( |
There was a problem hiding this comment.
what about case, where user inputs real values, where some are simply too high, so some results are negative?
Is it okay to disallow user from calculating all values ?
I personally would rather raise warning and exclude it from this validation function
| ("mean_equals_material", False, "Mean stress magnitude.*exceeds or equals"), | ||
| ], | ||
| ) | ||
| def test_validate_stress_inputs_parametrized( |
There was a problem hiding this comment.
validate function requires more then it brings value
There was a problem hiding this comment.
I would recommend using test classes to separate different test baste on function tested
There was a problem hiding this comment.
Test class than would be more generic, where each test case within the test class repeats for each tested function
class TestFunctioon1:
def test_feature1():
pass
def test_feature2(): ...
# now same thing for class TestFunction2
There was a problem hiding this comment.
it puts a bit more structure into the test file, so it is easier to navigate
also you can run tests based on the test class and more
|
Additionally the things should be added / considered:
|
|
On top of equivalent stress amplitude functionality requested by issues, several other functions were added based on this source: Mean stress effect in stress-life fatigue prediction re-evaluated Use-case info blocks in docstrings still need to be revised. |
pavkukula
left a comment
There was a problem hiding this comment.
SWT's validity condition σ_a > |σ_m| is incorrect — it rejects R≥0 loading, which is the canonical SWT case; should be σ_a + σ_m > 0.
invalid = (stress_amp_arr + mean_stress_arr) <= 0
| rules for the input arrays. | ||
|
|
||
| Raises: | ||
| Warning: If mean stress exceeds half of the ultimate tensile strength. |
There was a problem hiding this comment.
If mean stress exceeds DOUBLE of the ultimate tensile strength.
| Raises: | ||
| Warning: If mean stress exceeds half of the ultimate tensile strength. | ||
| ValueError: If ultimate tensile strength is not positive. | ||
| ValueError: If mean stress is equal to half of the ultimate tensile strength, |
| ratio = mean_stress_arr / (2 * ult_stress_arr) | ||
| if np.any(ratio == 1.0): | ||
| raise ValueError( | ||
| "Mean stress equals half of the ultimate tensile strength this would result" |
There was a problem hiding this comment.
- not half, but DOUBLE
- it will result in infinite eq stress amp, not zero
- Adjacent string literals concatenate without inserting a space, so the actual message reads "…would resultin zero…"
| invalid_condition = (walker_parameter_arr < 0) | (walker_parameter_arr > 1) | ||
| if np.any(invalid_condition): | ||
| raise ValueError("Walker parameter (γ') must be in the range (0, 1). ") | ||
|
|
There was a problem hiding this comment.
Walker is missing the σ_max > 0 guard — silently produces NaN
Walker is a generalization of SWT (it reduces to SWT at γ=0.5), so the same physical validity should hold.
Since γ is a non-integer, raising a negative (σ_a + σ_m) to a fractional power yields NaN. SWT guards against this; Walker doesn't.
Add the same σ_a + σ_m > 0 check as SWT.
| for mean_stress, stress_amp in [(-100.0, 180.0), (100.0, 180.0)]: | ||
| result = calc_stress_eq_amp_swt(stress_amp, mean_stress) | ||
| expected = np.sqrt((stress_amp + mean_stress) * stress_amp) | ||
| assert_allclose(result, expected) |
| result = calc_stress_eq_amp_ASME(stress_amp, mean_stress, 700.0) | ||
| assert result.shape == (4,) | ||
|
|
||
| def test_invalid_yield_strength(self) -> None: |
There was a problem hiding this comment.
with pytest.raises: outside for loop only tests the first input
When ys=0.0 raises, the context manager exits and the loop is abandoned — ys=-500.0 is never executed.
Fix:
@pytest.mark.parametrize("ys", [0.0, -500.0])
def test_invalid_yield_strength(self, ys: float) -> None:
with pytest.raises(ValueError):
calc_stress_eq_amp_ASME(100.0, 50.0, ys)
|
|
||
| ??? info "ASME Use-case" | ||
| The ASME criterion accounts for mean stress effects in high-cycle fatigue | ||
| by modifying the stress amplitude based on the yield strength using a linear |
There was a problem hiding this comment.
ASME (quadratic, square-root denominator) — docstring says "using a linear relationship".
|
|
||
| ??? info "Bagci Use-case" | ||
| The Bagci criterion accounts for mean stress effects in high-cycle fatigue | ||
| by modifying the stress amplitude based on the yield strength using a linear |
There was a problem hiding this comment.
Bagci (4th-power) — docstring also says "using a linear relationship".
| from numpy.typing import ArrayLike, NDArray | ||
|
|
||
|
|
||
| def calc_stress_eq_amp_ASME( |
There was a problem hiding this comment.
PEP 8 — calc_stress_eq_amp_ASME should be lowercase
Every other function uses _lowercase. ASME breaks both PEP 8 and the file's own convention. calc_stress_eq_amp_asme is consistent with calc_stress_eq_amp_swt.
On the other hand, ASME in upper letters is highly established abbreviation... I don't have strong opinion on that.
| invalid_condition = (walker_parameter_arr < 0) | (walker_parameter_arr > 1) | ||
| if np.any(invalid_condition): | ||
| raise ValueError("Walker parameter (γ') must be in the range (0, 1). ") | ||
|
|
There was a problem hiding this comment.
Invalid_condition handling accepts endpopints 0,1 even these degenerate walker (gamma=1 => no MSE; gamma=0 => σ_aeq = σ_a + σ_m)
Fix: raise ValueError("Walker parameter (γ') must be in the range [0, 1].")
|
ASME is more strict than Bagci/Gerber for no good reason |
|
ratio == 1.0 is a fragile float comparison |
| expected = 180.0 / np.sqrt(1.0 - (100.0 / 500.0) ** 2) | ||
| assert_allclose(result, expected) | ||
|
|
||
| def test_array_inputs( |
There was a problem hiding this comment.
That tests one specific combination: two arrays of identical shape (4,) plus a scalar. It exercises a sliver of broadcasting — the trivial case where the two arrays already match. But the docstring promises much more.
Things that should work according to the docstring but are never tested:
-
Scalar σ_a, array σ_m → result shape (3,)
calc_stress_eq_amp_goodman(150.0, np.array([0.0, 100.0, 200.0]), 700.0) -
1-D σ_a, 2-D σ_m → broadcasts to 2-D result
sa = np.array([100.0, 200.0]) # shape (2,)
sm = np.array([[0.0, 50.0], [100.0, 150.0]]) # shape (2, 2)
calc_stress_eq_amp_goodman(sa, sm, 700.0) # should give shape (2, 2) -
Array of UTS values too (e.g., comparing materials)
calc_stress_eq_amp_goodman(150.0, 100.0, np.array([500.0, 600.0, 700.0]))
|
Strongly compressive σ_m is silently accepted by Goodman/Linear/Morrow/Soderberg/Smith |
Pull request following issue #40 and is prepared for other stress based uniaxial fatigue criteria like #44 , #45 and #46.