Interpolating Logarithmic Plots for Fatigue Analysis -- Multi-linear

Published in Design, Programming

In this post we will modify the code from an earlier post to include 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:

\[ f = \text{interp1d}(\log10{N}, S) \]

\[ S_{new} = f(\log10{N_{new}}) \]

And interpolating for cycles:

\[ f = \text{interp1d}(S,\log10{N}) \]

\[ N_{new} = 10^{f(S_{new})} \]

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:

\[ f = \text{interp1d}(\log10{N},\log10{S}) \]

\[ S_{new} = 10^{f(\log10{N_{new}})} \]

To obtain cycles:

\[ f = \text{interp1d}(\log10{S},\log10{N}) \]

\[ N_{new} = 10^{f(\log10{S_{new}})} \]

Python

A Python script implementing the above can be:

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


__author__    = 'Rob Siegwart'
__copyright__ = 'Copyright 2019 Rob Siegwart'


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


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
>>> curve.getN(34e3,plot=True)
18309