Implementing the theory into Python code, step-by-step:
import numpy as np
The material for subsequent codes and examples:
m1={'E1':40000, 'E2':10000, 'v12':0.3, 'G12':5000,
'XT':1200, 'XC':800, 'YT':50, 'YC': 120, 'S12': 60}
The 2D compliance matrix was defined in Plane stress
$$ \mathbf{S}=\begin{bmatrix} 1/E_1 & -v_{12}/E_1 & 0 \\ -v_{12}/E_1 & 1/E_{2} & 0 \\ 0 & 0 & 1/G_{12} \end{bmatrix} $$The function takes a material (m) as the only argument:
def S2D(m):
return np.array([[ 1/m['E1'], -m['v12']/m['E1'], 0],
[-m['v12']/m['E1'], 1/m['E2'], 0],
[ 0, 0, 1/m['G12']]], float)
Example:
temp = S2D(m1)
print(temp)
The 2D stiffness matrix of the material is simply the inverse of the compliance matrix:
$$ \mathbf{Q} = \mathbf{S}^{-1} $$where the components are given by
$$ Q_{11}=\frac{E_1}{1-v_{12}v_{21}}, \quad Q_{22}=\frac{E_2}{1-v_{12}v_{21}}, \quad Q_{12}=\frac{v_{12}E_2}{1-v_{12}v_{21}}, \quad Q_{66}=G_{12} $$In the following implementation we call the previous function for the compliance matrix S with a material (m) as argument. Then, the inverse of the compliance matrix S is returned:
def Q2D(m):
S=S2D(m)
return np.linalg.inv(S)
Example:
temp = Q2D(m1)
print(temp)
The 2D transformation matrices are
$$ \mathbf{T}_{\sigma}=\begin{bmatrix} c^2 & s^2 & 2cs \\ s^2 & c^2 & -2cs \\ -cs & cs & c^2-s^2 \end{bmatrix} $$and $$ \mathbf{T}_{\epsilon}=\begin{bmatrix} c^2 & s^2 & cs \\ s^2 & c^2 & -cs \\ -2cs & 2cs & c^2-s^2 \end{bmatrix} $$
Both functions takes the orientation angle a as the only argument:
def T2Ds(a):
c,s = np.cos(np.radians(a)), np.sin(np.radians(a))
return np.array([[ c*c , s*s , 2*c*s],
[ s*s , c*c , -2*c*s],
[-c*s, c*s , c*c-s*s]], float)
def T2De(a):
c,s = np.cos(np.radians(a)), np.sin(np.radians(a))
return np.array([[ c*c, s*s, c*s ],
[ s*s, c*c, -c*s ],
[-2*c*s, 2*c*s, c*c-s*s ]], float)
Transformation of the 2D stiffness matrix is given by: $$ \mathbf{Q{'}} = \mathbf{T}_{\sigma}^{-1}\mathbf{Q} \mathbf{T}_{\epsilon} $$
The function Q2Dtransform which takes the material stiffness matrix (Q) and the orientation angle (a) as arguments:
def Q2Dtransform(Q,a):
return np.dot(np.linalg.inv( T2Ds(a) ) , np.dot(Q,T2De(a)) )
Example:
tempQ=Q2D(m1)
tempQt=Q2Dtransform(tempQ,30) # orientation angle = 30
print('Stiffness matrix in material coordinate system:\n')
print(tempQ)
print('\nTransformed stiffness matrix (30 degrees):\n')
print(tempQt)
Defining a layup:
Layups will be defined as dictionaries as described in Definitions and notation:
layup1 = [ {'mat':m1 , 'ori': 0 , 'thi':0.5} ,
{'mat':m1 , 'ori': 90 , 'thi':0.5} ,
{'mat':m1 , 'ori': 45 , 'thi':0.5} ,
{'mat':m1 , 'ori':-45 , 'thi':0.5} ]
Example: the orientation of layer number 3 (index 2) is found by:
layup1[2]['ori']
Example: the shear modulus $G_{12}$ for the material used in the first layer:
layup1[0]['mat']['G12']
Example: number of layers is
n = len(layup1)
print(n)
The total thickness::
def laminateThickness(layup):
return sum([layer['thi'] for layer in layup])
laminateThickness(layup1)
where
$$ \mathbf{A}=\sum_{k=1}^n\mathbf{Q{'}}_{k}(h_k-h_{k-1}) $$$$ \mathbf{B}=\frac{1}{2}\sum_{k=1}^n\mathbf{Q{'}}_{k}(h_k^2-h_{k-1}^2) $$$$ \mathbf{D}=\frac{1}{3}\sum_{k=1}^n\mathbf{Q{'}}_{k}(h_k^3-h_{k-1}^3) $$Lets start with the A sub-matrix: We initiate a 3x3 numpy array of zeros:
temp_A = np.zeros((3,3), float)
print(temp_A)
Then add contribution from the individual layers, i.e. $$ \mathbf{A}=\sum_{k=1}^n\mathbf{Q{'}}_{k}(h_k-h_{k-1}) $$
# The very bottom is also to bottom of the first layer:
hbot = -laminateThickness(layup1) / 2
# A loop over the layers:
for layer in layup1:
# material:
m = layer['mat']
# stiffness matrix:
Q = Q2D(m)
# orientation angle:
a = layer['ori']
#transformed stiffness materix:
Qt = Q2Dtransform(Q,a)
# compute the top coordinate of current layer:
htop = hbot + layer['thi']
# for the next layer, the hbot is the current htop:
hbot=htop
# finally, add current layer contribution to the A matrix (temp_A):
temp_A = temp_A + temp
print('A=')
print(temp_A)
Now, lets put all the stuff into a function for the A-matrix:
def computeA(layup):
A=np.zeros((3,3),float)
hbot = -laminateThickness(layup)/2 # bottom of first layer
for layer in layup:
m = layer['mat']
Q = Q2D(m)
a = layer['ori']
Qt = Q2Dtransform(Q,a)
htop = hbot + layer['thi'] # top of current layer
A = A + Qt*(htop-hbot)
hbot=htop # for the next layer
return A
Example:
computeA(layup1)
Continue with the B-matrix: $$ \mathbf{B}=\frac{1}{2}\sum_{k=1}^n\mathbf{Q{'}}_{k}(h_k^2-h_{k-1}^2) $$
def computeB(layup):
B=np.zeros((3,3),float)
hbot = -laminateThickness(layup)/2 # bottom of first layer
for layer in layup:
m = layer['mat']
Q = Q2D(m)
a = layer['ori']
Qt = Q2Dtransform(Q,a)
htop = hbot + layer['thi'] # top of current layer
B = B + (1/2)*Qt*(htop**2-hbot**2)
hbot=htop # for the next layer
return B
And then, the D-matrix: $$ \mathbf{D}=\frac{1}{3}\sum_{k=1}^n\mathbf{Q{'}}_{k}(h_k^3-h_{k-1}^3) $$
def computeD(layup):
D=np.zeros((3,3),float)
hbot = -laminateThickness(layup)/2 # bottom of first layer
for layer in layup:
m = layer['mat']
Q = Q2D(m)
a = layer['ori']
Qt = Q2Dtransform(Q,a)
htop = hbot + layer['thi'] # top of current layer
D = D + (1/3)*Qt*(htop**3-hbot**3)
hbot=htop # for the next layer
return D
Example: for the laminate with layup
layup2 = [ {'mat':m1 , 'ori': 0 , 'thi':1} ,
{'mat':m1 , 'ori': 90 , 'thi':1} ,
{'mat':m1 , 'ori': 0 , 'thi':1} ]
we find the sub-matrices as
computeA(layup2)
computeB(layup2)
computeD(layup2)
For efficiency we may improve the code by merging the computeA, computeB and computeD into one function that return A, B and D as individual variables simultanously:
def computeABD(layup):
A=np.zeros((3,3),float)
B=np.zeros((3,3),float)
D=np.zeros((3,3),float)
hbot = -laminateThickness(layup)/2 # bottom of first layer
for layer in layup:
m = layer['mat']
Q = Q2D(m)
a = layer['ori']
Qt = Q2Dtransform(Q,a)
htop = hbot + layer['thi'] # top of current layer
A = A + Qt*(htop -hbot )
B = B + (1/2)*Qt*(htop**2-hbot**2)
D = D + (1/3)*Qt*(htop**3-hbot**3)
hbot=htop # for the next layer
return A,B,D
Example:
myA, myB, myD = computeABD(layup2)
print(myA)
print('---------------------------------------------')
print(myB)
print('---------------------------------------------')
print(myD)
The A, B and D can be concatenated into one 6 x 6 matrix as shown in this example:
myABBD=np.concatenate(( np.concatenate((myA,myB),axis=1) , np.concatenate((myB,myD),axis=1)),axis=0)
print(np.round(myABBD))
And finally, another function that returns the whole 6 x 6 laminate stiffness matrix more efficiently than the previous approches. The local intermediate variables are also eliminated:
def laminateStiffnessMatrix(layup):
ABD=np.zeros((6,6),float)
hbot = -laminateThickness(layup)/2 # bottom of first layer
for layer in layup:
Qt = Q2Dtransform(Q2D(layer['mat']), layer['ori'])
htop = hbot + layer['thi'] # top of current layer
ABD[0:3,0:3] = ABD[0:3,0:3] + Qt*(htop -hbot )
ABD[0:3,3:6] = ABD[0:3,3:6] + (1/2)*Qt*(htop**2-hbot**2)
ABD[3:6,0:3] = ABD[3:6,0:3] + (1/2)*Qt*(htop**2-hbot**2)
ABD[3:6,3:6] = ABD[3:6,3:6] + (1/3)*Qt*(htop**3-hbot**3)
hbot=htop # for the next layer
return ABD
Example:
myABBD = laminateStiffnessMatrix(layup2)
print(np.round(myABBD))
Or in details; $$ \begin{bmatrix} N_x \\ N_y \\ N_{xy} \\ M_x \\ M_y \\ M_{xy} \end{bmatrix} = \begin{bmatrix} A_{xx} & A_{xy} & A_{xs} & B_{xx} & B_{xy} & B_{xs} \\ A_{xy} & A_{yy} & A_{ys} & B_{xy} & B_{yy} & B_{ys} \\ A_{xs} & A_{ys} & A_{ss} & B_{xs} & B_{ys} & B_{ss} \\ B_{xx} & B_{xy} & B_{xs} & D_{xx} & D_{xy} & D_{xs} \\ B_{xy} & B_{yy} & B_{ys} & D_{xy} & D_{yy} & D_{ys} \\ B_{xs} & B_{ys} & B_{ss} & D_{xs} & D_{ys} & D_{ss} \end{bmatrix} \begin{bmatrix} \varepsilon_x^0 \\ \varepsilon_y^0 \\ \gamma_{xy}^0 \\ \kappa_x \\ \kappa_y \\ \kappa_{xy} \end{bmatrix} $$
Material and layup for the proceeding examples:
m1={'E1':140000, 'E2':10000, 'v12':0.3, 'G12':5000}
layup = [ {'mat':m1 , 'ori': 0 , 'thi':1} ,
{'mat':m1 , 'ori': 90 , 'thi':1} ,
{'mat':m1 , 'ori': 45 , 'thi':1} ]
In this case the procedure is pretty straigth forward:
ABD=laminateStiffnessMatrix(layup)
deformations1=(0.001,0,0,0,0,0)
loads1=np.dot(ABD,deformations1)
print(np.round(loads1))
Compared to the previous case, this is just a case where we solve for the deformations:
ABD=laminateStiffnessMatrix(layup)
loads2=(1000,0,0,0,0,0)
deformations2=np.linalg.solve(ABD,loads2)
print(np.round(deformations2,5))
#alternatively:
deformations3=np.dot( np.linalg.inv(ABD) ,loads2 )
print(np.round(deformations3,5))
Now it becomes slightly more tricky. Somehow we must rearrange the set of equations such that the known quantities are split from the unknown quantities.
The function that follows makes use of keyword arguments (**kwargs
) in the following way:
Nx=100
, or Kx=0.1
) overrides the initial assumption.ey = 0
), the force or moment becomes the unknown (Ny
in this case)def solveLaminateLoadCase(ABD,**kwargs):
# ABD: the laminate stiffness matrix
# kwargs: prescribed loads or deformations
loads=np.zeros((6))
defor=np.zeros((6))
loadKeys=('Nx','Ny','Nxy','Mx','My','Mxy') # valid load keys
defoKeys=('ex','ey','exy','Kx','Ky','Kxy') # valid deformation keys
knownKeys=['Nx','Ny','Nxy','Mx','My','Mxy'] # initially assume known loads
knownVals=np.array([0,0,0,0,0,0],float)
for key in kwargs:
if key in loadKeys:
i=loadKeys.index(key)
knownKeys[i]=key
knownVals[i]=kwargs[key]
if key in defoKeys:
i=defoKeys.index(key)
knownKeys[i]=key
knownVals[i]=kwargs[key]
M1=-np.copy(ABD)
M2= np.copy(ABD)
ID= np.identity(6)
for k in range(0,6):
if knownKeys[k] in loadKeys:
M2[:,k]=-ID[:,k]
else:
M1[:,k]=ID[:,k]
sol=np.dot( np.linalg.inv(M1), np.dot(M2,knownVals))
for i in range(0,6):
if knownKeys[i] == loadKeys[i]:
loads[i]=knownVals[i]
defor[i]=sol[i]
if knownKeys[i] == defoKeys[i]:
defor[i]=knownVals[i]
loads[i]=sol[i]
return loads,defor
Example:
loads, defs = solveLaminateLoadCase(ABD=ABD, Nx=100., ey=0.001, exy=0.005, Mx=50, My=25,Kxy=0.002 )
loadKeys=('Nx','Ny','Nxy','Mx','My','Mxy')
defoKeys=('ex','ey','exy','Kx','Ky','Kxy')
for i in range(0,6):
print(loadKeys[i],'=',loads[i])
for i in range(0,6):
print(defoKeys[i],'=',defs[i])
A function to compute the thermal loads:
$$ \mathbf{N}_{th}= \Big[\Delta T \sum_{k=1}^{n} \mathbf{Q}'_k \boldsymbol{\alpha}'_k (h_k-h_{k-1}) \Big] $$$$ \mathbf{M}_{th}=\Big[\Delta T \frac{1}{2} \sum_{k=1}^{n} \mathbf{Q}'_k \boldsymbol{\alpha}'_k (h_k^2-h_{k-1}^2) \Big] $$where
$$ \boldsymbol{\alpha}'= \mathbf{T}_{\varepsilon}^{-1} \boldsymbol{\alpha} $$def thermalLoad(layup,dT):
NTMT=np.zeros(6,float) #thermal loads
hbot = -laminateThickness(layup)/2
for layer in layup:
m = layer['mat']
Q = Q2D(m)
a = layer['ori']
Qt = Q2Dtransform(Q,a)
a123=[m['a1'], m['a2'], 0]
aXYZ=np.dot( T2De(-layer['ori']) , a123 )
htop = hbot + layer['thi']
NTMT[0:3] = NTMT[0:3] + dT*( np.dot(Qt,aXYZ) )*(htop -hbot )
NTMT[3:6] = NTMT[3:6] + 0.5*dT*( np.dot(Qt,aXYZ) )*(htop**2-hbot**2)
hbot=htop
return NTMT
cfrp = {'E1':140000, 'E2':8000, 'v12':0.28, 'G12':4000, 'a1':-0.5E-6, 'a2': 25E-6}
layup1 =[ {'mat':cfrp , 'ori': 0 , 'thi':0.5} ,
{'mat':cfrp , 'ori': 90 , 'thi':0.5} ]
thermLoad=thermalLoad(layup1,-100)
ABD=laminateStiffnessMatrix(layup1)
defs= np.dot(np.linalg.inv(ABD) , thermLoad)
print(defs)
from plotlib import illustrateCurvatures
%matplotlib inline
illustrateCurvatures(Kx=defs[3], Ky=defs[4], Kxy=defs[5])
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.