TMM4175 Polymer Composites

Home About Python Links Next Previous Table of Contents

Laminate optimization

Simply put, optimization of a structure is the process of making the best or most effective use of a situation and/or a resource. Optimal solutions must generally satisfy a given set of design constraints and requirements that reflects both the limitations of the reality and the objectives for the optimization.

High-performance structural composites are most commonly found in light-weight design structures, suggesting that the most common objective for optimization of such designs is to minimize the weight.

Advanced optimization techniques are not within the scope of the course. The following examples use variants of brute force (check every possible alternative) which is a viable method for these problems. For more complex structures that is generally not the case and more sophisticated techniques must be employed. Note that the brute force method will always converge to the optimal solution.

Example 1: strength optimization

Constraints and requirements:

  • Ply material: Carbon/Epoxy(a)

  • Ply thickness: free variable

  • Possible orientations: 0, 90

  • Symmetric and balanced laminate

  • Load cases: Nx = 1000 N/mm and Ny = 2000 Nmm/mm

  • Stress exposure factor according to Maximum stress criterion shall be equal to 1.0 considering all plies and all positions

Objectives:

  • Minimize the mass

Since the we are free to vary the thickness of individual plies, the task is rather straight-forward and an optimum solution can be found using the following steps:

  1. Create a laminate as simple as possible, which for the given constraints is either [0/90/0] or [90/0/90]
  2. Assume a given total thickness, for example unity $t=1$
  3. By brute force, solve for all combinations of thicknesses where $t_0=0...t$ while $t_{90} = t - t_0$.
  4. When the minimum exposure factor is found along with the optimum combination of thicknesses, scale the total thickness by the exposure factor (remember it is a linear system of relations).

Code:

In [1]:
import laminatelib, matlib, numpy
import matplotlib.pyplot as plt

m = matlib.get('Carbon/Epoxy(a)')

t=1   # total thickness

ts=numpy.linspace(0,t,100)       # 100 values from 0 to t
fE=[]                            # to be filled with exposure factors for all values in ts     

for t0 in ts:
    t90=t-t0
    layup = [ {'mat':m , 'ori':  0   , 'thi':t0/2},
              {'mat':m , 'ori': 90   , 'thi':t90},
              {'mat':m , 'ori':  0   , 'thi':t0/2}]
    ABD=laminatelib.laminateStiffnessMatrix(layup)
    load1,def1=laminatelib.solveLaminateLoadCase(ABD, Nx=1000, Ny=2000)
    res=laminatelib.layerResults(layup,def1)
    fE.append(    max( res[0]['fail']['MS']['bot'], res[1]['fail']['MS']['bot'])   )  # top layer results will be the same
    
plt.plot(ts,fE)
plt.show()

As shown in the figure, there is only one minimum for the exposure factor at approximatly $t_0=0.3$. Extracting the exact values:

In [2]:
minfE = numpy.amin(fE)
ind = numpy.where(fE == minfE)
t0=ts[ind[0]][0]

print(minfE,t0)
6.610010508983899 0.30303030303030304

Scaling all thicknesses by the exposure factor:

In [3]:
t = t*minfE
t0=t0*minfE
t90=t-t0

print(t0,t90,t)
2.0030334875708786 4.606977021413021 6.610010508983899

Solve with these values to verify that the exposure factor is equal to 1.0

In [4]:
layup = [ {'mat':m , 'ori':  0   , 'thi':t0/2},
          {'mat':m , 'ori': 90   , 'thi':t90},
          {'mat':m , 'ori':  0   , 'thi':t0/2}]
ABD=laminatelib.laminateStiffnessMatrix(layup)
load1,def1=laminatelib.solveLaminateLoadCase(ABD, Nx=1000, Ny=2000)
res=laminatelib.layerResults(layup,def1)
print( max( res[0]['fail']['MS']['bot'], res[1]['fail']['MS']['bot'])   )
1.0000000000000002

Example 2: stiffness optimization, fixed ply thickness

Constraints and requirements:

  • Ply material: Carbon/Epoxy(a)

  • Ply thickness: 0.2 mm

  • Possible orientations: 0 and/or 90

  • Symmetric and balanced laminate

  • Loads: Mx = 1000 Nmm/mm and My = 200 Nmm/mm

  • Allowed deformation: κx < 0.01 mm-1 and κy < 0.01 mm-1

Objectives:

  • Minimize the mass

One major difference from the first example is the quantisized ply thickness.

For a laminate with a single 0-oriented layer:

In [5]:
layup = [ {'mat':m , 'ori': 0   , 'thi':0.2} ]

ABD=laminatelib.laminateStiffnessMatrix(layup)

load1,def1=laminatelib.solveLaminateLoadCase(ABD, Mx=1000, My=200)

print('[Kx, Ky, Kxy =]',def1[3:6])
[Kx, Ky, Kxy =] [10.89230769 26.76923077  0.        ]

The bending stiffness is clearly way to low (the resulting maximum curvature is almost 27 compared to the limit of 0.01).

A simple optimization technique is to add plies until the constraints are satisfied. For simplicity, let us just add more 0-oriented plies for this demonstration:

In [6]:
layup=[]
for i in range(0,100):
    layup.append( {'mat':m , 'ori': 0   , 'thi':0.2} )
    ABD=laminatelib.laminateStiffnessMatrix(layup)
    load,deformation=laminatelib.solveLaminateLoadCase(ABD, Mx=1000, My=200)
    if max(deformation[3:6])<0.01:
        print('No. of plies=',i+1, 'where [Kx, Ky, Kxy =]',deformation[3:6])
        break  # satisfied, no reason to continue.
No. of plies= 14 where [Kx, Ky, Kxy =] [0.0039695  0.00975555 0.        ]

While the constraints and requirements are met with 14 plies, this is most likely not the optimum solution; some 90-oriented plies will probably reduce the required number of plies.

The brute force technique tests for all possible designs and will therefor find the absolut optimum solution.

The following code generates a list of all possible layup combinations for an even number of plies (6 plies for this example) where the number of combinations is 23 = 8

In [7]:
combs=[]     # List of combinitions
angs=(0,90)  # Possible orientations
for a1 in angs:
    for a2 in angs:
        for a3 in angs:
            combs.append([a1,a2,a3,a3,a2,a1])
print(combs)
[[0, 0, 0, 0, 0, 0], [0, 0, 90, 90, 0, 0], [0, 90, 0, 0, 90, 0], [0, 90, 90, 90, 90, 0], [90, 0, 0, 0, 0, 90], [90, 0, 90, 90, 0, 90], [90, 90, 0, 0, 90, 90], [90, 90, 90, 90, 90, 90]]

Alternative pythonic syntax of the same is:

In [8]:
angs=(0,90)
combs = [[a1,a2,a3,a3,a2,a1] for a1 in angs for a2 in angs for a3 in angs ]
print(combs)
[[0, 0, 0, 0, 0, 0], [0, 0, 90, 90, 0, 0], [0, 90, 0, 0, 90, 0], [0, 90, 90, 90, 90, 0], [90, 0, 0, 0, 0, 90], [90, 0, 90, 90, 0, 90], [90, 90, 0, 0, 90, 90], [90, 90, 90, 90, 90, 90]]

Testing the performance of all eight layups and sort the result from best to worst:

In [9]:
maxdef=[]
for c in combs:
    layup=[]
    for a in c:
        layup.append( {'mat':m , 'ori': a   , 'thi':0.2} )
    ABD=laminatelib.laminateStiffnessMatrix(layup)
    load,deformation=laminatelib.solveLaminateLoadCase(ABD, Mx=1000, My=200)
    maxdef.append(max(deformation[3:6]))

zipped = zip(maxdef, combs)
sorted_pairs = sorted(zipped)

for s in sorted_pairs:
    print('Max curvature:',s[0],'Layup:',s[1])
Max curvature: 0.06898343575733218 Layup: [0, 90, 0, 0, 90, 0]
Max curvature: 0.07231933113001443 Layup: [0, 90, 90, 90, 90, 0]
Max curvature: 0.08528572972059642 Layup: [0, 0, 90, 90, 0, 0]
Max curvature: 0.12393162393162388 Layup: [0, 0, 0, 0, 0, 0]
Max curvature: 0.15089624168218338 Layup: [90, 0, 0, 0, 0, 90]
Max curvature: 0.16727197859439027 Layup: [90, 0, 90, 90, 0, 90]
Max curvature: 0.4778039567055883 Layup: [90, 90, 0, 0, 90, 90]
Max curvature: 0.6914529914529913 Layup: [90, 90, 90, 90, 90, 90]

Since none of the solutions satisfied the limit on deformation, let us try with 12 plies:

In [10]:
angs=(0,90)
combs = [[a1,a2,a3,a4,a5,a6,a6,a5,a4,a3,a2,a1] 
         for a1 in angs for a2 in angs for a3 in angs for a4 in angs for a5 in angs for a6 in angs]
print('No. of combinations:',len(combs))
No. of combinations: 64

Results for the laminates that satisfy the constraints and requirements:

In [11]:
maxdef=[]
for c in combs:
    layup=[]
    for a in c:
        layup.append( {'mat':m , 'ori': a   , 'thi':0.2} )
    ABD=laminatelib.laminateStiffnessMatrix(layup)
    load,deformation=laminatelib.solveLaminateLoadCase(ABD, Mx=1000, My=200)
    maxdef.append(max(deformation[3:6]))

zipped = zip(maxdef, combs)
sorted_pairs = sorted(zipped)

for s in sorted_pairs:
    if s[0]<0.01:
        print('Max curvature:',s[0],'Layup:',s[1])
Max curvature: 0.007234129744898257 Layup: [0, 0, 0, 90, 0, 90, 90, 0, 90, 0, 0, 0]
Max curvature: 0.007315965943776281 Layup: [0, 0, 0, 90, 90, 0, 0, 90, 90, 0, 0, 0]
Max curvature: 0.0073547808051637655 Layup: [0, 0, 0, 90, 90, 90, 90, 90, 90, 0, 0, 0]
Max curvature: 0.007434777529212576 Layup: [0, 0, 0, 90, 0, 0, 0, 0, 90, 0, 0, 0]
Max curvature: 0.00775733992400395 Layup: [0, 0, 90, 0, 0, 0, 0, 0, 0, 90, 0, 0]
Max curvature: 0.007799282106249661 Layup: [0, 0, 90, 0, 0, 90, 90, 0, 0, 90, 0, 0]
Max curvature: 0.008058571162440369 Layup: [0, 0, 90, 0, 90, 0, 0, 90, 0, 90, 0, 0]
Max curvature: 0.00810314752057136 Layup: [0, 0, 90, 0, 90, 90, 90, 90, 0, 90, 0, 0]
Max curvature: 0.008622929469666526 Layup: [0, 0, 90, 90, 0, 0, 0, 0, 90, 90, 0, 0]
Max curvature: 0.008673113269026155 Layup: [0, 0, 90, 90, 0, 90, 90, 0, 90, 90, 0, 0]
Max curvature: 0.008879280259754267 Layup: [0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0]
Max curvature: 0.008932234400657272 Layup: [0, 90, 0, 0, 0, 90, 90, 0, 0, 0, 90, 0]
Max curvature: 0.008985775923505 Layup: [0, 0, 90, 90, 90, 0, 0, 90, 90, 90, 0, 0]
Max curvature: 0.009039916391251809 Layup: [0, 0, 90, 90, 90, 90, 90, 90, 90, 90, 0, 0]
Max curvature: 0.009262706739820404 Layup: [0, 90, 0, 0, 90, 0, 0, 90, 0, 0, 90, 0]
Max curvature: 0.0093200232861537 Layup: [0, 90, 0, 0, 90, 90, 90, 90, 0, 0, 90, 0]
Max curvature: 0.009998012353145147 Layup: [0, 90, 0, 90, 0, 0, 0, 0, 90, 0, 90, 0]

Out of 64 possible laminate designs, there are 17 acceptable solutions. However, an optimum could be found with fewer plies.

To go from 12 to 11 plies we only need to remove one of the instances of the paramter a6:

In [12]:
angs=(0,90)
combs = [[a1,a2,a3,a4,a5,a6,a5,a4,a3,a2,a1] 
         for a1 in angs for a2 in angs for a3 in angs for a4 in angs for a5 in angs for a6 in angs]
print('No. of combinations:',len(combs))
No. of combinations: 64

Results:

In [13]:
maxdef=[]
for c in combs:
    layup=[]
    for a in c:
        layup.append( {'mat':m , 'ori': a   , 'thi':0.2} )
    ABD=laminatelib.laminateStiffnessMatrix(layup)
    load,deformation=laminatelib.solveLaminateLoadCase(ABD, Mx=1000, My=200)
    maxdef.append(max(deformation[3:6]))

zipped = zip(maxdef, combs)
sorted_pairs = sorted(zipped)

for s in sorted_pairs:
    if s[0]<0.01:
        print('Max curvature:',s[0],'Layup:',s[1])
Max curvature: 0.009319990409756994 Layup: [0, 0, 0, 90, 90, 90, 90, 90, 0, 0, 0]
Max curvature: 0.009360715243209678 Layup: [0, 0, 0, 90, 90, 0, 90, 90, 0, 0, 0]
Max curvature: 0.009983737414396667 Layup: [0, 0, 90, 0, 0, 0, 0, 0, 90, 0, 0]
Max curvature: 0.009992431049849279 Layup: [0, 0, 90, 0, 0, 90, 0, 0, 90, 0, 0]

Conclusion: 11 plies are sufficient and there are 4 equivalent layups with respect to weight. Out of the 4 solutions, we may argue that the first in the list above performs better (smallest deformation) and is therefor the optimum solution. This is however not explicitly stated for the objective (but could be included for some added value).

Example 3: strength optimization, fixed ply thickness

Constraints and requirements:

  • Ply material: Carbon/Epoxy(a)

  • Ply thickness: 0.2 mm

  • Possible orientations: 0, 90, 45 and -45

  • Symmetric and balanced laminate

  • Load cases, both should be considered:

    • Case-1: Nxy = 500 N/mm
    • Case-2: Mx = 1000 Nmm/mm
  • Stress exposure factor according to Maximum stress criterion shall be < 1.0 considering all plies and all positions

Objectives:

  • Minimize the mass

For an odd number of plies (example: 8 plies):

In [14]:
angs=(0,90,45,-45)
combs = [[a1,a2,a3,a4,a4,a3,a2,a1] 
         for a1 in angs for a2 in angs for a3 in angs for a4 in angs ]
print('No. of combinations:',len(combs))
No. of combinations: 256

The algorithm does not check if a layup is balanced. Hence, we need a procedure that extract balanced layups only:

In [15]:
def isBalanced(c):
    suma=0
    for a in c:
        if ((a !=0) and (a !=90)):
            suma=suma+a
    if suma == 0:
        return True
    else:
        return False
    
balancedCombs=[]

for c in combs:
    if isBalanced(c):
        balancedCombs.append(c)

print('No. of combinations:',len(balancedCombs))
No. of combinations: 70

Thus, there are 256 combinations of symmetric layups but only 70 combinations of both symmetric and balanced laminates.

Another useful function: finding the greates exposure factor across all layers and positions based on the Maximum stress criterion:

In [16]:
def failExposure(LayerResults):
    fe = []
    for layer in LayerResults:
        fe.append( layer['fail']['MS']['bot']   )
        fe.append( layer['fail']['MS']['top']   )
    return max(fe)

Solving and and reporting the five best solutions:

In [17]:
def solveStrength(combinations):
    maxfe=[]
    for c in combinations:
        layup=[]
        for a in c:
            layup.append( {'mat':m , 'ori': a   , 'thi':0.2} )
        ABD=laminatelib.laminateStiffnessMatrix(layup)
        loads,deformations=laminatelib.solveLaminateLoadCase(ABD, Nxy=500)
        layerResults=laminatelib.layerResults(layup,deformations)
        maxfe1=failExposure(layerResults)
        loads,deformations=laminatelib.solveLaminateLoadCase(ABD, Mx=1000)
        layerResults=laminatelib.layerResults(layup,deformations)
        maxfe2=failExposure(layerResults)
        maxfe.append(max(maxfe1,maxfe2))
    zipped = zip(maxfe, combinations)
    sorted_pairs = sorted(zipped)
    return sorted_pairs
    

sp=solveStrength(balancedCombs)

for i in range(0,5):
    print(sp[i][0],sp[i][1])
2.177004338463762 [0, 0, -45, 45, 45, -45, 0, 0]
2.177004338463762 [0, 0, 45, -45, -45, 45, 0, 0]
2.679200349124809 [0, -45, 0, 45, 45, 0, -45, 0]
2.679200349124809 [0, 45, 0, -45, -45, 0, 45, 0]
2.9814487691212026 [0, -45, 45, 0, 0, 45, -45, 0]

Number of plies must be increased to meet the requirements:

In [18]:
angs=(0,90,45,-45)
combs = [[a1,a2,a3,a4,a5,a6,a6,a5,a4,a3,a2,a1] 
         for a1 in angs for a2 in angs for a3 in angs for a4 in angs for a5 in angs for a6 in angs ]
print('No. of combinations:',len(combs))
balancedCombs=[]
for c in combs:
    if isBalanced(c):
        balancedCombs.append(c)
print('No. of combinations:',len(balancedCombs))
No. of combinations: 4096
No. of combinations: 924
In [19]:
sp=solveStrength(balancedCombs)

for i in range(0,5):
    print(sp[i][0],sp[i][1])
1.1516895730313126 [0, 0, -45, 45, 45, -45, -45, 45, 45, -45, 0, 0]
1.1516895730313126 [0, 0, 45, -45, -45, 45, 45, -45, -45, 45, 0, 0]
1.1533487335453922 [0, 0, -45, 45, -45, 45, 45, -45, 45, -45, 0, 0]
1.1533487335453922 [0, 0, 45, -45, 45, -45, -45, 45, -45, 45, 0, 0]
1.1613775129490245 [0, 0, -45, -45, 45, 45, 45, 45, -45, -45, 0, 0]

Try with one more ply:

In [20]:
angs=(0,90,45,-45)
combs = [[a1,a2,a3,a4,a5,a6,a7,a6,a5,a4,a3,a2,a1] 
         for a1 in angs for a2 in angs for a3 in angs for a4 in angs for a5 in angs for a6 in angs for a7 in angs ]
print('No. of combinations:',len(combs))
balancedCombs=[]
for c in combs:
    if isBalanced(c):
        balancedCombs.append(c)
print('No. of combinations:',len(balancedCombs))
No. of combinations: 16384
No. of combinations: 1848
In [21]:
sp=solveStrength(balancedCombs)

for i in range(0,5):
    print(sp[i][0],sp[i][1])
1.0226250933835497 [0, 0, -45, 45, 45, -45, 0, -45, 45, 45, -45, 0, 0]
1.0226250933835497 [0, 0, 45, -45, -45, 45, 0, 45, -45, -45, 45, 0, 0]
1.0230385884731994 [0, 0, -45, 45, 45, -45, 90, -45, 45, 45, -45, 0, 0]
1.0230385884731994 [0, 0, 45, -45, -45, 45, 90, 45, -45, -45, 45, 0, 0]
1.0239977446042243 [0, 0, -45, 45, -45, 45, 0, 45, -45, 45, -45, 0, 0]

Very close but one more ply is required:

In [22]:
angs=(0,90,45,-45)
combs = [[a1,a2,a3,a4,a5,a6,a7,a7,a6,a5,a4,a3,a2,a1] 
         for a1 in angs for a2 in angs for a3 in angs for a4 in angs for a5 in angs for a6 in angs for a7 in angs ]
print('No. of combinations:',len(combs))
balancedCombs=[]
for c in combs:
    if isBalanced(c):
        balancedCombs.append(c)
print('No. of combinations:',len(balancedCombs))

sp=solveStrength(balancedCombs)

for i in range(0,10):
    print(sp[i][0],sp[i][1])
No. of combinations: 16384
No. of combinations: 3432
0.761068036828512 [0, 0, 0, 45, -45, -45, 45, 45, -45, -45, 45, 0, 0, 0]
0.761068036828512 [0, 0, 0, 45, -45, 45, -45, -45, 45, -45, 45, 0, 0, 0]
0.761068036828512 [0, 0, 0, 45, 45, -45, -45, -45, -45, 45, 45, 0, 0, 0]
0.7610680368285122 [0, 0, 0, -45, 45, 45, -45, -45, 45, 45, -45, 0, 0, 0]
0.7610680368285125 [0, 0, 0, -45, -45, 45, 45, 45, 45, -45, -45, 0, 0, 0]
0.7610680368285125 [0, 0, 0, -45, 45, -45, 45, 45, -45, 45, -45, 0, 0, 0]
0.8104119907335773 [0, 0, -45, 0, 45, 45, -45, -45, 45, 45, 0, -45, 0, 0]
0.8104119907335773 [0, 0, 45, 0, -45, -45, 45, 45, -45, -45, 0, 45, 0, 0]
0.8118010702710254 [0, 0, -45, 0, 45, -45, 45, 45, -45, 45, 0, -45, 0, 0]
0.8118010702710254 [0, 0, 45, 0, -45, 45, -45, -45, 45, -45, 0, 45, 0, 0]
A major improvement of performance is observed when going from 13 to 14 plies. Why?

The number of possible combinations for solutions having 14 plies is

In [23]:
n=len(balancedCombs)
print(n)
3432

Although the computational effort to solve all these laminates is manageable, some simple algorithms to identify and eliminate combinations that obviously seems to be less fitted can easily be made.

In real-life engineering using for example FEA, we simply CANNOT solve for all possible combinations and some intelligence must be employed. Observe that the computational effort to generate the list of combinations is much less than the computational time for solving the mechanics of the problem.

Let us randomly pick about 1% of the possible combinations:

In [24]:
from numpy.random import randint
samp=randint(low=0,high=n,size=int(n*0.01))  # about n/100 numbers betwwen 0 and total number of combinations (n)
print(samp)
sampCombs=[]
for i in samp:
    sampCombs.append(balancedCombs[i])
[2536   10  853  540 2922 3376 1501  785 2965  295 1254 2158  239  237
 2580 3115 1383   19 3292 1472 2351  417 3158  104 2636 1787 2469 3169
 2784 1132 2131 1616 3002 1678]

Then, solve and sort from best to worst:

In [25]:
sp = solveStrength(sampCombs)

for c in sp:
    print(c[0],c[1])
0.9091070970165275 [0, 0, -45, 45, 90, -45, 45, 45, -45, 90, 45, -45, 0, 0]
0.9126073508563693 [0, 0, -45, 45, 45, 90, -45, -45, 90, 45, 45, -45, 0, 0]
1.2560807189677212 [0, 0, 0, 0, 90, 45, -45, -45, 45, 90, 0, 0, 0, 0]
1.2560807189677214 [0, 0, 0, 0, -45, 45, 90, 90, 45, -45, 0, 0, 0, 0]
1.2560807189677214 [0, 45, 0, -45, 0, 0, 0, 0, 0, 0, -45, 0, 45, 0]
1.5027211273219934 [0, 0, 90, 90, 45, -45, 0, 0, -45, 45, 90, 90, 0, 0]
1.5212085960383142 [0, -45, 45, 90, 90, 0, 90, 90, 0, 90, 90, 45, -45, 0]
1.7934275244385574 [-45, 0, 45, 90, 45, 0, -45, -45, 0, 45, 90, 45, 0, -45]
1.8652483874778327 [0, 90, 0, 45, 0, -45, 90, 90, -45, 0, 45, 0, 90, 0]
1.8740860092076843 [-45, 45, 0, -45, 0, 0, 45, 45, 0, 0, -45, 0, 45, -45]
1.9992955854836385 [0, -45, 90, 90, 0, 0, 45, 45, 0, 0, 90, 90, -45, 0]
2.5544650704440617 [0, 90, 45, 90, -45, 0, 90, 90, 0, -45, 90, 45, 90, 0]
2.7422385366956226 [-45, -45, 45, 0, 45, 0, 0, 0, 0, 45, 0, 45, -45, -45]
3.0247628830242763 [45, -45, 45, 0, 45, -45, -45, -45, -45, 45, 0, 45, -45, 45]
3.0303806896161376 [90, 0, -45, 0, 45, 45, -45, -45, 45, 45, 0, -45, 0, 90]
3.507696767107677 [45, -45, 90, 0, 90, -45, 45, 45, -45, 90, 0, 90, -45, 45]
3.7261870402584325 [-45, 45, -45, 45, 90, 0, 0, 0, 0, 90, 45, -45, 45, -45]
3.773147811448079 [-45, 90, 45, 0, 0, 90, 90, 90, 90, 0, 0, 45, 90, -45]
4.2433053448119455 [45, 45, -45, 90, 90, 0, -45, -45, 0, 90, 90, -45, 45, 45]
4.531619609981357 [90, -45, 0, 45, 90, 45, -45, -45, 45, 90, 45, 0, -45, 90]
4.591802352831388 [90, 45, 0, -45, 90, 90, 0, 0, 90, 90, -45, 0, 45, 90]
4.689378320680719 [-45, 90, 45, 45, 0, 0, -45, -45, 0, 0, 45, 45, 90, -45]
4.75290124493659 [45, -45, -45, -45, 45, 0, 45, 45, 0, 45, -45, -45, -45, 45]
4.75290124493659 [45, -45, 45, -45, -45, 0, 45, 45, 0, -45, -45, 45, -45, 45]
5.283066597238096 [-45, 45, 90, 90, -45, 45, 90, 90, 45, -45, 90, 90, 45, -45]
5.339538870367666 [-45, 45, 90, 90, 90, 90, 0, 0, 90, 90, 90, 90, 45, -45]
6.615438569686075 [90, 90, -45, 0, 45, 90, 90, 90, 90, 45, 0, -45, 90, 90]
7.449487942195995 [45, 90, 90, 90, 0, -45, 0, 0, -45, 0, 90, 90, 90, 45]
7.9330100167298045 [90, 45, -45, 45, 45, -45, -45, -45, -45, 45, 45, -45, 45, 90]
7.9886896639628855 [45, 90, 90, -45, 90, 90, 0, 0, 90, 90, -45, 90, 90, 45]
8.099889701663542 [-45, 90, 90, 90, 0, 90, 45, 45, 90, 0, 90, 90, 90, -45]
8.181811207768067 [90, 90, 90, 0, 90, 90, 0, 0, 90, 90, 0, 90, 90, 90]
8.475999559169917 [90, -45, 45, 90, -45, 90, 45, 45, 90, -45, 90, 45, -45, 90]
8.884611335478294 [90, 45, 90, 90, 0, -45, 0, 0, -45, 0, 90, 90, 45, 90]

The resulting data can now be analyzed using various algorithms for pattern recognition. The basic question is: what characterizes good solutions and what characterizes bad solutions?

One low-hanging fruit is simply the difference between the composition of orientations. The simple code that follows computes the average number of plies for the different orientations for the 5 best and the 5 worst cases:

In [26]:
def averageNumberOf(sp,ori):
    best=0
    worst=0
    for i in range(0,5):
        best=best+sp[i][1].count(ori)
        worst=worst+sp[len(sp)-1-i][1].count(ori)
    return best/5, worst/5
In [27]:
print(' 0-plies, best/worst: ',averageNumberOf(sp,0))
print('90-plies, best/worst: ',averageNumberOf(sp,90))
print('45-plies, best/worst: ',averageNumberOf(sp,45))
 0-plies, best/worst:  (6.8, 2.4)
90-plies, best/worst:  (1.6, 7.6)
45-plies, best/worst:  (2.8, 2.0)

Based on these values, we may do a relatively moderate filtering:

In [28]:
filteredCombs=[]
n=0
for c in balancedCombs:
    if c.count(0)>4:
        if c.count(90)<5:
            if c.count(45)>2:
                filteredCombs.append(c)
                n=n+1
print('Remaining viable combinations:' ,n)  
Remaining viable combinations: 210

Additional random sampling from the remaining combinations could further reduce the options. In the following example, about 10% of the 210 combinations are randomly selected:

In [29]:
samp=randint(low=0,high=len(filteredCombs),size=20)
sampCombs=[]
for i in samp:
    sampCombs.append(filteredCombs[i])
    
sp = solveStrength(sampCombs)

for c in sp:
    print(c[0],c[1])
0.8593330239181465 [0, 0, 45, -45, 0, 45, -45, -45, 45, 0, -45, 45, 0, 0]
0.895296459180603 [0, 0, 45, -45, -45, 0, 45, 45, 0, -45, -45, 45, 0, 0]
0.9020000088036325 [0, 45, 0, 0, -45, 45, -45, -45, 45, -45, 0, 0, 45, 0]
0.9749149449889591 [0, 45, 0, 45, 0, -45, -45, -45, -45, 0, 45, 0, 45, 0]
0.9848942306005389 [45, 0, 0, 0, -45, -45, 45, 45, -45, -45, 0, 0, 0, 45]
1.0084301260741042 [0, -45, 0, 45, -45, 0, 45, 45, 0, -45, 45, 0, -45, 0]
1.0326288158722434 [0, -45, 0, 45, -45, 45, 0, 0, 45, -45, 45, 0, -45, 0]
1.0994966923841425 [-45, 0, 0, 45, 0, 45, -45, -45, 45, 0, 45, 0, 0, -45]
1.1127732351879076 [0, -45, 45, 0, 45, 0, -45, -45, 0, 45, 0, 45, -45, 0]
1.1450681557183855 [0, -45, 45, 0, -45, 45, 0, 0, 45, -45, 0, 45, -45, 0]
1.214891486398208 [0, -45, 45, -45, 0, 0, 45, 45, 0, 0, -45, 45, -45, 0]
1.2165942441399114 [-45, 0, 0, 45, 45, -45, 0, 0, -45, 45, 45, 0, 0, -45]
1.2513484355852467 [0, -45, 45, -45, 0, 45, 0, 0, 45, 0, -45, 45, -45, 0]
1.408036892491884 [-45, 0, 45, 0, 45, -45, 0, 0, -45, 45, 0, 45, 0, -45]
1.5225102911921222 [-45, 45, 0, 0, 0, 45, -45, -45, 45, 0, 0, 0, 45, -45]
1.5358664717449397 [45, 0, 45, -45, 0, -45, 0, 0, -45, 0, -45, 45, 0, 45]
1.5829031602450299 [-45, 0, 45, 45, 0, -45, 0, 0, -45, 0, 45, 45, 0, -45]
1.7132550032120637 [45, 45, 0, 0, 0, -45, -45, -45, -45, 0, 0, 0, 45, 45]
1.7180871856561397 [-45, 0, 45, 45, -45, 0, 0, 0, 0, -45, 45, 45, 0, -45]
2.1812422340974527 [-45, 45, 0, -45, 45, 0, 0, 0, 0, 45, -45, 0, 45, -45]

The obvious difference between best and worst is the orientation at outermost layers:

In [30]:
finalFilteredCombs=[]
n=0
for c in filteredCombs:
    if (c[0]==0 and c[1]==0):
                finalFilteredCombs.append(c)
                n=n+1
print('Remaining viable combinations:' ,n)  
Remaining viable combinations: 30
In [31]:
sp = solveStrength(finalFilteredCombs)

for c in sp:
    print(c[0],c[1])
0.761068036828512 [0, 0, 0, 45, -45, -45, 45, 45, -45, -45, 45, 0, 0, 0]
0.761068036828512 [0, 0, 0, 45, -45, 45, -45, -45, 45, -45, 45, 0, 0, 0]
0.761068036828512 [0, 0, 0, 45, 45, -45, -45, -45, -45, 45, 45, 0, 0, 0]
0.7610680368285122 [0, 0, 0, -45, 45, 45, -45, -45, 45, 45, -45, 0, 0, 0]
0.7610680368285125 [0, 0, 0, -45, -45, 45, 45, 45, 45, -45, -45, 0, 0, 0]
0.7610680368285125 [0, 0, 0, -45, 45, -45, 45, 45, -45, 45, -45, 0, 0, 0]
0.8104119907335773 [0, 0, -45, 0, 45, 45, -45, -45, 45, 45, 0, -45, 0, 0]
0.8104119907335773 [0, 0, 45, 0, -45, -45, 45, 45, -45, -45, 0, 45, 0, 0]
0.8118010702710254 [0, 0, -45, 0, 45, -45, 45, 45, -45, 45, 0, -45, 0, 0]
0.8118010702710254 [0, 0, 45, 0, -45, 45, -45, -45, 45, -45, 0, 45, 0, 0]
0.816473621702366 [0, 0, -45, 0, -45, 45, 45, 45, 45, -45, 0, -45, 0, 0]
0.816473621702366 [0, 0, 45, 0, 45, -45, -45, -45, -45, 45, 0, 45, 0, 0]
0.8587247745202514 [0, 0, -45, 45, 0, 45, -45, -45, 45, 0, 45, -45, 0, 0]
0.8587247745202514 [0, 0, 45, -45, 0, -45, 45, 45, -45, 0, -45, 45, 0, 0]
0.8593330239181465 [0, 0, -45, 45, 0, -45, 45, 45, -45, 0, 45, -45, 0, 0]
0.8593330239181465 [0, 0, 45, -45, 0, 45, -45, -45, 45, 0, -45, 45, 0, 0]
0.8692779308461361 [0, 0, -45, -45, 0, 45, 45, 45, 45, 0, -45, -45, 0, 0]
0.8692779308461361 [0, 0, 45, 45, 0, -45, -45, -45, -45, 0, 45, 45, 0, 0]
0.895296459180603 [0, 0, -45, 45, 45, 0, -45, -45, 0, 45, 45, -45, 0, 0]
0.895296459180603 [0, 0, 45, -45, -45, 0, 45, 45, 0, -45, -45, 45, 0, 0]
0.8969824439661401 [0, 0, -45, 45, -45, 0, 45, 45, 0, -45, 45, -45, 0, 0]
0.8969824439661401 [0, 0, 45, -45, 45, 0, -45, -45, 0, 45, -45, 45, 0, 0]
0.9020000088036325 [0, 0, -45, -45, 45, 0, 45, 45, 0, 45, -45, -45, 0, 0]
0.9020000088036325 [0, 0, 45, 45, -45, 0, -45, -45, 0, -45, 45, 45, 0, 0]
0.9151129922119039 [0, 0, -45, 45, 45, -45, 0, 0, -45, 45, 45, -45, 0, 0]
0.9151129922119039 [0, 0, 45, -45, -45, 45, 0, 0, 45, -45, -45, 45, 0, 0]
0.9161798299832166 [0, 0, -45, 45, -45, 45, 0, 0, 45, -45, 45, -45, 0, 0]
0.9161798299832166 [0, 0, 45, -45, 45, -45, 0, 0, -45, 45, -45, 45, 0, 0]
0.920298693782939 [0, 0, -45, -45, 45, 45, 0, 0, 45, 45, -45, -45, 0, 0]
0.920298693782939 [0, 0, 45, 45, -45, -45, 0, 0, -45, -45, 45, 45, 0, 0]

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.