From 6b2210539bbf1fb2b610f8fa6e2296dd5e816d2b Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Thu, 20 Aug 2026 15:44:10 -0400 Subject: [PATCH 1/5] convert Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL from option to variable --- .../flops_based/test/test_wing_detailed.py | 51 ++++++++++--------- .../mass/flops_based/wing_detailed.py | 38 +++++++++++--- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py index 72e9b23d11..68eb819db5 100644 --- a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py @@ -53,9 +53,6 @@ def test_case(self, case_name): Aircraft.Wing.INPUT_STATION_DISTRIBUTION: inputs.get_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION ), - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL: inputs.get_val( - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL - ), Aircraft.Wing.NUM_INTEGRATION_STATIONS: inputs.get_val( Aircraft.Wing.NUM_INTEGRATION_STATIONS ), @@ -86,6 +83,7 @@ def test_case(self, case_name): Aircraft.Engine.WING_LOCATIONS, Aircraft.Wing.THICKNESS_TO_CHORD, Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, ], output_keys=[ Aircraft.Wing.BENDING_MATERIAL_FACTOR, @@ -125,9 +123,6 @@ def test_case_multiengine(self): Aircraft.Wing.INPUT_STATION_DISTRIBUTION: aviary_options.get_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION ), - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL: aviary_options.get_val( - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL - ), Aircraft.Wing.NUM_INTEGRATION_STATIONS: aviary_options.get_val( Aircraft.Wing.NUM_INTEGRATION_STATIONS ), @@ -150,6 +145,7 @@ def test_case_multiengine(self): Aircraft.Wing.AEROELASTIC_TAILORING_FACTOR, Aircraft.Wing.THICKNESS_TO_CHORD, Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, ] for key in input_keys: @@ -213,9 +209,6 @@ def test_case_fuselage_engines(self): Aircraft.Wing.INPUT_STATION_DISTRIBUTION: aviary_options.get_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION ), - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL: aviary_options.get_val( - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL - ), Aircraft.Wing.NUM_INTEGRATION_STATIONS: aviary_options.get_val( Aircraft.Wing.NUM_INTEGRATION_STATIONS ), @@ -240,6 +233,7 @@ def test_case_fuselage_engines(self): Aircraft.Wing.AEROELASTIC_TAILORING_FACTOR, Aircraft.Wing.THICKNESS_TO_CHORD, Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, ] for key in input_keys: @@ -296,9 +290,6 @@ def test_case_fuselage_multiengine(self): Aircraft.Wing.INPUT_STATION_DISTRIBUTION: aviary_options.get_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION ), - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL: aviary_options.get_val( - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL - ), Aircraft.Wing.NUM_INTEGRATION_STATIONS: aviary_options.get_val( Aircraft.Wing.NUM_INTEGRATION_STATIONS ), @@ -323,6 +314,7 @@ def test_case_fuselage_multiengine(self): Aircraft.Wing.AEROELASTIC_TAILORING_FACTOR, Aircraft.Wing.THICKNESS_TO_CHORD, Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, ] for key in input_keys: @@ -374,9 +366,6 @@ def test_extreme_engine_loc(self): Aircraft.Wing.INPUT_STATION_DISTRIBUTION: aviary_options.get_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION ), - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL: aviary_options.get_val( - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL - ), Aircraft.Wing.NUM_INTEGRATION_STATIONS: aviary_options.get_val( Aircraft.Wing.NUM_INTEGRATION_STATIONS ), @@ -401,6 +390,7 @@ def test_extreme_engine_loc(self): Aircraft.Wing.AEROELASTIC_TAILORING_FACTOR, Aircraft.Wing.THICKNESS_TO_CHORD, Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, ] for key in input_keys: @@ -435,7 +425,6 @@ def test_intensity_factor(self): options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, val=[0.0, 0.5, 1.0], units='unitless' ) - options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=3, units='unitless') options.set_val(Aircraft.Wing.NUM_INTEGRATION_STATIONS, val=46, units='unitless') prob = self.prob @@ -470,6 +459,9 @@ def test_intensity_factor(self): prob.model.set_input_defaults( Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, val=0.01, units='unitless' ) + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=3, units='unitless' + ) setup_model_options(prob, options) prob.setup(check=False, force_alloc_complex=True) @@ -485,7 +477,9 @@ def test_intensity_factor(self): pod_inertia_factor = prob.get_val(Aircraft.Wing.ENG_POD_INERTIA_FACTOR) assert_near_equal(pod_inertia_factor, 0.9772998541, tolerance=1e-9) - options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=1, units='unitless') + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=1, units='unitless' + ) setup_model_options(prob, options) prob.setup(check=False, force_alloc_complex=True) prob.run_model() @@ -496,7 +490,9 @@ def test_intensity_factor(self): pod_inertia_factor = prob.get_val(Aircraft.Wing.ENG_POD_INERTIA_FACTOR) assert_near_equal(pod_inertia_factor, 1.0, tolerance=1e-9) - options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=1.5, units='unitless') + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=1.5, units='unitless' + ) setup_model_options(prob, options) prob.setup(check=False, force_alloc_complex=True) prob.run_model() @@ -507,7 +503,9 @@ def test_intensity_factor(self): pod_inertia_factor = prob.get_val(Aircraft.Wing.ENG_POD_INERTIA_FACTOR) assert_near_equal(pod_inertia_factor, 1.0, tolerance=1e-9) - options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=2.5, units='unitless') + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=2.5, units='unitless' + ) setup_model_options(prob, options) prob.setup(check=False, force_alloc_complex=True) prob.run_model() @@ -542,7 +540,6 @@ def test_case1(self): aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, [0.0, 0.5, 1.0], units='unitless' ) - aviary_options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless') aviary_options.set_val(Aircraft.Wing.NUM_INTEGRATION_STATIONS, 50, units='unitless') aviary_options.set_val(Aircraft.BWB.DETAILED_WING_PROVIDED, False, units='unitless') @@ -564,6 +561,9 @@ def test_case1(self): prob.model.set_input_defaults( Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, 0.11, units='unitless' ) + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless' + ) setup_model_options(self.prob, aviary_options) prob.setup(check=False, force_alloc_complex=True) @@ -614,7 +614,6 @@ def test_case1(self): [0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], units='unitless', ) - aviary_options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless') aviary_options.set_val(Aircraft.Wing.NUM_INTEGRATION_STATIONS, 100, units='unitless') aviary_options.set_val(Aircraft.BWB.DETAILED_WING_PROVIDED, True, units='unitless') @@ -636,6 +635,9 @@ def test_case1(self): prob.model.set_input_defaults( Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, 0.11, units='unitless' ) + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless' + ) setup_model_options(self.prob, aviary_options) prob.setup(check=False, force_alloc_complex=True) @@ -734,7 +736,6 @@ def test_case2(self): [0.0, 0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], units='unitless', ) - aviary_options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless') aviary_options.set_val(Aircraft.Wing.NUM_INTEGRATION_STATIONS, 10, units='unitless') aviary_options.set_val(Aircraft.BWB.DETAILED_WING_PROVIDED, True, units='unitless') @@ -755,6 +756,9 @@ def test_case2(self): prob.model.set_input_defaults( Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE, 0.11, units='unitless' ) + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless' + ) setup_model_options(self.prob, aviary_options) prob.setup(check=False, force_alloc_complex=True) @@ -797,6 +801,3 @@ def test_case2(self): if __name__ == '__main__': unittest.main() - # test = DetailedWingBendingTest() - # test.setUp() - # test.test_intensity_factor() diff --git a/aviary/subsystems/mass/flops_based/wing_detailed.py b/aviary/subsystems/mass/flops_based/wing_detailed.py index fb6c565bfd..4e9072e375 100644 --- a/aviary/subsystems/mass/flops_based/wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/wing_detailed.py @@ -46,6 +46,32 @@ def load_intensity_by_factor(load_dist_factor, intn_stations): return load_intensity +def smooth_load_intensity_by_factor(load_dist_factor, intn_stations): + """ + Calculate load intensity at wing integration stations for the given load_dist_factor. + + Parameters: + load_dist_factor (float): 0 or 1 <= load_dist_factor <= 3. + 1.0 : triangular distribution + 2.0 : elliptical distribution (default) + 3.0 : rectangular distribution + 1.0-2.0 : blend of triangular and elliptical by quartic polynomial + 2.0-3.0 : blend of elliptical and rectangular by quartic polynomial + intn_stations (array): integration stations + + Note: In FLOPS, load_dist_factor can be 0 in which case load intensities are computed + based on input pressure distribution. + """ + a = 1.0 - intn_stations + b = np.sqrt(1.0 - intn_stations**2) + c = np.ones(len(intn_stations)) + t = load_dist_factor - 2.0 + load_intensity = ( + b + (c - a) / 2 * t + 7 * (a - 2 * b + c) / 6 * t**2 + 2 * (-a + 2 * b - c) / 3 * t**4 + ) + return load_intensity + + class DetailedWingBendingFact(om.ExplicitComponent): """ Computation of wing bending factor and engine inertia relief factor @@ -62,7 +88,6 @@ def initialize(self): add_aviary_option(self, Aircraft.Engine.NUM_WING_ENGINES) add_aviary_option(self, Aircraft.Propulsion.TOTAL_NUM_WING_ENGINES) add_aviary_option(self, Aircraft.Wing.INPUT_STATION_DISTRIBUTION) - add_aviary_option(self, Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL) add_aviary_option(self, Aircraft.Wing.NUM_INTEGRATION_STATIONS) def setup(self): @@ -95,6 +120,7 @@ def setup(self): add_aviary_input(self, Aircraft.Wing.ASPECT_RATIO_REFERENCE, units='unitless') add_aviary_input(self, Aircraft.Wing.STRUT_BRACING_FACTOR, units='unitless') add_aviary_input(self, Aircraft.Wing.AEROELASTIC_TAILORING_FACTOR, units='unitless') + add_aviary_input(self, Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL) if total_num_wing_engines > 1: add_aviary_input( @@ -167,8 +193,8 @@ def compute(self, inputs, outputs): (dy[1:] + 2.0 * integration_stations[1:-1]) * dy[1:] * sweep_int_stations[1:-1] ) - load_distribution_factor = self.options[Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL] - load_intensity = load_intensity_by_factor(load_distribution_factor, integration_stations) + load_distrib_factor = inputs[Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL] + load_intensity = smooth_load_intensity_by_factor(load_distrib_factor, integration_stations) chord_interp = InterpND( method='slinear', points=(inp_stations), x_interp=integration_stations @@ -307,7 +333,6 @@ def initialize(self): add_aviary_option(self, Aircraft.Engine.NUM_WING_ENGINES) add_aviary_option(self, Aircraft.Propulsion.TOTAL_NUM_WING_ENGINES) add_aviary_option(self, Aircraft.Wing.INPUT_STATION_DISTRIBUTION) - add_aviary_option(self, Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL) add_aviary_option(self, Aircraft.Wing.NUM_INTEGRATION_STATIONS) add_aviary_option(self, Aircraft.BWB.DETAILED_WING_PROVIDED) add_aviary_option(self, Settings.VERBOSITY) @@ -335,6 +360,7 @@ def setup(self): add_aviary_input(self, Aircraft.Wing.AEROELASTIC_TAILORING_FACTOR, units='unitless') add_aviary_input(self, Aircraft.Wing.SPAN, units='ft') add_aviary_input(self, Aircraft.Fuselage.MAX_WIDTH, units='ft') + add_aviary_input(self, Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL) if total_num_wing_engines > 1: add_aviary_input( @@ -443,8 +469,8 @@ def compute(self, inputs, outputs): (dy[0:] + 2.0 * integration_stations[0:-1]) * dy[0:] * sweep_int_stations[0:-1] ) - load_distribution_factor = self.options[Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL] - load_intensity = load_intensity_by_factor(load_distribution_factor, integration_stations) + load_distrib_factor = inputs[Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL] + load_intensity = smooth_load_intensity_by_factor(load_distrib_factor, integration_stations) chord_interp = InterpND( method='slinear', points=(inp_stations_mod), x_interp=integration_stations From 082a2aa02d79d23ced1cea005638b9f816ffcb8a Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Thu, 20 Aug 2026 19:01:57 -0400 Subject: [PATCH 2/5] added a note --- aviary/subsystems/mass/flops_based/test/test_wing_detailed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py index 68eb819db5..eed0bf358b 100644 --- a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py @@ -174,6 +174,7 @@ def test_case_multiengine(self): ) assert_near_equal(pod_inertia, pod_inertia_expected, tolerance=1e-10) + # is this needed? There is no compute_partial() in DetailedWingBendingFact partial_data = prob.check_partials( out_stream=None, compact_print=True, From 826bc2f8243d62afe7c836bb5c1d531329acde3e Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Fri, 21 Aug 2026 13:21:07 -0400 Subject: [PATCH 3/5] added two cases: LOAD_DISTRIBUTION_CONTROL = 1.25 and 2.75 --- .../flops_based/test/test_wing_detailed.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py index eed0bf358b..b42b6c9e61 100644 --- a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py @@ -417,7 +417,7 @@ def test_extreme_engine_loc(self): def test_intensity_factor(self): """ data taken from high_wing_single_aisle.csv - Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL = 3, 1, 1.5, 2.5 + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL = 3, 1, 1.5, 2.5, 1.25, 2.75 """ options = get_option_defaults() options.set_val(Aircraft.Engine.NUM_ENGINES, val=[2], units='unitless') @@ -517,6 +517,34 @@ def test_intensity_factor(self): pod_inertia_factor = prob.get_val(Aircraft.Wing.ENG_POD_INERTIA_FACTOR) assert_near_equal(pod_inertia_factor, 1.0, tolerance=1e-9) + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=1.25, units='unitless' + ) + setup_model_options(prob, options) + prob.setup(check=False, force_alloc_complex=True) + prob.run_model() + + bending_mat_factor = prob.get_val(Aircraft.Wing.BENDING_MATERIAL_FACTOR) + # without smoothing, it is 1.7507575953 + assert_near_equal(bending_mat_factor, 1.78032845, tolerance=1e-9) + + pod_inertia_factor = prob.get_val(Aircraft.Wing.ENG_POD_INERTIA_FACTOR) + assert_near_equal(pod_inertia_factor, 1.0, tolerance=1e-9) + + prob.model.set_input_defaults( + Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, val=2.75, units='unitless' + ) + setup_model_options(prob, options) + prob.setup(check=False, force_alloc_complex=True) + prob.run_model() + + bending_mat_factor = prob.get_val(Aircraft.Wing.BENDING_MATERIAL_FACTOR) + # without smoothing, it is 2.7838124896 + assert_near_equal(bending_mat_factor, 2.8107811534, tolerance=1e-9) + + pod_inertia_factor = prob.get_val(Aircraft.Wing.ENG_POD_INERTIA_FACTOR) + assert_near_equal(pod_inertia_factor, 1.0, tolerance=1e-9) + def test_IO(self): assert_match_varnames(self.prob.model) From 002d5815005847fe3d665c2f6511820add5467d9 Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Wed, 2 Sep 2026 13:08:59 -0400 Subject: [PATCH 4/5] modified a note in code --- aviary/subsystems/mass/flops_based/wing_detailed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aviary/subsystems/mass/flops_based/wing_detailed.py b/aviary/subsystems/mass/flops_based/wing_detailed.py index 27ee67c45b..61c2cbe0e2 100644 --- a/aviary/subsystems/mass/flops_based/wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/wing_detailed.py @@ -60,7 +60,7 @@ def smooth_load_intensity_by_factor(load_dist_factor, intn_stations): intn_stations (array): integration stations Note: In FLOPS, load_dist_factor can be 0 in which case load intensities are computed - based on input pressure distribution. + based on input pressure distribution. This is NOT currently implemented in Aviary. """ a = 1.0 - intn_stations b = np.sqrt(1.0 - intn_stations**2) From 167ab7806ba9f4b492e5df4b0abe16a84fd63a16 Mon Sep 17 00:00:00 2001 From: Jason Kirk <110835404+jkirk5@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:36:27 -0400 Subject: [PATCH 5/5] Update aviary/subsystems/mass/flops_based/test/test_wing_detailed.py --- aviary/subsystems/mass/flops_based/test/test_wing_detailed.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py index fdcd3dba2d..9dc53cfa75 100644 --- a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py @@ -174,7 +174,6 @@ def test_case_multiengine(self): ) assert_near_equal(pod_inertia, pod_inertia_expected, tolerance=1e-10) - # is this needed? There is no compute_partial() in DetailedWingBendingFact partial_data = prob.check_partials( out_stream=None, compact_print=True,