TMM4175 Polymer Composites

Home About Python Links Next Previous Table of Contents

Progressive damage analysis

Laminates exposed to loading beyond failure of individual layers will usually have a significant residual strength. The typical mechanisms involve progressive matrix dominated failure and subsequent redistribution of loads to layers having fibers alligned with the loads.

A simple example introduces the concept:

Assume a [0/90/90/0] laminates subjected to tensile loading $N_x$.

In [1]:
import numpy as np
from laminatelib import laminateStiffnessMatrix, layerResults
from plotlib import plotLayerStresses
import matplotlib.pyplot as plt
%matplotlib inline

m ={'E1':40000, 'E2':10000, 'v12':0.3, 'G12':5000,
    'XT':1000, 'XC':800, 'YT':50, 'YC':150, 'S12':75, 'f12':-0.5, 'S23':50}

lup1=[{ 'mat':m , 'ori':  0 , 'thi':1 },
      { 'mat':m , 'ori': 90 , 'thi':1 },
      { 'mat':m , 'ori': 90 , 'thi':1 },
      { 'mat':m , 'ori':  0 , 'thi':1 }]

ABD=laminateStiffnessMatrix(lup1)
abd=np.linalg.inv(ABD)
load=(100,0,0,0,0,0)
defo=np.dot(abd,load)
res=layerResults(lup1,defo)
plotLayerStresses(res)

Using the plane stress Hashin criterion and computing the inter fiber failure (IFF) exposure factors for the layers:

In [2]:
print( res[0]['fail']['HNIFF'])
print( res[1]['fail']['HNIFF'])
print( res[2]['fail']['HNIFF'])
print( res[3]['fail']['HNIFF'])
{'bot': 0.036525974025974024, 'top': 0.03652597402597402}
{'bot': 0.19561688311688313, 'top': 0.19561688311688313}
{'bot': 0.19561688311688313, 'top': 0.19561688311688313}
{'bot': 0.03652597402597401, 'top': 0.03652597402597401}

The 90-layers are obviously most exposed with respect to matrix dominated, or inter fiber failure (0.1956), as illustrated below:

In [3]:
from plotlib import plotHashin
plotHashin(res)

Computing the failure load by dividing the previously applied load by the worst case exposure factor:

In [4]:
Nx_1 = 100/0.19561688311688313
print(Nx_1)
511.20331950207463

Now we can run a new solution using this value of the $N_x$:

In [5]:
ABD=laminateStiffnessMatrix(lup1)
abd=np.linalg.inv(ABD)
load=(Nx_1,0,0,0,0,0)
defo=np.dot(abd,load)
res=layerResults(lup1,defo)
plotHashin(res)

The midplane strain $\varepsilon_x^0$ at this loading found by:

In [6]:
ex0_1 = defo[0]
print(ex0_1)
0.005070020746887966

The failure of the 90-layers will typically appare as matrix cracking running through the thickness of the layers:

Let us assume that the consequence is a reduction of all the matrix dominated elastic properties of these two layers, including the transverse stiffness $E_2$ and the shear modulus $G_{12}$. We can illustrate that by creating a new material ($m_{90}$) degraded by 99% (that is, only 1% of original stiffness remains).

The concept of a degradation factor $d$ in the range 0...1 is commonly used in this framework as illustrated in the code below.

The case study Elastic degradation of laminates provides an idea of realistic degradation factors based on FEA results.

In [7]:
d=0.99

m90 ={'E1':40000, 'E2':10000*(1-d), 'v12':0.3, 'G12':5000*(1-d),
     'XT':1000, 'XC':800, 'YT':50, 'YC':150, 'S12':75, 'f12':-0.5, 'S23':50}

lup1=[{ 'mat':m  , 'ori': 0 , 'thi':1 },
      { 'mat':m90, 'ori':90 , 'thi':1 },
      { 'mat':m90, 'ori':90 , 'thi':1 },
      { 'mat':m  , 'ori': 0 , 'thi':1 }]

Now we must obtaine a new solution for the degraded laminate using the loading at failure. Just for the record, we may define a new parameter for the loading at this stage, being equal to the failure loading:

In [8]:
Nx_2 = Nx_1
print(Nx_2)
511.20331950207463

New solution and evaluation of the exposure factors:

In [9]:
ABD=laminateStiffnessMatrix(lup1)
abd=np.linalg.inv(ABD)
load=(Nx_2,0,0,0,0,0)
defo=np.dot(abd,load)
res=layerResults(lup1,defo)
plotHashin(res)

The strain is now:

In [10]:
ex0_2 = defo[0]
print(ex0_2)
0.006260209508959959

Based on the exposure factors at the current loading on the degraded laminate,

In [11]:
print( res[0]['fail']['HNIFF'])
print( res[1]['fail']['HNIFF'])
print( res[2]['fail']['HNIFF'])
print( res[3]['fail']['HNIFF'])
{'bot': 0.3052470136503229, 'top': 0.3052470136503229}
{'bot': 0.012291483757682187, 'top': 0.012291483757682187}
{'bot': 0.012291483757682187, 'top': 0.012291483757682187}
{'bot': 0.3052470136503229, 'top': 0.3052470136503229}

we can make a prediction for the next failure load. At this stage, the 0-layers are most exposed, and the next failure load becomes

In [12]:
Nx_3=Nx_2/0.3052470136503229
print(Nx_3)
1674.720133667502

Solution for this load is

In [13]:
load=(Nx_3,0,0,0,0,0)
defo=np.dot(abd,load)
res=layerResults(lup1,defo)
plotHashin(res)

where the strain now is

In [14]:
ex0_3 = defo[0]
print(ex0_3)
0.020508667502088556

Now we shall introduce a degradation of the 0-oriented layers as well:

In [15]:
d=0.99

m0 ={'E1':40000, 'E2':10000*(1-d), 'v12':0.3, 'G12':5000*(1-d),
     'XT':1000, 'XC':800, 'YT':50, 'YC':150, 'S12':75, 'f12':-0.5, 'S23':50}

lup1=[{ 'mat':m0 , 'ori': 0 , 'thi':1 },
      { 'mat':m90, 'ori':90 , 'thi':1 },
      { 'mat':m90, 'ori':90 , 'thi':1 },
      { 'mat':m0 , 'ori': 0 , 'thi':1 }]

Nx_4 = Nx_3

ABD=laminateStiffnessMatrix(lup1)
abd=np.linalg.inv(ABD)
load=(Nx_4,0,0,0,0,0)
defo=np.dot(abd,load)
res=layerResults(lup1,defo)
plotHashin(res)

The strain is now

In [16]:
ex0_4 = defo[0]
print(ex0_4)
0.02087714551312135

There are still some load capacity since the exposure factors for fiber failure (FF) is less than 1.0. The exact numbers are:

In [17]:
print( res[0]['fail']['HNFF']['bot'])
print( res[1]['fail']['HNFF']['bot'])
print( res[2]['fail']['HNFF']['bot'])
print( res[3]['fail']['HNFF']['bot'])
0.8352728197805782
0.0007791635655181481
0.0007791635655181481
0.8352728197805782

Hence, the ultimate failure load is

In [18]:
Nx_5 = Nx_4/0.8352728197805782
print(Nx_5)
2004.9977612194327
In [19]:
load=(Nx_5,0,0,0,0,0)
defo=np.dot(abd,load)
res=layerResults(lup1,defo)
plotHashin(res)

The ultimate failure strain is

In [20]:
ex0_5 = defo[0]
print(ex0_5)
0.024994403048581977

the load-strain curve, along with the initial linear realation, is:

In [21]:
ex0= (0, ex0_1, ex0_2, ex0_3, ex0_4, ex0_5)
Nx=(0, Nx_1, Nx_2, Nx_3, Nx_4, Nx_5)
fig,ax = plt.subplots(ncols=1,nrows=1,figsize=(8,6))
ax.grid(True)
ax.plot(ex0,Nx,'-', color='black')
ax.plot( (0, 2000*ex0_1/Nx_1) , (0, 2000), '--', color = 'gray')
ax.set_xlabel('$\epsilon_x^0$',fontsize=14)
ax.set_ylabel('$N_x$', fontsize=14)
plt.tight_layout()
plt.show()     
The following concept and results to be discussed during lectures on the topic.
In [22]:
m ={'E1':40000, 'E2':10000, 'v12':0.3, 'G12':5000,
    'XT':1000, 'XC':800, 'YT':50, 'YC':150, 'S12':75, 'f12':-0.5, 'S23':50}

lup1=[{ 'mat':m , 'ori':  0 , 'thi':1 },
      { 'mat':m , 'ori': 90 , 'thi':1 },
      { 'mat':m , 'ori': 90 , 'thi':1 },
      { 'mat':m , 'ori':  0 , 'thi':1 }]
    
from magicdamage import progressiveDamageNx
    
ex0_a,Nx_a = progressiveDamageNx(layup=lup1,increment=1,degfactor=0.00, maxNx=3000)
ex0_b,Nx_b = progressiveDamageNx(layup=lup1,increment=1,degfactor=0.10, maxNx=3000)
ex0_c,Nx_c = progressiveDamageNx(layup=lup1,increment=1,degfactor=0.99, maxNx=3000)

fig,ax = plt.subplots(nrows=1,ncols=1, figsize = (8,6))
ax.plot(ex0_a,Nx_a,'-',color='black',label='degfact=0.00')
ax.plot(ex0_b,Nx_b,'-',color='blue',label='degfact=0.10')
ax.plot(ex0_c,Nx_c,'-',color='red',label='degfact=0.99')
ax.grid(True)
ax.legend(loc='best',prop={'size': 12})
ax.set_xlabel(r'$\varepsilon_x^0}$',size=14)
ax.set_ylabel('$N_x$',size=14)
ax.set_xlim(0,)
ax.set_ylim(0,)
plt.tight_layout()
plt.show()

Disclaimer:This site is about polymer composites, designed for educational purposes. Consumption and use of any sort & kind is solely at your own risk.
Fair use: I spent some time making all the pages, and even the figures and illustrations are my own creations. Obviously, you may steal whatever you find useful here, but please show decency and give some acknowledgment if or when copying. Thanks! Contact me: nils.p.vedvik@ntnu.no www.ntnu.edu/employees/nils.p.vedvik

Copyright 2021, All right reserved, I guess.