diff --git a/openaerostruct/__init__.py b/openaerostruct/__init__.py index 04188a16d..36a511eca 100644 --- a/openaerostruct/__init__.py +++ b/openaerostruct/__init__.py @@ -1 +1 @@ -__version__ = '2.2.0' +__version__ = '2.2.1' diff --git a/openaerostruct/docs/aero_n2.html b/openaerostruct/docs/aero_n2.html index 34b4c2781..89eefe147 100644 --- a/openaerostruct/docs/aero_n2.html +++ b/openaerostruct/docs/aero_n2.html @@ -1,3626 +1,9555 @@ + + + +OpenMDAO Model Hierarchy and N2 diagram + + + + - + + + +.icon-resize-vertical:before { + content: '\e807'; +} -
-
-

OpenMDAO Partition Tree and N2 diagram.

-
-
- - -
-
-
- - - - -
-
- - - - - -
-
- - - - - - -
-
- -
-
- -
-
-
-
- - - -
- - -
-
-
-
-
+.icon-floppy:before { + content: '\e808'; +} - -
- +.icon-minus:before { + content: '\e80a'; +} - - + + + + + + + + + + + + + + + + -$.fire = function(target, type, properties) { - var evt = document.createEvent("HTMLEvents"); + - - - - - - + - - + - - textWidthGroup.remove(); + + + - - - \ No newline at end of file + + + + diff --git a/openaerostruct/docs/aero_walkthrough.rst b/openaerostruct/docs/aero_walkthrough.rst index ae1f9e2de..82c4f8e45 100644 --- a/openaerostruct/docs/aero_walkthrough.rst +++ b/openaerostruct/docs/aero_walkthrough.rst @@ -165,8 +165,8 @@ To create this diagram for any OpenMDAO problem, add these two lines after you c .. code-block:: python - from openmdao.api import view_model - view_model(prob) + from openmdao.api import n2 + n2(prob) Use any web browser to open the `.html` file and you can examine your problem layout. This diagram shows groups in dark blue, components in light blue, as organized by your actual problem hierarchy. diff --git a/openaerostruct/tests/test_aerostruct.py b/openaerostruct/tests/test_aerostruct.py index 3058cb440..751d35d4e 100644 --- a/openaerostruct/tests/test_aerostruct.py +++ b/openaerostruct/tests/test_aerostruct.py @@ -145,7 +145,7 @@ def test(self): prob.run_driver() - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 104393.448214, 1e-8) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 97696.33252514644, 1e-8) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_analysis.py b/openaerostruct/tests/test_aerostruct_analysis.py index 110f3ff40..c0e4a3bb9 100644 --- a/openaerostruct/tests/test_aerostruct_analysis.py +++ b/openaerostruct/tests/test_aerostruct_analysis.py @@ -149,8 +149,8 @@ def test(self): prob.run_model() - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 276492.942161, 1e-4) - assert_rel_error(self, prob['AS_point_0.CM'][1], -0.567558367647, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 266779.31928094657, 1e-4) + assert_rel_error(self, prob['AS_point_0.CM'][1], -0.5921107171641, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_analysis_Sref.py b/openaerostruct/tests/test_aerostruct_analysis_Sref.py index d810f1c7f..19bc6b2cb 100644 --- a/openaerostruct/tests/test_aerostruct_analysis_Sref.py +++ b/openaerostruct/tests/test_aerostruct_analysis_Sref.py @@ -153,8 +153,8 @@ def test(self): prob.run_model() - assert_rel_error(self, prob['AS_point_0.CL'][0], 1.5775046966345903, 1e-6) - assert_rel_error(self, prob['AS_point_0.CM'][1], -1.61358383281, 1e-5) + assert_rel_error(self, prob['AS_point_0.CL'][0], 1.6217443031469607, 1e-6) + assert_rel_error(self, prob['AS_point_0.CM'][1], -1.682700295091543, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_analysis_compressible.py b/openaerostruct/tests/test_aerostruct_analysis_compressible.py index 237f776e9..2ab69dec8 100644 --- a/openaerostruct/tests/test_aerostruct_analysis_compressible.py +++ b/openaerostruct/tests/test_aerostruct_analysis_compressible.py @@ -149,8 +149,8 @@ def test(self): prob.run_model() - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 234725.35871961166, 1e-4) - assert_rel_error(self, prob['AS_point_0.CM'][1], -0.7848616090025305, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 224407.92673401345, 1e-4) + assert_rel_error(self, prob['AS_point_0.CM'][1], -0.8308573193422, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_engine_thrusts.py b/openaerostruct/tests/test_aerostruct_engine_thrusts.py index b9f21bc88..df59068da 100644 --- a/openaerostruct/tests/test_aerostruct_engine_thrusts.py +++ b/openaerostruct/tests/test_aerostruct_engine_thrusts.py @@ -168,8 +168,8 @@ def test(self): print(prob['AS_point_0.fuelburn'][0]) print(prob['AS_point_0.CM'][1]) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 277112.600118, 1e-4) - assert_rel_error(self, prob['AS_point_0.CM'][1], -0.565647440276, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 267385.8819288796, 1e-4) + assert_rel_error(self, prob['AS_point_0.CM'][1], -0.590064260544, 1e-5) diff --git a/openaerostruct/tests/test_aerostruct_ffd.py b/openaerostruct/tests/test_aerostruct_ffd.py index 14fe12cb8..33f3ff0c4 100644 --- a/openaerostruct/tests/test_aerostruct_ffd.py +++ b/openaerostruct/tests/test_aerostruct_ffd.py @@ -203,7 +203,7 @@ def test(self): # filename += '_' + str(surf_dict['mx']) + '_' + str(surf_dict['my']) + '.mesh' # np.save(filename, mesh) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 104675.0989232741, 1e-3) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 97680.8964568375, 1e-3) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_point_loads.py b/openaerostruct/tests/test_aerostruct_point_loads.py index a4563990c..793579a25 100644 --- a/openaerostruct/tests/test_aerostruct_point_loads.py +++ b/openaerostruct/tests/test_aerostruct_point_loads.py @@ -162,8 +162,8 @@ def test(self): prob.run_model() - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 277258.454855, 1e-4) - assert_rel_error(self, prob['AS_point_0.CM'][1], -0.565356878767, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 267518.2837095164, 1e-4) + assert_rel_error(self, prob['AS_point_0.CM'][1], -0.58977996718612, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_wingbox_+weight_analysis.py b/openaerostruct/tests/test_aerostruct_wingbox_+weight_analysis.py index 055e3d10b..bac85400e 100644 --- a/openaerostruct/tests/test_aerostruct_wingbox_+weight_analysis.py +++ b/openaerostruct/tests/test_aerostruct_wingbox_+weight_analysis.py @@ -201,7 +201,7 @@ def test(self): # print(prob['AS_point_0.fuelburn'][0]) # print(prob['wing.structural_mass'][0]/1.25) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 112469.567077, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 89003.902195, 1e-5) assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 24009.5230566, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_wingbox_analysis.py b/openaerostruct/tests/test_aerostruct_wingbox_analysis.py index 9ea3cdbb8..c0111fccd 100644 --- a/openaerostruct/tests/test_aerostruct_wingbox_analysis.py +++ b/openaerostruct/tests/test_aerostruct_wingbox_analysis.py @@ -199,10 +199,10 @@ def test(self): print(prob['wing.structural_mass'][0]/1.25) print(prob['AS_point_0.wing_perf.failure'][0]) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 112527.031936, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 89411.50246542075, 1e-5) assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 24009.5230566, 1e-5) - assert_rel_error(self, prob['AS_point_0.wing_perf.failure'][0], 1.70644139941, 1e-5) + assert_rel_error(self, prob['AS_point_0.wing_perf.failure'][0], 1.6254327137382174, 1e-5) if __name__ == '__main__': - unittest.main() + unittest.main() \ No newline at end of file diff --git a/openaerostruct/tests/test_aerostruct_wingbox_fuel_vol_constraint_opt.py b/openaerostruct/tests/test_aerostruct_wingbox_fuel_vol_constraint_opt.py index bdc673551..bb10d2933 100644 --- a/openaerostruct/tests/test_aerostruct_wingbox_fuel_vol_constraint_opt.py +++ b/openaerostruct/tests/test_aerostruct_wingbox_fuel_vol_constraint_opt.py @@ -225,12 +225,13 @@ def test(self): # prob.check_partials(form='central', compact_print=True) - # print(prob['AS_point_0.fuelburn'][0]) - # print(prob['wing.structural_mass'][0]/1.25) + print(prob['AS_point_0.fuelburn'][0]) + print(prob['wing.structural_mass'][0]/1.25) + print(prob['fuel_vol_delta.fuel_vol_delta'][0]) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 83619.581901, 1e-5) - assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 13768.9206457, 1e-5) - assert_rel_error(self, prob['fuel_vol_delta.fuel_vol_delta'][0], 39.6491222105, 1e-4) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 82019.934119, 1e-5) + assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 12211.382514, 1e-4) + assert_rel_error(self, prob['fuel_vol_delta.fuel_vol_delta'][0], 35.3020703438527, 1e-4) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_aerostruct_wingbox_opt.py b/openaerostruct/tests/test_aerostruct_wingbox_opt.py index 2a46bde0f..58746031e 100644 --- a/openaerostruct/tests/test_aerostruct_wingbox_opt.py +++ b/openaerostruct/tests/test_aerostruct_wingbox_opt.py @@ -215,8 +215,8 @@ def test(self): # print(prob['AS_point_0.fuelburn'][0]) # print(prob['wing.structural_mass'][0]/1.25) # print(prob['wing.geometry.span']) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 87618.3090845, 1e-5) - assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 17221.5568854, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 84387.962001, 1e-5) + assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 13974.684240, 1e-5) assert_rel_error(self, prob['wing.geometry.span'][0], 60., 1e-5) diff --git a/openaerostruct/tests/test_aerostruct_wingbox_wave_fuel_vol_constraint_opt.py b/openaerostruct/tests/test_aerostruct_wingbox_wave_fuel_vol_constraint_opt.py index 296871cc7..ee62ae30f 100644 --- a/openaerostruct/tests/test_aerostruct_wingbox_wave_fuel_vol_constraint_opt.py +++ b/openaerostruct/tests/test_aerostruct_wingbox_wave_fuel_vol_constraint_opt.py @@ -229,8 +229,8 @@ def test(self): # print(prob['AS_point_0.fuelburn'][0]) # print(prob['wing.structural_mass'][0]/1.25) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 85029.7479699, 1e-5) - assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 18927.5387802, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 83383.555232, 1e-5) + assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 16219.119311, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_multipoint_wingbox_aerostruct.py b/openaerostruct/tests/test_multipoint_wingbox_aerostruct.py index 2ef09f12c..731c4214a 100644 --- a/openaerostruct/tests/test_multipoint_wingbox_aerostruct.py +++ b/openaerostruct/tests/test_multipoint_wingbox_aerostruct.py @@ -258,11 +258,11 @@ def test(self): # prob.check_partials(form='central', compact_print=True) - # print(prob['AS_point_0.fuelburn'][0]) - # print(prob['wing.structural_mass'][0]/1.25) + print(prob['AS_point_0.fuelburn'][0]) + print(prob['wing.structural_mass'][0]/1.25) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 94552.8697703, 1e-5) - assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 28295.9737159, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 93177.833744, 1e-5) + assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 26640.125856, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_simple_rect_AS.py b/openaerostruct/tests/test_simple_rect_AS.py index c1a0c054e..440bfe8b2 100644 --- a/openaerostruct/tests/test_simple_rect_AS.py +++ b/openaerostruct/tests/test_simple_rect_AS.py @@ -163,7 +163,7 @@ def test(self): prob.run_driver() - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 70754.19144483653, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 68345.6633812, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_v1_aerostruct_analysis.py b/openaerostruct/tests/test_v1_aerostruct_analysis.py index ebaa158f2..49fb61ea4 100644 --- a/openaerostruct/tests/test_v1_aerostruct_analysis.py +++ b/openaerostruct/tests/test_v1_aerostruct_analysis.py @@ -153,10 +153,10 @@ def test(self): prob.run_model() - assert_rel_error(self, prob['AS_point_0.wing_perf.CL'][0], 0.501212803372, 1e-6) - assert_rel_error(self, prob['AS_point_0.wing_perf.failure'][0], -0.434049851068, 1e-6) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 70365.875285, 1e-4) - assert_rel_error(self, prob['AS_point_0.CM'][1], -1.27266001707, 1e-5) + assert_rel_error(self, prob['AS_point_0.wing_perf.CL'][0], 0.510849206378, 1e-6) + assert_rel_error(self, prob['AS_point_0.wing_perf.failure'][0], -0.483587598753, 1e-6) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 68894.2100988, 1e-4) + assert_rel_error(self, prob['AS_point_0.CM'][1], -1.301925362641, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_v1_aerostruct_opt.py b/openaerostruct/tests/test_v1_aerostruct_opt.py index a6d01a73c..fa2ccfec0 100644 --- a/openaerostruct/tests/test_v1_aerostruct_opt.py +++ b/openaerostruct/tests/test_v1_aerostruct_opt.py @@ -166,10 +166,10 @@ def test(self): prob.run_driver() - assert_rel_error(self, prob['AS_point_0.wing_perf.CL'][0], 0.469128339791, 1e-6) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 95393.7772462, 1.5e-6) + assert_rel_error(self, prob['AS_point_0.wing_perf.CL'][0], 0.443870671238, 1e-6) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 92145.07464246, 1.5e-6) assert_rel_error(self, prob['AS_point_0.wing_perf.failure'][0], 0., 1e-6) - assert_rel_error(self, prob['AS_point_0.CM'][1], -1.3154462936779994, 1e-4) + assert_rel_error(self, prob['AS_point_0.CM'][1], -1.3438534437120, 1e-4) if __name__ == '__main__': diff --git a/openaerostruct/tests/test_wingbox_distributed_fuel.py b/openaerostruct/tests/test_wingbox_distributed_fuel.py index c7f8f7cd2..fcdb062f5 100644 --- a/openaerostruct/tests/test_wingbox_distributed_fuel.py +++ b/openaerostruct/tests/test_wingbox_distributed_fuel.py @@ -242,8 +242,8 @@ def test(self): print(prob['AS_point_0.fuelburn'][0]) print(prob['wing.structural_mass'][0]/1.25) - assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 82444.7895704, 1e-5) - assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 15174.9970923, 1e-5) + assert_rel_error(self, prob['AS_point_0.fuelburn'][0], 80758.28839215, 1e-5) + assert_rel_error(self, prob['wing.structural_mass'][0]/1.25, 12330.193521430, 1e-5) if __name__ == '__main__': diff --git a/openaerostruct/transfer/load_transfer.py b/openaerostruct/transfer/load_transfer.py index 3a6da589c..c52fe930d 100644 --- a/openaerostruct/transfer/load_transfer.py +++ b/openaerostruct/transfer/load_transfer.py @@ -15,21 +15,16 @@ class LoadTransfer(om.ExplicitComponent): Parameters ---------- def_mesh[nx, ny, 3] : numpy array - Flattened array defining the lifting surfaces after deformation. + Array defining the lifting surfaces after deformation. + Arrays will be flattened in Fortran order (only relevant when more than one chordwise panel). sec_forces[nx-1, ny-1, 3] : numpy array - Flattened array containing the sectional forces acting on each panel. - Stored in Fortran order (only relevant when more than one chordwise - panel). + Array containing the sectional forces acting on each panel. Returns ------- - LoadsA[ny,3] : numpy array = loads[:,:3] - LoadsB[ny,3]: nympy array = loads[:,3:] - Flattened array containing the loads applied on the FEM component, - computed from the sectional forces. - - Loads[ny, 6] : numpy array = [LoadsA,LoadsB] - + loads[ny, 6] : numpy array + Array containing the loads applied on the FEM component at each node, + computed from the sectional forces. The first 3 columns are N, and the last 3 are N*m. """ def initialize(self): @@ -41,7 +36,6 @@ def setup(self): self.nx = nx = surface['mesh'].shape[0] self.ny = ny = surface['mesh'].shape[1] - if surface['fem_model_type'] == 'tube': self.fem_origin = surface['fem_origin'] else: @@ -49,9 +43,9 @@ def setup(self): x_upper = surface['data_x_upper'] y_lower = surface['data_y_lower'] - fem_origin = (x_upper[0] * (y_upper[0] - y_lower[0]) + - x_upper[-1] * (y_upper[-1] - y_lower[-1])) / \ - ((y_upper[0] - y_lower[0]) + (y_upper[-1] - y_lower[-1])) + fem_origin = (x_upper[0] * (y_upper[0] - y_lower[0]) + x_upper[-1] * (y_upper[-1] - y_lower[-1])) / ( + (y_upper[0] - y_lower[0]) + (y_upper[-1] - y_lower[-1]) + ) # For some reason, surface data is complex in some tests. self.fem_origin = np.float(fem_origin) @@ -60,9 +54,9 @@ def setup(self): self.w2 = self.fem_origin self.add_input('def_mesh', val=np.zeros((nx, ny, 3)), units='m') - self.add_input('sec_forces', val=np.zeros((nx-1, ny-1, 3)), units='N') + self.add_input('sec_forces', val=np.zeros((nx - 1, ny - 1, 3)), units='N') - self.add_output('loads', val=np.zeros((self.ny, 6)), units='N') ## WARNING!!! UNITS ARE A MIXTURE OF N & N*m + self.add_output('loads', val=np.zeros((self.ny, 6)), units='N') ## WARNING!!! UNITS ARE A MIXTURE OF N & N*m # Well, technically the units of this load array are mixed. # The first 3 indices are N and the last 3 are N*m. @@ -71,19 +65,19 @@ def setup(self): # First, the direct loads wrt sec_forces terms. base_row = np.array([0, 1, 2, 6, 7, 8]) base_col = np.array([0, 1, 2, 0, 1, 2]) - row = np.tile(base_row, ny-1) + np.repeat(6*np.arange(ny-1), 6) - col = np.tile(base_col, ny-1) + np.repeat(3*np.arange(ny-1), 6) - rows1 = np.tile(row, nx-1) - cols1 = np.tile(col, nx-1) + np.repeat(3*(ny-1)*np.arange(nx-1), 6*(ny-1)) + row = np.tile(base_row, ny - 1) + np.repeat(6 * np.arange(ny - 1), 6) + col = np.tile(base_col, ny - 1) + np.repeat(3 * np.arange(ny - 1), 6) + rows1 = np.tile(row, nx - 1) + cols1 = np.tile(col, nx - 1) + np.repeat(3 * (ny - 1) * np.arange(nx - 1), 6 * (ny - 1)) # Then, the term from the cross product. base_row = np.array([3, 3, 4, 4, 5, 5]) base_col = np.array([1, 2, 0, 2, 0, 1]) - row = np.tile(base_row, ny-1) + np.repeat(6*np.arange(ny-1), 6) - col = np.tile(base_col, ny-1) + np.repeat(3*np.arange(ny-1), 6) - row1 = np.tile(row, nx-1) - col1 = np.tile(col, nx-1) + np.repeat(3*(ny-1)*np.arange(nx-1), 6*(ny-1)) - rows2 = np.tile(row1, 2) + np.repeat(np.array([0, 6]), 6*(nx-1)*(ny-1)) + row = np.tile(base_row, ny - 1) + np.repeat(6 * np.arange(ny - 1), 6) + col = np.tile(base_col, ny - 1) + np.repeat(3 * np.arange(ny - 1), 6) + row1 = np.tile(row, nx - 1) + col1 = np.tile(col, nx - 1) + np.repeat(3 * (ny - 1) * np.arange(nx - 1), 6 * (ny - 1)) + rows2 = np.tile(row1, 2) + np.repeat(np.array([0, 6]), 6 * (nx - 1) * (ny - 1)) cols2 = np.tile(col1, 2) rows = np.concatenate([rows1, rows2]) @@ -94,27 +88,26 @@ def setup(self): # Top diagonal is forward-most mesh point. base_row = np.array([3, 3, 4, 4, 5, 5]) base_col = np.array([4, 5, 3, 5, 3, 4]) - row = np.tile(base_row, ny-1) + np.repeat(6*np.arange(ny-1), 6) - col = np.tile(base_col, ny-1) + np.repeat(3*np.arange(ny-1), 6) + row = np.tile(base_row, ny - 1) + np.repeat(6 * np.arange(ny - 1), 6) + col = np.tile(base_col, ny - 1) + np.repeat(3 * np.arange(ny - 1), 6) rows1 = np.tile(row, nx) - cols1 = np.tile(col, nx) + np.repeat(3*ny*np.arange(nx), 6*(ny-1)) + cols1 = np.tile(col, nx) + np.repeat(3 * ny * np.arange(nx), 6 * (ny - 1)) # Bottom diagonal is backward-most mesh point. base_row = np.array([9, 9, 10, 10, 11, 11]) base_col = np.array([1, 2, 0, 2, 0, 1]) - row = np.tile(base_row, ny-1) + np.repeat(6*np.arange(ny-1), 6) - col = np.tile(base_col, ny-1) + np.repeat(3*np.arange(ny-1), 6) + row = np.tile(base_row, ny - 1) + np.repeat(6 * np.arange(ny - 1), 6) + col = np.tile(base_col, ny - 1) + np.repeat(3 * np.arange(ny - 1), 6) rows2 = np.tile(row, nx) - cols2 = np.tile(col, nx) + np.repeat(3*ny*np.arange(nx), 6*(ny-1)) + cols2 = np.tile(col, nx) + np.repeat(3 * ny * np.arange(nx), 6 * (ny - 1)) # Central Diagonal blocks base_row = np.array([3, 3, 4, 4, 5, 5]) base_col = np.array([1, 2, 0, 2, 0, 1]) - row = np.tile(base_row, ny) + np.repeat(6*np.arange(ny), 6) - col = np.tile(base_col, ny) + np.repeat(3*np.arange(ny), 6) + row = np.tile(base_row, ny) + np.repeat(6 * np.arange(ny), 6) + col = np.tile(base_col, ny) + np.repeat(3 * np.arange(ny), 6) rows3 = np.tile(row, nx) - cols3 = np.tile(col, nx) + np.repeat(3*ny*np.arange(nx), 6*ny) - + cols3 = np.tile(col, nx) + np.repeat(3 * ny * np.arange(nx), 6 * ny) rows = np.concatenate([rows1, rows2, rows3]) cols = np.concatenate([cols1, cols2, cols3]) @@ -125,38 +118,40 @@ def setup(self): self.set_check_partial_options('*', method='cs', step=1e-40) def compute(self, inputs, outputs): - mesh = inputs['def_mesh'] #[nx, ny, 3] + mesh = inputs['def_mesh'] # [nx, ny, 3] sec_forces = inputs['sec_forces'] - # Compute the aerodynamic centers at the quarter-chord point of each panel - # a_pts [nx-1, ny-1, 3] - a_pts = 0.5 * (1-self.w1) * mesh[:-1, :-1, :] + \ - 0.5 * self.w1 * mesh[1:, :-1, :] + \ - 0.5 * (1-self.w1) * mesh[:-1, 1:, :] + \ - 0.5 * self.w1 * mesh[1:, 1:, :] - - # Compute the structural midpoints based on the fem_origin location - # s_pts [ny-1, 3] - s_pts = 0.5 * (1-self.w2) * mesh[0, :-1, :] + \ - 0.5 * self.w2 * mesh[-1, :-1, :] + \ - 0.5 * (1-self.w2) * mesh[0, 1:, :] + \ - 0.5 * self.w2 * mesh[-1, 1:, :] - - # Find the moment arm between the aerodynamic centers of each panel - # and the FEM elements - # diff [nx-1, ny-1, 3] - moment = 0.5 * np.sum(np.cross(a_pts - s_pts, sec_forces), axis=0) - + # ----- 1. Forces transfer ----- # Only need to zero out the part that is assigned via += - outputs['loads'][-1, :] = 0. + outputs['loads'][-1, :] = 0.0 - # Compute the loads based on the xyz forces and the computed moments + # The aero force acting on each panel is evenly transferred to the adjacent FEM nodes. sec_forces_sum = 0.5 * np.sum(sec_forces, axis=0) outputs['loads'][:-1, :3] = sec_forces_sum outputs['loads'][1:, :3] += sec_forces_sum - outputs['loads'][:-1, 3:] = moment - outputs['loads'][1:, 3:] += moment + # ----- 2. Moments transfer ----- + # Compute the aerodynamic centers at the quarter-chord point of each panel + # a_pts [nx-1, ny-1, 3] + a_pts = ( + 0.5 * (1 - self.w1) * mesh[:-1, :-1, :] + + 0.5 * self.w1 * mesh[1:, :-1, :] + + 0.5 * (1 - self.w1) * mesh[:-1, 1:, :] + + 0.5 * self.w1 * mesh[1:, 1:, :] + ) + + # Compute the structural nodes based on the fem_origin location (weighted sum of the LE and TE mesh vertices) + # s_pts [ny, 3] + s_pts = (1 - self.w2) * mesh[0, :, :] + self.w2 * mesh[-1, :, :] + + # The moment arm is between the aerodynamic centers of each panel and the FEM nodes. + # Moment contribution of sec_forces (acting on aero center) to the inner/outer adjacent node + moment_in = np.sum(np.cross(a_pts - s_pts[:-1, :], 0.5 * sec_forces), axis=0) # [ny-1, 3] + moment_out = np.sum(np.cross(a_pts - s_pts[1:, :], 0.5 * sec_forces), axis=0) + + # Total moment at each node = sum of moment_in and moment_out, except the edge nodes.s + outputs['loads'][:-1, 3:] = moment_in + outputs['loads'][1:, 3:] += moment_out def compute_partials(self, inputs, partials): mesh = inputs['def_mesh'] @@ -167,89 +162,104 @@ def compute_partials(self, inputs, partials): w2 = self.w2 # Compute the aerodynamic centers at the quarter-chord point of each panel - a_pts = 0.5 * (1-w1) * mesh[:-1, :-1, :] + \ - 0.5 * w1 * mesh[1:, :-1, :] + \ - 0.5 * (1-w1) * mesh[:-1, 1:, :] + \ - 0.5 * w1 * mesh[1:, 1:, :] - - # Compute the structural midpoints based on the fem_origin location - s_pts = 0.5 * (1-w2) * mesh[0, :-1, :] + \ - 0.5 * w2 * mesh[-1, :-1, :] + \ - 0.5 * (1-w2) * mesh[0, 1:, :] + \ - 0.5 * w2 * mesh[-1, 1:, :] - - diff = 0.5 * (a_pts - s_pts) - - # dmoment__dsec_forces - - dmom_dsec = np.empty((nx-1, ny-1, 6)) - dmom_dsec[:, :, 0] = -diff[:, :, 2] - dmom_dsec[:, :, 1] = diff[:, :, 1] - dmom_dsec[:, :, 2] = diff[:, :, 2] - dmom_dsec[:, :, 3] = -diff[:, :, 0] - dmom_dsec[:, :, 4] = -diff[:, :, 1] - dmom_dsec[:, :, 5] = diff[:, :, 0] - - id1 = 6*(ny-1)*(nx-1) + a_pts = ( + 0.5 * (1 - w1) * mesh[:-1, :-1, :] + + 0.5 * w1 * mesh[1:, :-1, :] + + 0.5 * (1 - w1) * mesh[:-1, 1:, :] + + 0.5 * w1 * mesh[1:, 1:, :] + ) + + # Compute the structural nodes + s_pts = (1 - self.w2) * mesh[0, :, :] + self.w2 * mesh[-1, :, :] + + # ----- 1. dmoment__dsec_forces ----- + # Sensitivity of loads (moments) at inner node wrt sec_force + diff_in = 0.5 * (a_pts - s_pts[:-1, :]) # moment arm from inner node to aero center. + + dmom_dsec_in = np.empty((nx - 1, ny - 1, 6)) + dmom_dsec_in[:, :, 0] = -diff_in[:, :, 2] + dmom_dsec_in[:, :, 1] = diff_in[:, :, 1] + dmom_dsec_in[:, :, 2] = diff_in[:, :, 2] + dmom_dsec_in[:, :, 3] = -diff_in[:, :, 0] + dmom_dsec_in[:, :, 4] = -diff_in[:, :, 1] + dmom_dsec_in[:, :, 5] = diff_in[:, :, 0] + + # Repeat for moments at outer node wrt sec_force + diff_out = 0.5 * (a_pts - s_pts[1:, :]) # moment arm from outer node to aero center. + dmom_dsec_out = np.empty((nx - 1, ny - 1, 6)) + dmom_dsec_out[:, :, 0] = -diff_out[:, :, 2] + dmom_dsec_out[:, :, 1] = diff_out[:, :, 1] + dmom_dsec_out[:, :, 2] = diff_out[:, :, 2] + dmom_dsec_out[:, :, 3] = -diff_out[:, :, 0] + dmom_dsec_out[:, :, 4] = -diff_out[:, :, 1] + dmom_dsec_out[:, :, 5] = diff_out[:, :, 0] + + id1 = 6 * (ny - 1) * (nx - 1) partials['loads', 'sec_forces'][:id1] = 0.5 id2 = id1 * 2 - dmom_dsec = dmom_dsec.flatten() - partials['loads', 'sec_forces'][id1:id2] = dmom_dsec - partials['loads', 'sec_forces'][id2:] = dmom_dsec - - # dmoment__dmesh - - dmom_ddiff = np.zeros((nx-1, ny-1, 6)) - dmom_ddiff[:, :, 0] = sec_forces[:, :, 2] - dmom_ddiff[:, :, 1] = -sec_forces[:, :, 1] - dmom_ddiff[:, :, 2] = -sec_forces[:, :, 2] - dmom_ddiff[:, :, 3] = sec_forces[:, :, 0] - dmom_ddiff[:, :, 4] = sec_forces[:, :, 1] - dmom_ddiff[:, :, 5] = -sec_forces[:, :, 0] - - dmom_ddiff_sum = np.sum(dmom_ddiff, axis=0) - - dmon_ddiff_diag = np.zeros((nx-1, ny, 6)) - dmon_ddiff_diag[:, 1:, :] = dmom_ddiff - dmon_ddiff_diag[:, :-1, :] += dmom_ddiff + dmom_dsec_in = dmom_dsec_in.flatten() + dmom_dsec_out = dmom_dsec_out.flatten() + partials['loads', 'sec_forces'][id1:id2] = dmom_dsec_in + partials['loads', 'sec_forces'][id2:] = dmom_dsec_out + + # ----- 2. dmoment__dmesh ----- + # Sensitivity of moments at inner nodes wrt diff_in (upper diagonal) + dmom_ddiff_in = np.zeros((nx - 1, ny - 1, 6)) + dmom_ddiff_in[:, :, 0] = sec_forces[:, :, 2] + dmom_ddiff_in[:, :, 1] = -sec_forces[:, :, 1] + dmom_ddiff_in[:, :, 2] = -sec_forces[:, :, 2] + dmom_ddiff_in[:, :, 3] = sec_forces[:, :, 0] + dmom_ddiff_in[:, :, 4] = sec_forces[:, :, 1] + dmom_ddiff_in[:, :, 5] = -sec_forces[:, :, 0] + dmom_ddiff_in_sum = np.sum(dmom_ddiff_in, axis=0) + + # Sensitivity of moments at outer nodes wrt diff_out (lower diagonal) + dmom_ddiff_out = np.zeros((nx - 1, ny - 1, 6)) + dmom_ddiff_out[:, :, 0] = sec_forces[:, :, 2] + dmom_ddiff_out[:, :, 1] = -sec_forces[:, :, 1] + dmom_ddiff_out[:, :, 2] = -sec_forces[:, :, 2] + dmom_ddiff_out[:, :, 3] = sec_forces[:, :, 0] + dmom_ddiff_out[:, :, 4] = sec_forces[:, :, 1] + dmom_ddiff_out[:, :, 5] = -sec_forces[:, :, 0] + dmom_ddiff_out_sum = np.sum(dmom_ddiff_out, axis=0) + + dmon_ddiff_diag = np.zeros((nx - 1, ny, 6)) + dmon_ddiff_diag[:, 1:, :] = dmom_ddiff_out + dmon_ddiff_diag[:, :-1, :] += dmom_ddiff_in dmon_ddiff_diag_sum = np.zeros((1, ny, 6)) - dmon_ddiff_diag_sum[:, :-1, :] = dmom_ddiff_sum - dmon_ddiff_diag_sum[:, 1:, :] += dmom_ddiff_sum + dmon_ddiff_diag_sum[:, :-1, :] = dmom_ddiff_in_sum + dmon_ddiff_diag_sum[:, 1:, :] += dmom_ddiff_out_sum - dmom_ddiff = dmom_ddiff.flatten() - dmom_ddiff_sum = dmom_ddiff_sum.flatten() + dmom_ddiff_in = dmom_ddiff_in.flatten() + dmom_ddiff_out = dmom_ddiff_out.flatten() dmon_ddiff_diag = dmon_ddiff_diag.flatten() dmon_ddiff_diag_sum = dmon_ddiff_diag_sum.flatten() - idy = 6*(ny-1) - idx = idy*nx - idw = idy*(nx-1) + idy = 6 * (ny - 1) + idx = idy * nx + idw = idy * (nx - 1) # Need to zero out what's there because our assignments overlap. - partials['loads','def_mesh'][:] = 0.0 + partials['loads', 'def_mesh'][:] = 0.0 # Upper diagonal blocks - partials['loads','def_mesh'][:idw] = dmom_ddiff * ((1-w1) * 0.25) - partials['loads','def_mesh'][idy:idx] += dmom_ddiff * (w1 * 0.25) - partials['loads','def_mesh'][:idy] -= dmom_ddiff_sum * ((1-w2) * 0.25) - partials['loads','def_mesh'][idx-idy:idx] -= dmom_ddiff_sum * (w2 * 0.25) + partials['loads', 'def_mesh'][:idw] = dmom_ddiff_in * ((1 - w1) * 0.25) + partials['loads', 'def_mesh'][idy:idx] += dmom_ddiff_in * (w1 * 0.25) # Lower Diagonal blocks id2 = idx * 2 - partials['loads','def_mesh'][idx:idx+idw] = dmom_ddiff * ((1-w1) * 0.25) - partials['loads','def_mesh'][idx+idy:id2] += dmom_ddiff * (w1 * 0.25) - partials['loads','def_mesh'][idx:idx+idy] -= dmom_ddiff_sum * ((1-w2) * 0.25) - partials['loads','def_mesh'][id2-idy:id2] -= dmom_ddiff_sum * (w2 * 0.25) + partials['loads', 'def_mesh'][idx : idx + idw] = dmom_ddiff_out * ((1 - w1) * 0.25) + partials['loads', 'def_mesh'][idx + idy : id2] += dmom_ddiff_out * (w1 * 0.25) # Central Diagonal blocks - idy = 6*ny - idz = 6*(nx-1) + idy = 6 * ny + idz = 6 * (nx - 1) id3 = id2 + idw + idz - partials['loads','def_mesh'][id2:id3] = dmon_ddiff_diag * ((1-w1) * 0.25) - partials['loads','def_mesh'][id2:id2+idy] -= dmon_ddiff_diag_sum * ((1-w2) * 0.25) + partials['loads', 'def_mesh'][id2:id3] = dmon_ddiff_diag * ((1 - w1) * 0.25) + partials['loads', 'def_mesh'][id2 : id2 + idy] -= dmon_ddiff_diag_sum * ((1 - w2) * 0.5) id2 += idy id3 += idy - partials['loads','def_mesh'][id2:id3] += dmon_ddiff_diag * (w1 * 0.25) - partials['loads','def_mesh'][id3-idy:id3] -= dmon_ddiff_diag_sum * (w2 * 0.25) + partials['loads', 'def_mesh'][id2:id3] += dmon_ddiff_diag * (w1 * 0.25) + partials['loads', 'def_mesh'][id3 - idy : id3] -= dmon_ddiff_diag_sum * (w2 * 0.5)