Structural Optimization of a wingbox#
In the previous notebook we have seen the structural analysis of a single wingbox cell. We would like however to use these tools to optimize the various parameters of the wingbox in order to minimize the weight of the structure. In order to do so let’s remind ourselves what exactly we can change in order to improve the performance of this wingbox.
In the previous notebook we had defined the following parameters in order to perform the analysis.
\(n_{cell}\) - Number of cells
\(\text{loc}_{sp}\) - The location of the spars
\(t_{sk}\) - The thickness of the skin in each cell
\(A_{str}\) - The area of the stringers
\(t_{sp}\) - The thickness of the spars
\(n_{str}\) - The amount of stringers per cell
These variables were then loaded in the API data structure as shown below:
attr_dict = {
"n_cell":4,
"spar_loc_nondim":[0.3, 0.5, 0.7],
"t_sk_cell":[0.002,0.004,0.004, 0.004],
"area_str":20e-6,
"t_sp":0.008,
"str_cell":[8,8,7,8]
}
wingbox_struct = tud.Wingbox(**attr_dict)
However, there will be one major change. For the structural analysis of a wingbox only the stringer area is relevant however for the constraints the geometry of said stringers is also important as you will see further up in this notebook.
So in the optimization of the wingbox we wil add the stringer height, width and thickness to the optimization parameters.
The parameters which we will optimize for are the skin thickness in each cell, the area of the stringers, the thickness of the spars and finally the stringer of the cell. This leaves the amount of cells, the spar locations and the bay length to be decided by the designers. The reasoning behind this is that usually your rib and spar locations are constrained the placement of other systems in the wing such as the flap and slat mechanism. Below a summary can be found of the fixed and optimization parameters:
Optimiziation Parameters |
Fixed Parameters |
---|---|
Skin thickness in each cell |
The amount of cells |
Stringer width |
Spar locations |
Stringer height |
Bay length |
Stringer thickness |
|
Spar thickness |
|
The amount of stringers in each cell |
Laying out a framework for a full wing optimization#
In order define our constraints later on we will have to divide the wing in sections called bays. Each bay is enclosed by two ribs, thus the length of these bays is for the designer to decide. Once, a bay is defined, we can further split this up in a collection of flat sheets. Where each sheet is in turn enclosed by stringers. The boundary conditions for these stringers have been decided to be simply supported. In figure 1 we can see the result of the partitioning that was just described. Although we will not model the effects of taper (Future implementation).
Figure 1: Wing modeled as a combination of simply supported sheets and simply supported bays. Figure taken from T.H.G Megson, Aircraft Structures For Engineering Students
Now that our wing has been divided into bays, and the bays respectively into sheets we can limit the scope of the optimization. Instead, of optimizing for the entire wing simultaneously we will optimize each bay. In figure 2 an overview is found on how the optimization in this notebook will tackle the sizing of the structural members in the wing.
Figure 2: An overview of the optimization procedure for an entire wing.
The following sections will now enclose how to optimize a single one of these bays, starting with constraining the design space.
Constraining the design#
During the optimization it is important we set the minimum constraints the design should meet. Otherwise, the optimum solution would be no wingbox at all. For the API documentation of all these constraints please visit constraints documentation. We’ll cover the most important ones in this document and link to the documentation for the remainder of the constraints.
Buckling#
As previously discussed, a section of the wingbox is divided up in plates. Knowing these plates are simply supported we can compute the critical instability in both shear and compression using equations 1 and 2, respectively:
For the specifics on \(k_b\) and \(k_c\), please review the API documentation.
Whilst, we could simply apply them individually these two critical loads are not independent from each other. In increased shear loading, decreases the compression capability of the sheet. Hence, we use an interaction curve as described in equation 3.
In equation 3, \(N_{x,crit}\) is computed with equation 2 and \(N_{xy,crit}\) with equation 1. \(N_x\) and \(N_{xy}\) are the loads specified by the user.
Finally, we also constrain the design using global skin buckling as a stiffened panel can also buckle as a whole. In this case, the width of the panel is utilized instead of the stringer pitch, and simply supported conditions can be assumed. The contribution of the stringers that still provide a stiffening effect can be considered by smearing their thickness to the skin thickness, as in Equation 47.
The smeared thickness is substituted in the equation for critical sheet compression. The constraint is expressed below.
Von Mises failure Criterion#
Besides buckling we also do not want the wingbox to yield. Hence we also apply the von Mises yield criterion. Considering the direct stresses and plane stresses that occur in the wingbox we can derive equation 4.
Abiding by this constraint ensures that the wingbox does not yield under the expected loads.
Other constraints#
Stringer Flange Buckling (similar to buckling of sheet, except different BC’s)
Leveraging Python for the optimization#
In order to leverage Python for our optimization, we will first have to install two libraries into the google collab
environment. These are tuduam
and pymoo
, tuduam is a library specifically tailored for this course and pymoo a library for multi-objective optimization. Please run the code cell below in order to do.
!pip install tuduam
!pip install pymoo
!git clone https://github.com/saullocastro/tuduam.git
# If the following error occurs "fatal: destination path 'tuduam' already exists and is not an empty directory." please continue as your environment is already set up correctly
DEPRECATION: Loading egg at /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg is deprecated. pip 24.3 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330
Requirement already satisfied: tuduam in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg (2024.16)
Requirement already satisfied: numpy in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from tuduam) (1.26.4)
Requirement already satisfied: scipy in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from tuduam) (1.12.0)
DEPRECATION: Loading egg at /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg is deprecated. pip 24.3 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330
Requirement already satisfied: pymoo in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (0.6.1.1)
Requirement already satisfied: numpy>=1.15 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (1.26.4)
Requirement already satisfied: scipy>=1.1 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (1.12.0)
Requirement already satisfied: matplotlib>=3 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (3.9.1)
Requirement already satisfied: autograd>=1.4 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (1.6.2)
Requirement already satisfied: cma==3.2.2 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (3.2.2)
Requirement already satisfied: alive-progress in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (3.1.5)
Requirement already satisfied: dill in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (0.3.8)
Requirement already satisfied: Deprecated in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from pymoo) (1.2.14)
Requirement already satisfied: future>=0.15.2 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from autograd>=1.4->pymoo) (1.0.0)
Requirement already satisfied: contourpy>=1.0.1 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (4.47.2)
Requirement already satisfied: kiwisolver>=1.3.1 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (1.4.5)
Requirement already satisfied: packaging>=20.0 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (24.1)
Requirement already satisfied: pillow>=8 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (10.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (3.1.2)
Requirement already satisfied: python-dateutil>=2.7 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from matplotlib>=3->pymoo) (2.8.2)
Requirement already satisfied: about-time==4.2.1 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from alive-progress->pymoo) (4.2.1)
Requirement already satisfied: grapheme==0.6.0 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from alive-progress->pymoo) (0.6.0)
Requirement already satisfied: wrapt<2,>=1.10 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from Deprecated->pymoo) (1.16.0)
Requirement already satisfied: six>=1.5 in /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages (from python-dateutil>=2.7->matplotlib>=3->pymoo) (1.16.0)
Cloning into 'tuduam'...
remote: Enumerating objects: 1867, done.
remote: Counting objects: 0% (1/442)
remote: Counting objects: 1% (5/442)
remote: Counting objects: 2% (9/442)
remote: Counting objects: 3% (14/442)
remote: Counting objects: 4% (18/442)
remote: Counting objects: 5% (23/442)
remote: Counting objects: 6% (27/442)
remote: Counting objects: 7% (31/442)
remote: Counting objects: 8% (36/442)
remote: Counting objects: 9% (40/442)
remote: Counting objects: 10% (45/442)
remote: Counting objects: 11% (49/442)
remote: Counting objects: 12% (54/442)
remote: Counting objects: 13% (58/442)
remote: Counting objects: 14% (62/442)
remote: Counting objects: 15% (67/442)
remote: Counting objects: 16% (71/442)
remote: Counting objects: 17% (76/442)
remote: Counting objects: 18% (80/442)
remote: Counting objects: 19% (84/442)
remote: Counting objects: 20% (89/442)
remote: Counting objects: 21% (93/442)
remote: Counting objects: 22% (98/442)
remote: Counting objects: 23% (102/442)
remote: Counting objects: 24% (107/442)
remote: Counting objects: 25% (111/442)
remote: Counting objects: 26% (115/442)
remote: Counting objects: 27% (120/442)
remote: Counting objects: 28% (124/442)
remote: Counting objects: 29% (129/442)
remote: Counting objects: 30% (133/442)
remote: Counting objects: 31% (138/442)
remote: Counting objects: 32% (142/442)
remote: Counting objects: 33% (146/442)
remote: Counting objects: 34% (151/442)
remote: Counting objects: 35% (155/442)
remote: Counting objects: 36% (160/442)
remote: Counting objects: 37% (164/442)
remote: Counting objects: 38% (168/442)
remote: Counting objects: 39% (173/442)
remote: Counting objects: 40% (177/442)
remote: Counting objects: 41% (182/442)
remote: Counting objects: 42% (186/442)
remote: Counting objects: 43% (191/442)
remote: Counting objects: 44% (195/442)
remote: Counting objects: 45% (199/442)
remote: Counting objects: 46% (204/442)
remote: Counting objects: 47% (208/442)
remote: Counting objects: 48% (213/442)
remote: Counting objects: 49% (217/442)
remote: Counting objects: 50% (221/442)
remote: Counting objects: 51% (226/442)
remote: Counting objects: 52% (230/442)
remote: Counting objects: 53% (235/442)
remote: Counting objects: 54% (239/442)
remote: Counting objects: 55% (244/442)
remote: Counting objects: 56% (248/442)
remote: Counting objects: 57% (252/442)
remote: Counting objects: 58% (257/442)
remote: Counting objects: 59% (261/442)
remote: Counting objects: 60% (266/442)
remote: Counting objects: 61% (270/442)
remote: Counting objects: 62% (275/442)
remote: Counting objects: 63% (279/442)
remote: Counting objects: 64% (283/442)
remote: Counting objects: 65% (288/442)
remote: Counting objects: 66% (292/442)
remote: Counting objects: 67% (297/442)
remote: Counting objects: 68% (301/442)
remote: Counting objects: 69% (305/442)
remote: Counting objects: 70% (310/442)
remote: Counting objects: 71% (314/442)
remote: Counting objects: 72% (319/442)
remote: Counting objects: 73% (323/442)
remote: Counting objects: 74% (328/442)
remote: Counting objects: 75% (332/442)
remote: Counting objects: 76% (336/442)
remote: Counting objects: 77% (341/442)
remote: Counting objects: 78% (345/442)
remote: Counting objects: 79% (350/442)
remote: Counting objects: 80% (354/442)
remote: Counting objects: 81% (359/442)
remote: Counting objects: 82% (363/442)
remote: Counting objects: 83% (367/442)
remote: Counting objects: 84% (372/442)
remote: Counting objects: 85% (376/442)
remote: Counting objects: 86% (381/442)
remote: Counting objects: 87% (385/442)
remote: Counting objects: 88% (389/442)
remote: Counting objects: 89% (394/442)
remote: Counting objects: 90% (398/442)
remote: Counting objects: 91% (403/442)
remote: Counting objects: 92% (407/442)
remote: Counting objects: 93% (412/442)
remote: Counting objects: 94% (416/442)
remote: Counting objects: 95% (420/442)
remote: Counting objects: 96% (425/442)
remote: Counting objects: 97% (429/442)
remote: Counting objects: 98% (434/442)
remote: Counting objects: 99% (438/442)
remote: Counting objects: 100% (442/442)
remote: Counting objects: 100% (442/442), done.
remote: Compressing objects: 0% (1/261)
remote: Compressing objects: 1% (3/261)
remote: Compressing objects: 2% (6/261)
remote: Compressing objects: 3% (8/261)
remote: Compressing objects: 4% (11/261)
remote: Compressing objects: 5% (14/261)
remote: Compressing objects: 6% (16/261)
remote: Compressing objects: 7% (19/261)
remote: Compressing objects: 8% (21/261)
remote: Compressing objects: 9% (24/261)
remote: Compressing objects: 10% (27/261)
remote: Compressing objects: 11% (29/261)
remote: Compressing objects: 12% (32/261)
remote: Compressing objects: 13% (34/261)
remote: Compressing objects: 14% (37/261)
remote: Compressing objects: 15% (40/261)
remote: Compressing objects: 16% (42/261)
remote: Compressing objects: 17% (45/261)
remote: Compressing objects: 18% (47/261)
remote: Compressing objects: 19% (50/261)
remote: Compressing objects: 20% (53/261)
remote: Compressing objects: 21% (55/261)
remote: Compressing objects: 22% (58/261)
remote: Compressing objects: 23% (61/261)
remote: Compressing objects: 24% (63/261)
remote: Compressing objects: 25% (66/261)
remote: Compressing objects: 26% (68/261)
remote: Compressing objects: 27% (71/261)
remote: Compressing objects: 28% (74/261)
remote: Compressing objects: 29% (76/261)
remote: Compressing objects: 30% (79/261)
remote: Compressing objects: 31% (81/261)
remote: Compressing objects: 32% (84/261)
remote: Compressing objects: 33% (87/261)
remote: Compressing objects: 34% (89/261)
remote: Compressing objects: 35% (92/261)
remote: Compressing objects: 36% (94/261)
remote: Compressing objects: 37% (97/261)
remote: Compressing objects: 38% (100/261)
remote: Compressing objects: 39% (102/261)
remote: Compressing objects: 40% (105/261)
remote: Compressing objects: 41% (108/261)
remote: Compressing objects: 42% (110/261)
remote: Compressing objects: 43% (113/261)
remote: Compressing objects: 44% (115/261)
remote: Compressing objects: 45% (118/261)
remote: Compressing objects: 46% (121/261)
remote: Compressing objects: 47% (123/261)
remote: Compressing objects: 48% (126/261)
remote: Compressing objects: 49% (128/261)
remote: Compressing objects: 50% (131/261)
remote: Compressing objects: 51% (134/261)
remote: Compressing objects: 52% (136/261)
remote: Compressing objects: 53% (139/261)
remote: Compressing objects: 54% (141/261)
remote: Compressing objects: 55% (144/261)
remote: Compressing objects: 56% (147/261)
remote: Compressing objects: 57% (149/261)
remote: Compressing objects: 58% (152/261)
remote: Compressing objects: 59% (154/261)
remote: Compressing objects: 60% (157/261)
remote: Compressing objects: 61% (160/261)
remote: Compressing objects: 62% (162/261)
remote: Compressing objects: 63% (165/261)
remote: Compressing objects: 64% (168/261)
remote: Compressing objects: 65% (170/261)
remote: Compressing objects: 66% (173/261)
remote: Compressing objects: 67% (175/261)
remote: Compressing objects: 68% (178/261)
remote: Compressing objects: 69% (181/261)
remote: Compressing objects: 70% (183/261)
remote: Compressing objects: 71% (186/261)
remote: Compressing objects: 72% (188/261)
remote: Compressing objects: 73% (191/261)
remote: Compressing objects: 74% (194/261)
remote: Compressing objects: 75% (196/261)
remote: Compressing objects: 76% (199/261)
remote: Compressing objects: 77% (201/261)
remote: Compressing objects: 78% (204/261)
remote: Compressing objects: 79% (207/261)
remote: Compressing objects: 80% (209/261)
remote: Compressing objects: 81% (212/261)
remote: Compressing objects: 82% (215/261)
remote: Compressing objects: 83% (217/261)
remote: Compressing objects: 84% (220/261)
remote: Compressing objects: 85% (222/261)
remote: Compressing objects: 86% (225/261)
remote: Compressing objects: 87% (228/261)
remote: Compressing objects: 88% (230/261)
remote: Compressing objects: 89% (233/261)
remote: Compressing objects: 90% (235/261)
remote: Compressing objects: 91% (238/261)
remote: Compressing objects: 92% (241/261)
remote: Compressing objects: 93% (243/261)
remote: Compressing objects: 94% (246/261)
remote: Compressing objects: 95% (248/261)
remote: Compressing objects: 96% (251/261)
remote: Compressing objects: 97% (254/261)
remote: Compressing objects: 98% (256/261)
remote: Compressing objects: 99% (259/261)
remote: Compressing objects: 100% (261/261)
remote: Compressing objects: 100% (261/261), done.
Receiving objects: 0% (1/1867)
Receiving objects: 1% (19/1867)
Receiving objects: 2% (38/1867)
Receiving objects: 3% (57/1867)
Receiving objects: 4% (75/1867)
Receiving objects: 5% (94/1867)
Receiving objects: 6% (113/1867)
Receiving objects: 7% (131/1867)
Receiving objects: 8% (150/1867)
Receiving objects: 9% (169/1867)
Receiving objects: 10% (187/1867)
Receiving objects: 11% (206/1867)
Receiving objects: 12% (225/1867)
Receiving objects: 13% (243/1867)
Receiving objects: 14% (262/1867)
Receiving objects: 15% (281/1867)
Receiving objects: 16% (299/1867)
Receiving objects: 17% (318/1867)
Receiving objects: 18% (337/1867)
Receiving objects: 19% (355/1867)
Receiving objects: 20% (374/1867)
Receiving objects: 21% (393/1867)
Receiving objects: 22% (411/1867)
Receiving objects: 23% (430/1867)
Receiving objects: 24% (449/1867)
Receiving objects: 25% (467/1867)
Receiving objects: 26% (486/1867)
Receiving objects: 27% (505/1867)
Receiving objects: 28% (523/1867)
Receiving objects: 29% (542/1867)
Receiving objects: 30% (561/1867)
Receiving objects: 31% (579/1867)
Receiving objects: 32% (598/1867)
Receiving objects: 33% (617/1867)
Receiving objects: 34% (635/1867)
Receiving objects: 35% (654/1867)
Receiving objects: 36% (673/1867)
Receiving objects: 37% (691/1867)
Receiving objects: 38% (710/1867)
Receiving objects: 39% (729/1867)
Receiving objects: 40% (747/1867)
Receiving objects: 41% (766/1867)
Receiving objects: 42% (785/1867)
Receiving objects: 43% (803/1867)
Receiving objects: 44% (822/1867)
Receiving objects: 45% (841/1867)
Receiving objects: 46% (859/1867)
Receiving objects: 47% (878/1867)
Receiving objects: 48% (897/1867)
Receiving objects: 49% (915/1867)
Receiving objects: 50% (934/1867)
Receiving objects: 51% (953/1867)
Receiving objects: 52% (971/1867)
Receiving objects: 53% (990/1867)
Receiving objects: 54% (1009/1867)
Receiving objects: 55% (1027/1867)
Receiving objects: 56% (1046/1867)
Receiving objects: 57% (1065/1867)
Receiving objects: 58% (1083/1867)
Receiving objects: 59% (1102/1867)
Receiving objects: 60% (1121/1867)
Receiving objects: 61% (1139/1867)
Receiving objects: 62% (1158/1867)
Receiving objects: 63% (1177/1867)
Receiving objects: 64% (1195/1867)
Receiving objects: 65% (1214/1867)
Receiving objects: 66% (1233/1867)
Receiving objects: 67% (1251/1867)
Receiving objects: 68% (1270/1867)
Receiving objects: 69% (1289/1867)
Receiving objects: 70% (1307/1867)
Receiving objects: 71% (1326/1867)
Receiving objects: 72% (1345/1867)
Receiving objects: 73% (1363/1867)
Receiving objects: 74% (1382/1867)
Receiving objects: 75% (1401/1867)
Receiving objects: 76% (1419/1867)
Receiving objects: 77% (1438/1867)
Receiving objects: 78% (1457/1867)
Receiving objects: 79% (1475/1867)
Receiving objects: 80% (1494/1867)
Receiving objects: 81% (1513/1867)
Receiving objects: 82% (1531/1867)
Receiving objects: 83% (1550/1867)
Receiving objects: 84% (1569/1867)
Receiving objects: 85% (1587/1867)
Receiving objects: 86% (1606/1867)
Receiving objects: 87% (1625/1867)
Receiving objects: 88% (1643/1867)
Receiving objects: 89% (1662/1867)
Receiving objects: 90% (1681/1867)
Receiving objects: 91% (1699/1867)
Receiving objects: 92% (1718/1867)
Receiving objects: 93% (1737/1867)
Receiving objects: 94% (1755/1867)
Receiving objects: 95% (1774/1867)
Receiving objects: 96% (1793/1867)
Receiving objects: 97% (1811/1867)
Receiving objects: 98% (1830/1867)
Receiving objects: 99% (1849/1867)
remote: Total 1867 (delta 265), reused 330 (delta 177), pack-reused 1425 (from 1)
Receiving objects: 100% (1867/1867)
Receiving objects: 100% (1867/1867), 11.80 MiB | 39.87 MiB/s, done.
Resolving deltas: 0% (0/1087)
Resolving deltas: 1% (11/1087)
Resolving deltas: 2% (22/1087)
Resolving deltas: 3% (33/1087)
Resolving deltas: 4% (44/1087)
Resolving deltas: 5% (55/1087)
Resolving deltas: 6% (66/1087)
Resolving deltas: 7% (77/1087)
Resolving deltas: 8% (87/1087)
Resolving deltas: 9% (98/1087)
Resolving deltas: 10% (109/1087)
Resolving deltas: 11% (121/1087)
Resolving deltas: 12% (131/1087)
Resolving deltas: 13% (142/1087)
Resolving deltas: 14% (155/1087)
Resolving deltas: 15% (164/1087)
Resolving deltas: 16% (174/1087)
Resolving deltas: 17% (185/1087)
Resolving deltas: 18% (196/1087)
Resolving deltas: 19% (207/1087)
Resolving deltas: 20% (218/1087)
Resolving deltas: 21% (229/1087)
Resolving deltas: 22% (240/1087)
Resolving deltas: 23% (252/1087)
Resolving deltas: 24% (261/1087)
Resolving deltas: 25% (272/1087)
Resolving deltas: 26% (283/1087)
Resolving deltas: 27% (294/1087)
Resolving deltas: 28% (305/1087)
Resolving deltas: 29% (316/1087)
Resolving deltas: 30% (327/1087)
Resolving deltas: 31% (337/1087)
Resolving deltas: 32% (348/1087)
Resolving deltas: 33% (359/1087)
Resolving deltas: 34% (370/1087)
Resolving deltas: 35% (381/1087)
Resolving deltas: 36% (392/1087)
Resolving deltas: 37% (403/1087)
Resolving deltas: 38% (414/1087)
Resolving deltas: 39% (424/1087)
Resolving deltas: 40% (435/1087)
Resolving deltas: 41% (446/1087)
Resolving deltas: 42% (457/1087)
Resolving deltas: 43% (468/1087)
Resolving deltas: 44% (479/1087)
Resolving deltas: 45% (490/1087)
Resolving deltas: 46% (501/1087)
Resolving deltas: 47% (511/1087)
Resolving deltas: 48% (522/1087)
Resolving deltas: 49% (533/1087)
Resolving deltas: 50% (544/1087)
Resolving deltas: 51% (556/1087)
Resolving deltas: 52% (566/1087)
Resolving deltas: 53% (577/1087)
Resolving deltas: 54% (587/1087)
Resolving deltas: 55% (598/1087)
Resolving deltas: 56% (609/1087)
Resolving deltas: 57% (620/1087)
Resolving deltas: 58% (632/1087)
Resolving deltas: 59% (643/1087)
Resolving deltas: 60% (653/1087)
Resolving deltas: 61% (665/1087)
Resolving deltas: 62% (674/1087)
Resolving deltas: 63% (685/1087)
Resolving deltas: 64% (696/1087)
Resolving deltas: 65% (707/1087)
Resolving deltas: 66% (718/1087)
Resolving deltas: 67% (729/1087)
Resolving deltas: 68% (740/1087)
Resolving deltas: 69% (751/1087)
Resolving deltas: 70% (761/1087)
Resolving deltas: 71% (772/1087)
Resolving deltas: 72% (783/1087)
Resolving deltas: 73% (794/1087)
Resolving deltas: 74% (805/1087)
Resolving deltas: 75% (816/1087)
Resolving deltas: 76% (827/1087)
Resolving deltas: 77% (837/1087)
Resolving deltas: 78% (848/1087)
Resolving deltas: 79% (859/1087)
Resolving deltas: 80% (870/1087)
Resolving deltas: 81% (881/1087)
Resolving deltas: 82% (892/1087)
Resolving deltas: 83% (904/1087)
Resolving deltas: 84% (914/1087)
Resolving deltas: 85% (924/1087)
Resolving deltas: 86% (935/1087)
Resolving deltas: 87% (946/1087)
Resolving deltas: 88% (957/1087)
Resolving deltas: 89% (968/1087)
Resolving deltas: 90% (979/1087)
Resolving deltas: 91% (990/1087)
Resolving deltas: 92% (1001/1087)
Resolving deltas: 93% (1012/1087)
Resolving deltas: 94% (1022/1087)
Resolving deltas: 95% (1033/1087)
Resolving deltas: 96% (1044/1087)
Resolving deltas: 97% (1055/1087)
Resolving deltas: 98% (1066/1087)
Resolving deltas: 99% (1077/1087)
Resolving deltas: 100% (1087/1087)
Resolving deltas: 100% (1087/1087), done.
Initialisation of parameters#
To start the initialisation of the optimisation that we are going to perform there are a two parameters that require a definition as these are designer choices.
import os
import sys
# sys.path.append("/content/tuduam")
# os.chdir("/content/tuduam")
import tuduam
# import importlib
# importlib.reload(tuduam) # Force reload of the module
from tuduam.data_structures import Wingbox, Material
attr_dict = {
"n_cell":4,
"spar_loc_nondim":[0.3, 0.5, 0.75],
"str_cell": [9,7,9,8]
}
mat_dict = {
"young_modulus":3e9,
"shear_modulus":80e9,
"safety_factor":1.5,
"load_factor":1.0,
"poisson":0.3,
"density":1600,
"beta_crippling":1.42,
"sigma_ultimate":407000000.0,
"sigma_yield":407000000.0,
"g_crippling":5
}
mat_struct = Material(**mat_dict)
wingbox_struct = Wingbox(**attr_dict)
Having this definitnon we can now start the optimization. Before continuing it is probably useful to first take a look through the API documentation for the class and its methods that we will be using. The class that we will be using is SectionOpt
which is situated in the structures
subpackage in tuduam. The link to the API docs can be found here.
Before iterating for an entire wing, let us first do an example for a single section. We’ll take a section of the wing where the mean chord, \(\bar{c} = 2\) and the length of this section \(b = 1.2\). For the airfoil, we’ll use th NACA 4412. For the amount of cells and spar locations we’ll use the previously defined values.
from tuduam.structures import SectionOpt
import os
chord = 3 # Chord length
coord_path = os.path.realpath(os.path.join(os.path.abspath('.'), 'tuduam', 'examples', 'naca_4412.txt')) # Path to airfoil coordinates
len_sec = .2 # Lenght of the section
opt_obj = SectionOpt(coord_path, chord, len_sec, wingbox_struct, mat_struct)
Remember figure 2 which portrayed the top view of the optimization loop. Let us start at the core, i.e the optimization that is run for a fixed amount of stringers. For us these stringers were defined wingbox_struct
. This optimization in the library is defined in the method GA_optimize
, here you can find the specifics.
Let’s run the optimization below with some typical values for the loads! Also note that we specify the upper and lower boundsFeel free to alter them and watch the design change.
import numpy as np
shear_y = 30e3
shear_x = 15000
moment_y = 4e2
moment_x = 30e3
applied_loc = 0.45
opt = SectionOpt(coord_path, chord, len_sec, wingbox_struct, mat_struct)
upper_bnds = 8*[0.012]
lower_bnds = 4*[0.0001] + [0.003] + [0.001] + 2*[0.003]
res = opt.GA_optimize(shear_y, shear_x, moment_y, moment_x, applied_loc, upper_bnds, lower_bnds, pop=20, n_gen= 60,multiprocess=True, verbose= True)
print(f"\n\n======== Results ==========")
print(f"Skin thickness = {np.array(res.X[:4])*1000} mm" )
print(f"Spar thickness = {res.X[4]*1000} mm")
print(f"Stringer thickness = {res.X[5]*1000} mm")
print(f"Stringer width= {res.X[6]*1000} mm")
print(f"Stringer height = {res.X[7]*1000} mm")
# res = opt_obj.full_section_opt(, pop=100, pop_full= 10, n_gen= 5 ,n_gen_full= 2)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:331: UserWarning: The thickness of stringer is larger than than the height of the stringer perhaps resulting in a negative area.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:331: UserWarning: The thickness of stringer is larger than than the height of the stringer perhaps resulting in a negative area.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:331: UserWarning: The thickness of stringer is larger than than the height of the stringer perhaps resulting in a negative area.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:334: UserWarning: The thickness of stringer is larger than than the width of the stringer resulting in nonsensical geometries.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:334: UserWarning: The thickness of stringer is larger than than the width of the stringer resulting in nonsensical geometries.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:339: UserWarning: The stringer area is negative, please see previous error
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:339: UserWarning: The stringer area is negative, please see previous error
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/constraints.py:437: RuntimeWarning: invalid value encountered in scalar power
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/constraints.py:437: RuntimeWarning: invalid value encountered in scalar power
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:334: UserWarning: The thickness of stringer is larger than than the width of the stringer resulting in nonsensical geometries.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:331: UserWarning: The thickness of stringer is larger than than the height of the stringer perhaps resulting in a negative area.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:334: UserWarning: The thickness of stringer is larger than than the width of the stringer resulting in nonsensical geometries.
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:339: UserWarning: The stringer area is negative, please see previous error
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:635: UserWarning: Negative boom areas encountered this is currently a bug, temporary fix takes the absolute value
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/constraints.py:437: RuntimeWarning: invalid value encountered in scalar power
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:339: UserWarning: The stringer area is negative, please see previous error
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/constraints.py:437: RuntimeWarning: invalid value encountered in scalar power
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:635: UserWarning: Negative boom areas encountered this is currently a bug, temporary fix takes the absolute value
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:1389: UserWarning: 9 was not an even number and will be floored for conservative reasons
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:1389: UserWarning: 7 was not an even number and will be floored for conservative reasons
=================================================================================
n_gen | n_eval | cv_min | cv_avg | f_avg | f_min
=================================================================================
1 | 20 | nan | nan | - | -
2 | 40 | 5.892335E+05 | 2.586893E+06 | - | -
3 | 60 | 1.597098E+04 | 8.828549E+05 | - | -
4 | 80 | 1.475767E+04 | 4.876832E+05 | - | -
5 | 100 | 1.572664E+02 | 2.307658E+05 | - | -
6 | 120 | 1.330916E+02 | 1.778502E+04 | - | -
7 | 140 | 8.390189E+01 | 3.090627E+02 | - | -
8 | 160 | 4.823260E+01 | 1.332849E+02 | - | -
9 | 180 | 2.876452E+01 | 1.026962E+02 | - | -
10 | 200 | 2.354275E+01 | 6.086254E+01 | - | -
11 | 220 | 1.767519E+01 | 3.868588E+01 | - | -
12 | 240 | 1.767519E+01 | 2.547644E+01 | - | -
13 | 260 | 1.457934E+01 | 2.154567E+01 | - | -
14 | 280 | 1.457934E+01 | 1.884780E+01 | - | -
15 | 300 | 7.3260777168 | 1.565098E+01 | - | -
16 | 320 | 5.5390067387 | 1.291093E+01 | - | -
17 | 340 | 5.5214687616 | 9.3546616528 | - | -
18 | 360 | 5.3663213535 | 6.5134697471 | - | -
19 | 380 | 5.1252980579 | 5.4626972814 | - | -
20 | 400 | 2.7705223748 | 5.1812955117 | - | -
21 | 420 | 1.8802673609 | 4.4893896406 | - | -
22 | 440 | 0.8128057312 | 3.1804604891 | - | -
23 | 460 | 0.7976215623 | 2.0264520478 | - | -
24 | 480 | 0.3837283234 | 1.1931978843 | - | -
25 | 500 | 0.3453816585 | 0.7119007667 | - | -
26 | 520 | 0.0047099656 | 0.5012213253 | - | -
27 | 540 | 0.0047099656 | 0.2679410870 | - | -
28 | 560 | 0.0042659691 | 0.1118058332 | - | -
29 | 580 | 0.0042659691 | 0.0059999780 | - | -
30 | 600 | 0.0038944594 | 0.0045613230 | - | -
31 | 620 | 0.0034608084 | 0.0043678042 | - | -
32 | 640 | 0.0026648398 | 0.0039910481 | - | -
33 | 660 | 0.0023822746 | 0.0034588027 | - | -
34 | 680 | 0.0014011219 | 0.0029460993 | - | -
35 | 700 | 0.000000E+00 | 0.0022065751 | 0.0429117122 | 0.0429117122
36 | 720 | 0.000000E+00 | 0.0014997832 | 0.0430613629 | 0.0429117122
37 | 740 | 0.000000E+00 | 0.0008837850 | 0.0428172072 | 0.0413998642
38 | 760 | 0.000000E+00 | 0.0000371314 | 0.0427315086 | 0.0411576838
39 | 780 | 0.000000E+00 | 0.000000E+00 | 0.0422681761 | 0.0410578780
40 | 800 | 0.000000E+00 | 0.000000E+00 | 0.0415369764 | 0.0409264512
41 | 820 | 0.000000E+00 | 0.000000E+00 | 0.0411469917 | 0.0405330817
42 | 840 | 0.000000E+00 | 0.000000E+00 | 0.0409429253 | 0.0404786924
43 | 860 | 0.000000E+00 | 0.000000E+00 | 0.0406877301 | 0.0397829212
44 | 880 | 0.000000E+00 | 0.000000E+00 | 0.0405264004 | 0.0397829212
45 | 900 | 0.000000E+00 | 0.000000E+00 | 0.0403892590 | 0.0397829212
46 | 920 | 0.000000E+00 | 0.000000E+00 | 0.0402376947 | 0.0391904389
47 | 940 | 0.000000E+00 | 0.000000E+00 | 0.0399770525 | 0.0388116677
48 | 960 | 0.000000E+00 | 0.000000E+00 | 0.0395450563 | 0.0387084247
49 | 980 | 0.000000E+00 | 0.000000E+00 | 0.0390217266 | 0.0383586854
50 | 1000 | 0.000000E+00 | 0.000000E+00 | 0.0387678747 | 0.0383586854
51 | 1020 | 0.000000E+00 | 0.000000E+00 | 0.0386958158 | 0.0383561457
52 | 1040 | 0.000000E+00 | 0.000000E+00 | 0.0385594842 | 0.0381963505
53 | 1060 | 0.000000E+00 | 0.000000E+00 | 0.0383947490 | 0.0381682142
54 | 1080 | 0.000000E+00 | 0.000000E+00 | 0.0382808392 | 0.0381682142
55 | 1100 | 0.000000E+00 | 0.000000E+00 | 0.0381951416 | 0.0380180272
56 | 1120 | 0.000000E+00 | 0.000000E+00 | 0.0381173639 | 0.0378847512
57 | 1140 | 0.000000E+00 | 0.000000E+00 | 0.0380221608 | 0.0378824289
58 | 1160 | 0.000000E+00 | 0.000000E+00 | 0.0379554328 | 0.0378442887
59 | 1180 | 0.000000E+00 | 0.000000E+00 | 0.0378859941 | 0.0377812016
60 | 1200 | 0.000000E+00 | 0.000000E+00 | 0.0378435873 | 0.0377159718
======== Results ==========
Skin thickness = [6.38311823 6.59095961 6.03707018 4.61159371] mm
Spar thickness = 4.996795247355678 mm
Stringer thickness = 4.2038652157530505 mm
Stringer width= 5.7953735714898995 mm
Stringer height = 9.85470647635964 mm
We can also get to know more about the history of each iteration through the res
variable which is an instance of the pymoo.core.result.Result
class. In this class the objective functions is described with F
and the design vector with X
. The object contains the history of each generation which we can access like shown below where we access the first generation.
pop_lst = [gen.pop for gen in res.history]
print("Generation 1\nt_sk_cell1 - t_sk_cell2 - t_sk_cell3 - t_sk_cell4 - t_sp - t_st - w_st - h_st")
print(f"=====================================================================\n\n {pop_lst[0].get('X')}")
Generation 1
t_sk_cell1 - t_sk_cell2 - t_sk_cell3 - t_sk_cell4 - t_sp - t_st - w_st - h_st
=====================================================================
[[0.00506256 0.00867186 0.00010136 0.00369776 0.0043208 0.00201572
0.00467634 0.00611005]
[0.00482153 0.00651192 0.00508841 0.00825411 0.00484007 0.01065929
0.00324649 0.00903421]
[0.00506593 0.00674841 0.0017706 0.00245741 0.0102067 0.01165088
0.00582082 0.0092309 ]
[0.01052903 0.01074582 0.00111203 0.00056475 0.00452847 0.01065957
0.00388512 0.00678997]
[0.01149889 0.00644467 0.00833334 0.00385464 0.00917851 0.01018088
0.00316459 0.0097513 ]
[0.01186745 0.00900317 0.00343728 0.00949242 0.00392903 0.00592683
0.01117736 0.00564253]
[0.00352453 0.00164734 0.00033047 0.00817814 0.00490465 0.00392101
0.00742416 0.00348026]
[0.006932 0.00184607 0.00711274 0.00842712 0.00392101 0.00555462
0.0092496 0.00672761]
[0.00069445 0.00647717 0.00799916 0.00622718 0.01150135 0.00745211
0.01113062 0.00423727]
[0.00175739 0.00970796 0.00483235 0.00206771 0.01134758 0.00482542
0.00975731 0.00953398]
[0.01061134 0.0075217 0.00903621 0.00425189 0.00542935 0.01085475
0.00685282 0.01168356]
[0.00799495 0.00749818 0.00146548 0.01139892 0.00704921 0.00736229
0.00667323 0.00513324]
[0.01085022 0.00692679 0.00013416 0.00744402 0.0059398 0.00679764
0.01097348 0.00621543]
[0.01091157 0.00751799 0.00028827 0.0111603 0.00921807 0.01197055
0.00455106 0.00423422]
[0.01119789 0.00839214 0.0008854 0.00909001 0.00978489 0.01115327
0.00940372 0.00411844]
[0.00033657 0.00041191 0.00043685 0.00302991 0.01074025 0.00692714
0.0079754 0.01057828]
[0.00157766 0.00342229 0.00707054 0.01163819 0.00804927 0.00120512
0.01020569 0.00509677]
[0.00970455 0.00471554 0.01037615 0.00899075 0.00800616 0.00250101
0.00353926 0.00409209]
[0.00063017 0.00137918 0.00278594 0.00858457 0.00803745 0.00113812
0.00364777 0.01170549]
[0.0068604 0.00251919 0.00310268 0.00895153 0.00475887 0.00739495
0.01173018 0.01062146]]
We can also plot the average value of variables over time and optimization function over time, this gives us an idea of how the variables effect the design. Run the code below:
import matplotlib.pyplot as plt
t_sk_cell1_lst = [np.average(pop.get("X")[:][0]) for pop in pop_lst]
t_sk_cell2_lst = [np.average(pop.get("X")[:][1]) for pop in pop_lst]
t_sk_cell3_lst = [np.average(pop.get("X")[:][2]) for pop in pop_lst]
t_sk_cell4_lst = [np.average(pop.get("X")[:][3]) for pop in pop_lst]
t_sp_lst = [np.average(pop.get("X")[:][4]) for pop in pop_lst]
t_st_lst = [np.average(pop.get("X")[:][5]) for pop in pop_lst]
w_st_lst = [np.average(pop.get("X")[:][6]) for pop in pop_lst]
h_st__lst = [np.average(pop.get("X")[:][7]) for pop in pop_lst]
f_lst = [np.average(pop.get("F")) for pop in pop_lst]
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18.5, 10.5)
ax1.plot(t_sk_cell1_lst, label="T sk cell 1")
ax1.plot(t_sk_cell2_lst, label="T sk cell 2")
ax1.plot(t_sk_cell3_lst, label="T sk cell 3")
ax1.plot(t_sk_cell4_lst, label="T sk cell 4")
ax1.plot(t_sp_lst, label="T sp")
ax1.plot(t_st_lst, label="T st")
ax1.plot(w_st_lst, label="W st")
ax1.plot(h_st__lst, label="H st")
ax2.plot(f_lst, label="Weight")
ax1.set_title("Average value of variables over time")
ax2.set_title("Average value of the optimization function over time")
ax1.set_xlabel("Generation")
ax2.set_xlabel("Generation")
ax1.set_ylabel("Thickness (mm)")
ax1.legend()
ax2.legend()
ax1.grid()
ax2.grid()
plt.show()

As you can see, it gets quite messy with all variables in there. One could also argue the information gets lost when taking the average and only plotting the optimum solution would give a better idea. It is left up to the reader to implement this.
Changing the amount of stringers#
In the previous optimization we only optimized for the various thicknesses, however a better solution would be possible when different configuration of stringers are used, or even a different layout of ribs.
TUDuam
also provides a method for optimizing for different stringer configuration. This is the outer optimization loop as has been shown earlier in this notebook in figure 2. The API documentation for this functionality can be found here. SectionOpt.full_section_opt
interally also used the optimization which we experimented with previously. However, it calls this function for every stringer configuration, hence it is signficantly more computationally expensive.
Additionally, stringers often run along multiple sections and hence we’re not completely free in the amount of stringer in each section.
The method does come with a multiprocess
flag which does allow it to see major performance boosts on more powerful platforms. So feel free to give it a try on your local machine. The setup is very similar to GA_optimize
.
Unlike for the stringer configuration, this repository does not implememt an optimization of the spanwise rib distribution.
Example: Optimizing the wing of a Cessna 172R#
Let us do an example of how we would design the structure of a complete wing. To keep it computationally bounded we are assuming a 3 cell wingbox with a fixed stringer distribution of 10 stringers per cell. Furthermore, here is some important data on the Cessna 172
Characteristic |
Value |
---|---|
Wingspan |
11 m |
Aspect ratio |
7.32 |
Surface Area |
16.2 \(m^2\) |
Gross weight |
1110 kg |
To come up with the correct loading per bay we make some oversimplified assumptions to make our life easier for this notebook.
Assumptions#
Constant spanwise drag distribution of 230 N/m
Therefore a linear
My
distribution (see below)A linear lift distribution which is maximum at the root of the wing.
Hence, a cubic
Mx
distributionIgnoring the weight of the wing (conservative)
The functions below will be used to get the correct internal forces for each rib section, as can be inferred from average_fun
we assume a constant force over each section. This constant force is taken to be the average value over the entire section.
from scipy.integrate import quad
import matplotlib.pyplot as plt
import numpy as np
def average_fun(fun,
x_start: float,
x_end: float
):
"""
Calculate the average value of a function `func` over the interval [a, b].
Parameters:
func (function): A function that takes a float and returns a float.
a (float): Lower bound of the interval.
b (float): Upper bound of the interval.
Returns:
float: The average value of the function over [a, b].
"""
val, _ = quad(fun, x_start, x_end)
return val / (x_end - x_start)
def shear_y(span):
return 5444 - quad(lambda x: 1979 - 1979/5.5*x, 0, span)[0]
def moment_y(span):
return -9980 + quad(shear_y, 0, span)[0]
def shear_x(span):
return -1265 + 230*span
def moment_x(span):
return 3478.75 + quad(shear_x, 0, span)[0]
# print(average_fun(moment_x, 5, 5.5))
# print(moment_y(3))
# x = np.linspace(0,5.5,1000)
# y = np.vectorize(moment_x)(x)
# plt.plot(x,y)
# plt.show()
Creating the section#
We must now create the section to iterate over. See the code below whwere we create the rib locations. You can change the exponent of the slicer
variable to alter the distribution of the rib locations
import numpy as np
span = 5.5
S = 16.2
n_sec = 8
chord = S/span
slicer = np.linspace(0,1, n_sec)**1.6
rib_loc = slicer*span
print(rib_loc)
[0. 0.24445888 0.74106076 1.41775058 2.24647615 3.21039028
4.29781608 5.5 ]
Iterating over each section#
With the rib locations and loads specified we can iterate over all the sections. Please see the code below.
from tuduam.data_structures import Wingbox, Material
import warnings
warnings.simplefilter("ignore")
res_lst = []
for idx, rib_1 in enumerate(rib_loc, start=1):
if idx == len(rib_loc):
break
else:
rib_2 = rib_loc[idx]
attr_dict = {
"n_cell":3,
"spar_loc_nondim":[0.3, 0.7],
"str_cell": [10,10,10]
}
wingbox_struct = Wingbox(**attr_dict)
shear_y_sec = average_fun(shear_y, rib_1, rib_2)
shear_x_sec = average_fun(shear_x, rib_1, rib_2)
moment_y_sec = average_fun(moment_y, rib_1, rib_2)
moment_x_sec = average_fun(moment_x, rib_1, rib_2)
applied_loc = 0.45
opt = SectionOpt(coord_path, chord, len_sec, wingbox_struct, mat_struct)
upper_bnds = 7*[0.012]
lower_bnds = 3*[0.0001] + [0.003] + [0.001] + 2*[0.003]
res = opt.GA_optimize(shear_y_sec, shear_x_sec, moment_y_sec, moment_x_sec, applied_loc, upper_bnds, lower_bnds, pop=20, n_gen= 60,multiprocess= False, verbose= False)
res_lst.append(res)
print(f"\n\n======== Results rib section {idx} rib {round(rib_1,2)} to {round(rib_2,2)} ==========")
print(f"Skin thickness = {np.round(np.array(res.X[:3])*1000,2)} mm" )
print(f"Spar thickness = {round(res.X[3]*1000,2)} mm")
print(f"Stringer thickness = {round(res.X[4]*1000,2)} mm")
print(f"Stringer width= {round(res.X[5]*1000,2)} mm")
print(f"Stringer height = {round(res.X[6]*1000,2)} mm")
======== Results rib section 1 rib 0.0 to 0.24 ==========
Skin thickness = [2.75 2.93 2.47] mm
Spar thickness = 5.88 mm
Stringer thickness = 8.64 mm
Stringer width= 9.64 mm
Stringer height = 8.75 mm
======== Results rib section 2 rib 0.24 to 0.74 ==========
Skin thickness = [2.67 2.86 2.33] mm
Spar thickness = 3.08 mm
Stringer thickness = 6.61 mm
Stringer width= 6.66 mm
Stringer height = 8.77 mm
======== Results rib section 3 rib 0.74 to 1.42 ==========
Skin thickness = [2.56 2.6 2.07] mm
Spar thickness = 4.44 mm
Stringer thickness = 7.73 mm
Stringer width= 7.73 mm
Stringer height = 7.98 mm
======== Results rib section 4 rib 1.42 to 2.25 ==========
Skin thickness = [2.35 2.3 1.78] mm
Spar thickness = 5.04 mm
Stringer thickness = 1.65 mm
Stringer width= 7.4 mm
Stringer height = 10.19 mm
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[9], line 31
29 upper_bnds = 7*[0.012]
30 lower_bnds = 3*[0.0001] + [0.003] + [0.001] + 2*[0.003]
---> 31 res = opt.GA_optimize(shear_y_sec, shear_x_sec, moment_y_sec, moment_x_sec, applied_loc, upper_bnds, lower_bnds, pop=20, n_gen= 60,multiprocess= False, verbose= False)
32 res_lst.append(res)
34 print(f"\n\n======== Results rib section {idx} rib {round(rib_1,2)} to {round(rib_2,2)} ==========")
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/optimization.py:630, in SectionOpt.GA_optimize(self, shear_y, shear_x, moment_y, moment_x, applied_loc, upper_bnds, lower_bnds, n_gen, pop, verbose, seed, multiprocess, cores, save_hist)
614 problem = ProblemFixedPanel(
615 shear_y,
616 shear_x,
(...)
626 self.path_coord,
627 )
629 method = GA(pop_size=pop, eliminate_duplicates=True)
--> 630 resGA = minimize(
631 problem,
632 method,
633 termination=("n_gen", n_gen),
634 seed=seed,
635 save_history=save_hist,
636 verbose=verbose,
637 )
638 return resGA
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/optimize.py:67, in minimize(problem, algorithm, termination, copy_algorithm, copy_termination, **kwargs)
64 algorithm.setup(problem, **kwargs)
66 # actually execute the algorithm
---> 67 res = algorithm.run()
69 # store the deep copied algorithm in the result object
70 res.algorithm = algorithm
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/algorithm.py:138, in Algorithm.run(self)
136 def run(self):
137 while self.has_next():
--> 138 self.next()
139 return self.result()
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/algorithm.py:158, in Algorithm.next(self)
156 # call the advance with them after evaluation
157 if infills is not None:
--> 158 self.evaluator.eval(self.problem, infills, algorithm=self)
159 self.advance(infills=infills)
161 # if the algorithm does not follow the infill-advance scheme just call advance
162 else:
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/evaluator.py:69, in Evaluator.eval(self, problem, pop, skip_already_evaluated, evaluate_values_of, count_evals, **kwargs)
65 # evaluate the solutions (if there are any)
66 if len(I) > 0:
67
68 # do the actual evaluation - call the sub-function to set the corresponding values to the population
---> 69 self._eval(problem, pop[I], evaluate_values_of, **kwargs)
71 # update the function evaluation counter
72 if count_evals:
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/evaluator.py:90, in Evaluator._eval(self, problem, pop, evaluate_values_of, **kwargs)
87 X = pop.get("X")
89 # call the problem to evaluate the solutions
---> 90 out = problem.evaluate(X, return_values_of=evaluate_values_of, return_as_dictionary=True, **kwargs)
92 # for each of the attributes set it to the problem
93 for key, val in out.items():
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/problem.py:257, in Problem.evaluate(self, X, return_values_of, return_as_dictionary, *args, **kwargs)
254 only_single_value = not (isinstance(X, list) or isinstance(X, np.ndarray))
256 # this is where the actual evaluation takes place
--> 257 _out = self.do(X, return_values_of, *args, **kwargs)
259 out = {}
260 for k, v in _out.items():
261
262 # copy it to a numpy array (it might be one of jax at this point)
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/problem.py:297, in Problem.do(self, X, return_values_of, *args, **kwargs)
295 # do the function evaluation
296 if self.elementwise:
--> 297 self._evaluate_elementwise(X, out, *args, **kwargs)
298 else:
299 self._evaluate_vectorized(X, out, *args, **kwargs)
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/problem.py:315, in Problem._evaluate_elementwise(self, X, out, *args, **kwargs)
312 f = self.elementwise_func(self, args, kwargs)
314 # execute the runner
--> 315 elems = self.elementwise_runner(f, X)
317 # for each evaluation call
318 for elem in elems:
319
320 # for each key stored for this evaluation
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/problem.py:32, in LoopedElementwiseEvaluation.__call__(self, f, X)
31 def __call__(self, f, X):
---> 32 return [f(x) for x in X]
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/problem.py:32, in <listcomp>(.0)
31 def __call__(self, f, X):
---> 32 return [f(x) for x in X]
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pymoo/core/problem.py:25, in ElementwiseEvaluationFunction.__call__(self, x)
23 def __call__(self, x):
24 out = dict()
---> 25 self.problem._evaluate(x, out, *self.args, **self.kwargs)
26 return out
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/optimization.py:433, in ProblemFixedPanel._evaluate(self, x, out, *args, **kwargs)
430 box_copy.load_new_gauge(t_sk_cell, t_sp, t_st, w_st, h_st)
432 # Perform stress analysis
--> 433 box_copy.stress_analysis(
434 self.shear_y,
435 self.shear_x,
436 self.moment_y,
437 self.moment_x,
438 self.applied_loc,
439 self.mat_struct.shear_modulus,
440 )
442 # ================ Get constraints ======================================
443 constr_cls = IsotropicWingboxConstraints(
444 box_copy, self.mat_struct, self.len_sec
445 )
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/tuduam-2024.16-py3.11.egg/tuduam/structures/wingbox.py:1026, in IdealWingbox.stress_analysis(self, shear_y, shear_x, moment_y, moment_x, shear_y_appl, shear_mod, validate)
1022 # Else if just add qs,n. As no other influece needs to be taken into account
1023 else:
1024 # Check if it was not the cut panel
1025 if pnl.q_basic != 0:
-> 1026 sign = np.sign(np.cross(r_rel_vec, pnl.dir_vec))
1027 pnl.q_tot = pnl.q_basic + sign*qs_lst[idx]
1028 pnl.tau = pnl.q_tot/pnl.t_pnl
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/numpy/core/numeric.py:1591, in cross(a, b, axisa, axisb, axisc, axis)
1588 axisb = normalize_axis_index(axisb, b.ndim, msg_prefix='axisb')
1590 # Move working axis to the end of the shape
-> 1591 a = moveaxis(a, axisa, -1)
1592 b = moveaxis(b, axisb, -1)
1593 msg = ("incompatible dimensions for cross product\n"
1594 "(dimension must be 2 or 3)")
File /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/numpy/core/numeric.py:1393, in moveaxis(a, source, destination)
1389 def _moveaxis_dispatcher(a, source, destination):
1390 return (a,)
-> 1393 @array_function_dispatch(_moveaxis_dispatcher)
1394 def moveaxis(a, source, destination):
1395 """
1396 Move axes of an array to new positions.
1397
(...)
1440
1441 """
1442 try:
1443 # allow duck-array types if they define transpose
KeyboardInterrupt:
Viewing the results#
Above you have now seen the final result of each section. The history of each section as we have plotted previously, is also saved in the res_lst
variable. The previous code we utilized is wrapped in a function below for easy access such that you can compare the the various sections. Now feel free to change the variables or use the API for your optimizations!
from pymoo.core.result import Result
def plot_history(res: Result):
pop_lst = [gen.pop for gen in res.history]
t_sk_cell1_lst = [np.average(pop.get("X")[:][0]) for pop in pop_lst]
t_sk_cell2_lst = [np.average(pop.get("X")[:][1]) for pop in pop_lst]
t_sk_cell3_lst = [np.average(pop.get("X")[:][2]) for pop in pop_lst]
t_sk_cell4_lst = [np.average(pop.get("X")[:][3]) for pop in pop_lst]
t_sp_lst = [np.average(pop.get("X")[:][4]) for pop in pop_lst]
t_st_lst = [np.average(pop.get("X")[:][5]) for pop in pop_lst]
w_st_lst = [np.average(pop.get("X")[:][6]) for pop in pop_lst]
h_st__lst = [np.average(pop.get("X")[:][7]) for pop in pop_lst]
f_lst = [np.average(pop.get("F")) for pop in pop_lst]
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18.5, 10.5)
ax1.plot(t_sk_cell1_lst, label="T sk cell 1")
ax1.plot(t_sk_cell2_lst, label="T sk cell 2")
ax1.plot(t_sk_cell3_lst, label="T sk cell 3")
ax1.plot(t_sk_cell4_lst, label="T sk cell 4")
ax1.plot(t_sp_lst, label="T sp")
ax1.plot(t_st_lst, label="T st")
ax1.plot(w_st_lst, label="W st")
ax1.plot(h_st__lst, label="H st")
ax2.plot(f_lst, label="Weight")
ax1.set_title("Average value of variables over time")
ax2.set_title("Average value of the optimization function over time")
ax1.set_xlabel("Generation")
ax2.set_xlabel("Generation")
ax1.set_ylabel("Thickness (mm)")
ax1.legend()
ax2.legend()
ax1.grid()
ax2.grid()
plt.show()
plot_history(res_lst[3])
