Fatigue curves are typically in semi-log or log-log form. And in fatigue analysis it is often needed to interpolate both ways in order to determine stress or cycles. In both cases below we will interpolate over a straight line drawn on the graph using two points as its definition: $$(N_1,S_1)$$, $$(N_2,S_2)$$.

## Semi-Log

In semi-log plots the stress is linear and the cycles are logarithmic. The equation of a line in this coordinate space is therefore:

S = m \log_{10}N + b

The values of $$m$$ and $$b$$ can be found by 2 known points on the fatigue curve:

\begin{align}
&m = \frac{\Delta S}{\Delta \log_{10}N} = \frac{S_2-S_1}{\log_{10}(N_2/N_1)}
&b = S_1 – m\log_{10}N_1
\end{align}

To get stress from cycles along this line, apply the equation of the line with your values of $$m$$ and $$b$$:

S(N) = m\log_{10}N + b

To interpolate cycles from stress along this line, rearrange the equation for N:

N(S) = 10^{\frac{S-b}{m}}

## Log-log

Here both scales are logarithmic. The equation for a line in this coordinate system is therefore:

\log_{10}S = m \log_{10}N + \log_{10}b

m and b are found in the same manner:
\begin{align}
&m = \frac{\Delta\log_{10}S}{\Delta\log_{10}N} = \frac{\log_{10}(S_2/S_1)}{\log_{10}(N_2/N_1)}
&b = 10^{\log_{10}S_1 – m \log_{10}N_1}
\end{align}

To interpolate stress from cycles:

S(N) = 10^{m\log_{10}N + \log_{10}b}

To interpolate cycles from stress:

N(S) = 10^{ \frac{ \log_{10}S – \log_{10}b}{m} } = 10^{ \frac{\log_{10}(S/b)}{m} }

## Python Implementation

In Python, we can make a class to represent a fatigue curve and give it methods for interpolation and plotting. Moreover, we can define a __call__ method to make the instance callable. For example:

#!/usr/bin/env python
"""
Provides classes for semi-log and log-log fatigue curves having methods for
interpolation and plotting.
"""

import numpy as np
import matplotlib.pyplot as plt

__author__    = 'Rob Siegwart'

class FatigueCurve:
''' Base class for fatigue curves. Input:
A list containing two (N,S) data point pairs
'''
def __init__(self,points):
self.N, self.S = list(zip(points[0],points[1]))
self.N1, self.N2 = self.N
self.S1, self.S2 = self.S

def _plot(self):
''' Plot setup method '''
fig, ax = plt.subplots()
ax.set_xlabel('Cycles')
ax.set_ylabel('Stress')
ax.grid(True, which='both', axis='both', color='lightgrey')
return fig, ax

def plot(self):
fig, ax = self._plot()
ax.semilogx(self.N,self.S)

class SemiLogCurve(FatigueCurve):
def __init__(self,points):
super().__init__(points)
self.m = (self.S2 - self.S1)/np.log10(self.N2/self.N1)
self.b = self.S1 - self.m*np.log10(self.N1)

def getS(self,N,plot=False):
''' Interpolate stress from cycles. '''
S = np.round(self.m*np.log10(N) + self.b,2)
if plot:
fig,ax = self._plot()
ax.semilogx(self.N,self.S,N,S,'*')
return S

def getN(self,S,plot=False):
''' Interpolate cycles from stress. '''
N = int(10**((S-self.b)/self.m))
if plot:
fig,ax = self._plot()
ax.semilogx(self.N,self.S,N,S,'*')
return N

__call__ = getS

class LogLogCurve(FatigueCurve):
def __init__(self,points):
super().__init__(points)
self.m = np.log10(self.S2/self.S1)/np.log10(self.N2/self.N1)
self.b = 10**(np.log10(self.S1) - self.m*np.log10(self.N1))

def plot(self):
fig,ax = self._plot()
ax.loglog(self.N,self.S)

def getS(self,N,plot=False):
''' Interpolate stress from cycles '''
S = np.round(10**(self.m*np.log10(N)+np.log10(self.b)),2)
if plot:
fig,ax = self._plot()
ax.loglog(self.N,self.S,N,S,'*')
return S

def getN(self,S,plot=False):
''' Interpolate cycles from stress '''
N = int(10**(np.log10(S/self.b)/self.m))
if plot:
fig,ax = self._plot()
ax.loglog(self.N,self.S,N,S,'*')
return N

__call__ = getS

Usage:

>>> mat1 = SemiLogCurve([(1000,60),(1e6,20)])   # create a new curve
>>> mat1(3e5)                                   # default call is interpolating to obtain stress from cycles
26.97
>>> mat1.getN(28.5,plot=True)                   # call to get cycles from stress input with plot set to True to generate a graph
230409