Interpolating Logarithmic Plots for Fatigue Analysis

Here we will develop some classes for interpolating general multi-linear fatigue curves using SciPy's interp1d. interp1d creates a class instance which can then be called with lookup values:

x = [ 1,2,3,...]      # X-axis values
y = [ 20,15,10,...]   # Y-axis values
f = interp1d(x,y)
y1 = f(x1)            # Query at a new value, x1

Semi-Log

Using a linear interpolation function on a semi-log graph requires that the log of the values is used on the log axis. With fatigue curve arrays of \(N\) and \(S\), interpolating for stress can be done by:

\begin{equation*} f=\text{interp1d}(\log_{10}N, S) \end{equation*}
\begin{equation*} S_{new}=f(\log_{10}N_{new}) \end{equation*}

And interpolating for cycles:

\begin{equation*} f=\text{interp1d}(S, \log_{10}N) \end{equation*}
\begin{equation*} N_{new}=10^{f(S_{new})} \end{equation*}

Log-Log

Log-log calculations are the same as Semi-log but with the exception that both axes use the logarithm of the values.

To obtain stress:

\begin{equation*} f=\text{interp1d}(\log_{10}N, \log_{10}S) \end{equation*}
\begin{equation*} S_{new}=10^{f(\log_{10}N_{new})} \end{equation*}

To obtain cycles:

\begin{equation*} f=\text{interp1d}(\log_{10}S, \log_{10}N) \end{equation*}
\begin{equation*} N_{new}=10^{f(\log_{10}S_{new})} \end{equation*}

Implementation

In Python we can implement the above as classes. First a general class common to both log-log and semi-log:

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d

class FatigueCurve:
    ''' Base class for a fatigue curve.

        Parameters
        ----------
        points : list
            A list containing (N,S) data point pairs (2+)

    '''
    def __init__(self,points):
        self.N, self.S = list(zip(*points))
        self.N = np.asarray(self.N)
        self.S = np.asarray(self.S)
        self.logN = np.log10(self.N)
        self.logS = np.log10(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

Next we create classes for semi-log and log-log:

class SemiLogCurve(FatigueCurve):
    def __init__(self,points):
        super().__init__(points)
        self.getS_f = interp1d(self.logN,self.S)
        self.getN_f = interp1d(self.S,self.logN)

    def _semilogx(self,ax,N,S):
        ''' Plot an interpolated value on a semilog fatigue curve. '''
        ax.semilogx(self.N,self.S,markersize=6,marker='o')
        ax.semilogx(N,S,markersize=6,marker='o')
        ax.annotate('({:,.2}, {:,.2})'.format(N,S), (N,S))
        return ax

    def getS(self,N,plot=False):
        ''' Interpolate stress from cycles. '''
        S = self.getS_f(np.log10(N))
        if plot:
            fig,ax = self._plot()
            ax = self._semilogx(ax,float(N),float(S))
        return int(S)

    def getN(self,S,plot=False):
        ''' Interpolate cycles from stress. '''
        N = np.power(10,self.getN_f(S))
        if plot:
            fig,ax = self._plot()
            ax = self._semilogx(ax,float(N),float(S))
        return int(N)

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

    __call__ = getS


class LogLogCurve(FatigueCurve):
    def __init__(self,points):
        super().__init__(points)
        self.getS_f = interp1d(self.logN,self.logS)
        self.getN_f = interp1d(self.logS,self.logN)

    def _loglog(self,ax,N,S):
        ''' Plot an interpolated value on a log-log fatigue curve. '''
        ax.loglog(self.N,self.S,marker='o',markersize=6)
        ax.loglog(N,S,marker='o',markersize=6)
        ax.annotate('({:,.2}, {:,.2})'.format(N,S), (N,S))
        return ax

    def getS(self,N,plot=False):
        ''' Interpolate stress from cycles '''
        S = np.power(10,self.getS_f(np.log10(N)))
        if plot:
            fig,ax = self._plot()
            ax = self._loglog(ax,float(N),float(S))
        return int(S)

    def getN(self,S,plot=False):
        ''' Interpolate cycles from stress '''
        N = np.power(10,self.getN_f(np.log10(S)))
        if plot:
            fig,ax = self._plot()
            ax = self._loglog(ax,float(N),float(S))
        return int(N)

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

    __call__ = getS

Usage

>>> curve = LogLogCurve([(1e3,50e3),(1e6,20e3),(1e7,18e3)])
>>> curve.getS(70e3,plot=True)
28459
Logarithmic plot getting stress from cycles
>>> curve.getN(34e3,plot=True)
18309
Logarithmic plot getting cycles from stress