Skip to content

Tutorial: reproduce a published strain-life analysis

This walkthrough reproduces the SAE 1137 steel analysis of Williams, Lee, Rilly, "A practical method for statistical analysis of strain-life fatigue data", International Journal of Fatigue 25 (2003) 427-436, from the six re-tabulated tests bundled with the library. The same numbers anchor the golden regression tests in the suite, so what this page shows is enforced, not aspirational.

The data

from lcf import datasets

df = datasets.sae1137_reduced()
print(df)

Six strain-controlled tests, half-life total strain amplitude, stress amplitude in MPa, and reversals to failure. Elastic modulus 208000 MPa. The same records ship in the open seed collection as validated test-record@1 documents.

Fit the strain-life constants

The paper drops plastic strain amplitudes below 0.0005, they sit at measurement-noise level. min_plastic_strain applies the same rule.

import lcf

fit = lcf.fit_strain_life(
    total_strain_amp=df.total_strain_amp,
    stress_amp=df.stress_amp,
    reversals=df.reversals,
    E=datasets.SAE1137_E,
    min_plastic_strain=5e-4,
)

Output, validated against the published reduction in the test suite:

Constant This fit Meaning
eps_f 1.106 Fatigue ductility coefficient
c -0.620 Fatigue ductility exponent
sigma_f 1072.8 MPa Fatigue strength coefficient
b -0.0836 Fatigue strength exponent
transition 22362 reversals Elastic and plastic branches cross

Predict a life

two_nf = lcf.predict_reversals(fit, 0.004)   # 30902 reversals

Median and design life at a strain amplitude

from lcf import stats

ll = stats.fit_log_life(df.total_strain_amp, df.reversals)
median = stats.predict_life(ll, 0.005)                       # 23342
design = stats.design_life(ll, 0.005,
                           reliability=0.90, confidence=0.90)  # 3541

The median at 0.005, about 23300 reversals from these six bundled tests, sits close to the 23700 the paper reports from its own eight-specimen reduction. The R90C90 design value here is far below the paper's because it comes from this six-point total-strain regression and its scatter, not from the paper's branch-wise reduction, a reminder that a design value belongs to a fit, not to a material. The paper's own reduction formula is reproduced exactly in the test suite as a golden case.

The same analysis through the MCP server

An agent produces the identical numbers with two tool calls:

fit_strain_life(total_strain_amp=[...], stress_amp=[...],
                reversals=[...], E=208000, min_plastic_strain=0.0005)
fit_design_curve(amplitude=[...], life_values=[...],
                 design_amplitude=0.005)

Both persist their results for later recall_result calls.

If some tests had been runouts

Real campaigns suspend tests. Flag them instead of deleting them:

ml = stats.fit_log_life_censored(amplitude, life, censored)
bound = stats.design_life_ml(amplitude, life, censored,
                             at_amplitude=0.005)

The statistics page covers the censored layer.