From 2796e4ffafbf225eb70e2c1de4ead7ca5d2522c6 Mon Sep 17 00:00:00 2001 From: camUrban Date: Wed, 4 Dec 2024 13:18:01 -0500 Subject: [PATCH] I reformatted with Black again. --- benchmarks/unsteady_benchmark.py | 108 ++- benchmarks/unsteady_benchmark_converge.py | 106 ++- examples/analyze_steady_trim_example.py | 74 +- examples/analyze_unsteady_trim_example.py | 137 ++- examples/steady_convergence_example.py | 162 +++- ..._horseshoe_vortex_lattice_method_solver.py | 255 +++--- ...teady_ring_vortex_lattice_method_solver.py | 289 ++++--- ...ing_vortex_lattice_method_solver_static.py | 167 ++-- ...g_vortex_lattice_method_solver_variable.py | 164 ++-- ...attice_method_solver_variable_formation.py | 299 +++++-- .../unsteady_static_convergence_example.py | 80 +- .../unsteady_variable_convergence_example.py | 82 +- formation flight/formation_flight.py | 145 +++- .../formation_flight_convergence.py | 295 +++++-- pterasoftware/aerodynamics.py | 372 ++++++--- pterasoftware/geometry.py | 222 +++-- ..._horseshoe_vortex_lattice_method_solver.py | 130 ++- ...teady_ring_vortex_lattice_method_solver.py | 289 ++++--- ...ing_vortex_lattice_method_solver_static.py | 167 ++-- ...g_vortex_lattice_method_solver_variable.py | 164 ++-- ...attice_method_solver_variable_formation.py | 303 +++++-- pterasoftware/movement.py | 528 ++++++++---- pterasoftware/operating_point.py | 38 +- pterasoftware/output.py | 783 +++++++++++++----- pterasoftware/panel.py | 24 +- pterasoftware/problems.py | 10 +- .../steady_horseshoe_vortex_lattice_method.py | 91 +- .../steady_ring_vortex_lattice_method.py | 194 +++-- pterasoftware/trim.py | 161 +++- pterasoftware/ui_resources/textdialog.py | 15 +- .../unsteady_ring_vortex_lattice_method.py | 703 +++++++++++----- .../integration/fixtures/airplane_fixtures.py | 313 +++++-- .../integration/fixtures/movement_fixtures.py | 283 +++++-- .../integration/fixtures/problem_fixtures.py | 33 +- tests/integration/test_output.py | 24 +- tests/integration/test_steady_convergence.py | 15 +- ..._steady_horseshoe_vortex_lattice_method.py | 58 +- .../test_steady_ring_vortex_lattice_method.py | 23 +- ...ce_method_multiple_wing_static_geometry.py | 11 +- ..._method_multiple_wing_variable_geometry.py | 14 +- ...g_vortex_lattice_method_static_geometry.py | 15 +- ...vortex_lattice_method_variable_geometry.py | 15 +- tests/unit/fixtures/vortex_fixtures.py | 17 +- tests/unit/test_horseshoe_vortex.py | 102 ++- tests/unit/test_line_vortex.py | 21 +- tests/unit/test_ring_vortex.py | 252 ++++-- 46 files changed, 5456 insertions(+), 2297 deletions(-) diff --git a/benchmarks/unsteady_benchmark.py b/benchmarks/unsteady_benchmark.py index f21aef8e..250492a5 100644 --- a/benchmarks/unsteady_benchmark.py +++ b/benchmarks/unsteady_benchmark.py @@ -9,59 +9,105 @@ num_chordwise_panels = 5 num_spanwise_panels = 20 -example_airplane = ps.geometry.Airplane(name="Example Airplane", wings=[ - ps.geometry.Wing(name="Main Wing", symmetric=True, - num_chordwise_panels=num_chordwise_panels, chordwise_spacing="uniform", - wing_cross_sections=[ - ps.geometry.WingCrossSection(num_spanwise_panels=num_spanwise_panels, - spanwise_spacing="uniform", chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0000", ), ), - ps.geometry.WingCrossSection(num_spanwise_panels=num_spanwise_panels, - spanwise_spacing="uniform", x_le=0.625, y_le=5.0, chord=0.5, - airfoil=ps.geometry.Airfoil(name="naca0000", ), ), ], ), ], ) +example_airplane = ps.geometry.Airplane( + name="Example Airplane", + wings=[ + ps.geometry.Wing( + name="Main Wing", + symmetric=True, + num_chordwise_panels=num_chordwise_panels, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + num_spanwise_panels=num_spanwise_panels, + spanwise_spacing="uniform", + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0000", + ), + ), + ps.geometry.WingCrossSection( + num_spanwise_panels=num_spanwise_panels, + spanwise_spacing="uniform", + x_le=0.625, + y_le=5.0, + chord=0.5, + airfoil=ps.geometry.Airfoil( + name="naca0000", + ), + ), + ], + ), + ], +) upper_wing_root_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[0], ) + base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[0], +) upper_wing_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1 / flapping_frequency, - sweeping_spacing="sine", pitching_amplitude=5.0, - pitching_period=1 / flapping_frequency, pitching_spacing="sine", - heaving_amplitude=5.0, heaving_period=1 / flapping_frequency, - heaving_spacing="sine", ) - -upper_wing_movement = ps.movement.WingMovement(base_wing=example_airplane.wings[0], - wing_cross_sections_movements=[upper_wing_root_wing_cross_section_movement, - upper_wing_tip_wing_cross_section_movement, ], ) + sweeping_amplitude=15.0, + sweeping_period=1 / flapping_frequency, + sweeping_spacing="sine", + pitching_amplitude=5.0, + pitching_period=1 / flapping_frequency, + pitching_spacing="sine", + heaving_amplitude=5.0, + heaving_period=1 / flapping_frequency, + heaving_spacing="sine", +) + +upper_wing_movement = ps.movement.WingMovement( + base_wing=example_airplane.wings[0], + wing_cross_sections_movements=[ + upper_wing_root_wing_cross_section_movement, + upper_wing_tip_wing_cross_section_movement, + ], +) del upper_wing_root_wing_cross_section_movement del upper_wing_tip_wing_cross_section_movement -airplane_movement = ps.movement.AirplaneMovement(base_airplane=example_airplane, - wing_movements=[upper_wing_movement], ) +airplane_movement = ps.movement.AirplaneMovement( + base_airplane=example_airplane, + wing_movements=[upper_wing_movement], +) del upper_wing_movement -example_operating_point = ps.operating_point.OperatingPoint(density=1.225, beta=0.0, - velocity=10.0, alpha=0.0, ) +example_operating_point = ps.operating_point.OperatingPoint( + density=1.225, + beta=0.0, + velocity=10.0, + alpha=0.0, +) operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=example_operating_point, ) + base_operating_point=example_operating_point, +) -movement = ps.movement.Movement(airplane_movements=[airplane_movement], - operating_point_movement=operating_point_movement, ) +movement = ps.movement.Movement( + airplane_movements=[airplane_movement], + operating_point_movement=operating_point_movement, +) del airplane_movement del operating_point_movement -example_problem = ps.problems.UnsteadyProblem(movement=movement, - only_final_results=True) +example_problem = ps.problems.UnsteadyProblem( + movement=movement, only_final_results=True +) example_solver = ( ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( - unsteady_problem=example_problem, )) + unsteady_problem=example_problem, + ) +) del example_problem -example_solver.run(prescribed_wake=True, calculate_streamlines=False, ) +example_solver.run( + prescribed_wake=True, + calculate_streamlines=False, +) diff --git a/benchmarks/unsteady_benchmark_converge.py b/benchmarks/unsteady_benchmark_converge.py index 30e4062f..4ad98122 100644 --- a/benchmarks/unsteady_benchmark_converge.py +++ b/benchmarks/unsteady_benchmark_converge.py @@ -4,59 +4,105 @@ num_chordwise_panels = 5 num_spanwise_panels = 20 -example_airplane = ps.geometry.Airplane(name="Example Airplane", wings=[ - ps.geometry.Wing(name="Main Wing", symmetric=True, - num_chordwise_panels=num_chordwise_panels, chordwise_spacing="uniform", - wing_cross_sections=[ - ps.geometry.WingCrossSection(num_spanwise_panels=num_spanwise_panels, - spanwise_spacing="uniform", chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0000", ), ), - ps.geometry.WingCrossSection(num_spanwise_panels=num_spanwise_panels, - spanwise_spacing="uniform", x_le=0.625, y_le=5.0, chord=0.5, - airfoil=ps.geometry.Airfoil(name="naca0000", ), ), ], ), ], ) +example_airplane = ps.geometry.Airplane( + name="Example Airplane", + wings=[ + ps.geometry.Wing( + name="Main Wing", + symmetric=True, + num_chordwise_panels=num_chordwise_panels, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + num_spanwise_panels=num_spanwise_panels, + spanwise_spacing="uniform", + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0000", + ), + ), + ps.geometry.WingCrossSection( + num_spanwise_panels=num_spanwise_panels, + spanwise_spacing="uniform", + x_le=0.625, + y_le=5.0, + chord=0.5, + airfoil=ps.geometry.Airfoil( + name="naca0000", + ), + ), + ], + ), + ], +) upper_wing_root_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[0], ) + base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[0], +) upper_wing_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1 / flapping_frequency, - sweeping_spacing="sine", pitching_amplitude=5.0, - pitching_period=1 / flapping_frequency, pitching_spacing="sine", - heaving_amplitude=5.0, heaving_period=1 / flapping_frequency, - heaving_spacing="sine", ) + sweeping_amplitude=15.0, + sweeping_period=1 / flapping_frequency, + sweeping_spacing="sine", + pitching_amplitude=5.0, + pitching_period=1 / flapping_frequency, + pitching_spacing="sine", + heaving_amplitude=5.0, + heaving_period=1 / flapping_frequency, + heaving_spacing="sine", +) -upper_wing_movement = ps.movement.WingMovement(base_wing=example_airplane.wings[0], - wing_cross_sections_movements=[upper_wing_root_wing_cross_section_movement, - upper_wing_tip_wing_cross_section_movement, ], ) +upper_wing_movement = ps.movement.WingMovement( + base_wing=example_airplane.wings[0], + wing_cross_sections_movements=[ + upper_wing_root_wing_cross_section_movement, + upper_wing_tip_wing_cross_section_movement, + ], +) del upper_wing_root_wing_cross_section_movement del upper_wing_tip_wing_cross_section_movement -airplane_movement = ps.movement.AirplaneMovement(base_airplane=example_airplane, - wing_movements=[upper_wing_movement], ) +airplane_movement = ps.movement.AirplaneMovement( + base_airplane=example_airplane, + wing_movements=[upper_wing_movement], +) del example_airplane del upper_wing_movement -example_operating_point = ps.operating_point.OperatingPoint(density=1.225, beta=0.0, - velocity=10.0, alpha=0.0, ) +example_operating_point = ps.operating_point.OperatingPoint( + density=1.225, + beta=0.0, + velocity=10.0, + alpha=0.0, +) operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=example_operating_point, ) + base_operating_point=example_operating_point, +) del example_operating_point -movement = ps.movement.Movement(airplane_movements=[airplane_movement], - operating_point_movement=operating_point_movement, ) +movement = ps.movement.Movement( + airplane_movements=[airplane_movement], + operating_point_movement=operating_point_movement, +) del airplane_movement del operating_point_movement -ps.convergence.analyze_unsteady_convergence(ref_movement=movement, prescribed_wake=True, - free_wake=True, num_cycles_bounds=(1, 3), num_chords_bounds=None, - panel_aspect_ratio_bounds=(4, 1), num_chordwise_panels_bounds=(5, 10), - convergence_criteria=5.0, ) +ps.convergence.analyze_unsteady_convergence( + ref_movement=movement, + prescribed_wake=True, + free_wake=True, + num_cycles_bounds=(1, 3), + num_chords_bounds=None, + panel_aspect_ratio_bounds=(4, 1), + num_chordwise_panels_bounds=(5, 10), + convergence_criteria=5.0, +) # Converged result: # wake=false diff --git a/examples/analyze_steady_trim_example.py b/examples/analyze_steady_trim_example.py index 4b551db3..8a4be2de 100644 --- a/examples/analyze_steady_trim_example.py +++ b/examples/analyze_steady_trim_example.py @@ -12,16 +12,55 @@ # Create an airplane object. Read through the solver examples for more details on # creating this object. -default_airplane = ps.geometry.Airplane(weight=250, wings=[ - ps.geometry.Wing(symmetric=True, wing_cross_sections=[ - ps.geometry.WingCrossSection(airfoil=ps.geometry.Airfoil(name="naca2412", ), ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=5.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), - ps.geometry.Wing(x_le=7.50, z_le=0.25, symmetric=True, wing_cross_sections=[ - ps.geometry.WingCrossSection(x_le=0.0, y_le=0.0, chord=0.5, twist=-5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=1.0, chord=0.5, twist=-5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) +default_airplane = ps.geometry.Airplane( + weight=250, + wings=[ + ps.geometry.Wing( + symmetric=True, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=5.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), + ps.geometry.Wing( + x_le=7.50, + z_le=0.25, + symmetric=True, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=0.0, + chord=0.5, + twist=-5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=1.0, + chord=0.5, + twist=-5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Create an operating point object for this example's problem. Be sure to specify an # external thrust because this aircraft is not flapping, and will therefore generate @@ -30,15 +69,20 @@ default_operating_point = ps.operating_point.OperatingPoint(external_thrust=5) # Construct this example's problem object. -default_problem = ps.problems.SteadyProblem(airplanes=[default_airplane], - operating_point=default_operating_point) +default_problem = ps.problems.SteadyProblem( + airplanes=[default_airplane], operating_point=default_operating_point +) # Call the analyze_steady_trim function to search for a trim condition (thrust # balances drag, weight balances lift, and all moments are close to zero) within a # certain set of bounds. -trim_conditions = ps.trim.analyze_steady_trim(problem=default_problem, - velocity_bounds=(5, 15), alpha_bounds=(-10, 10), beta_bounds=(-1, 1), - external_thrust_bounds=(0, 10), ) +trim_conditions = ps.trim.analyze_steady_trim( + problem=default_problem, + velocity_bounds=(5, 15), + alpha_bounds=(-10, 10), + beta_bounds=(-1, 1), + external_thrust_bounds=(0, 10), +) # Log the trim conditions. If these display "nan", then the trim function couldn't # find a trimmed state. diff --git a/examples/analyze_unsteady_trim_example.py b/examples/analyze_unsteady_trim_example.py index ed2c76c2..28d4035e 100644 --- a/examples/analyze_unsteady_trim_example.py +++ b/examples/analyze_unsteady_trim_example.py @@ -12,35 +12,98 @@ # Create an airplane object. Read through the solver examples for more details on # creating this object. -example_airplane = ps.geometry.Airplane(x_ref=0.14, weight=420, wings=[ - ps.geometry.Wing(symmetric=True, num_chordwise_panels=5, wing_cross_sections=[ - ps.geometry.WingCrossSection(num_spanwise_panels=5, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=5.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), - ps.geometry.Wing(x_le=5, symmetric=True, num_chordwise_panels=5, - wing_cross_sections=[ - ps.geometry.WingCrossSection(num_spanwise_panels=5, twist=-5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=1.0, chord=1.0, twist=-5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) +example_airplane = ps.geometry.Airplane( + x_ref=0.14, + weight=420, + wings=[ + ps.geometry.Wing( + symmetric=True, + num_chordwise_panels=5, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + num_spanwise_panels=5, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=5.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), + ps.geometry.Wing( + x_le=5, + symmetric=True, + num_chordwise_panels=5, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + num_spanwise_panels=5, + twist=-5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=1.0, + chord=1.0, + twist=-5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Create a movement for this example's airplane. Read through the unsteady solver # examples for more details on this type of object. -example_airplane_movement = ps.movement.AirplaneMovement(base_airplane=example_airplane, - wing_movements=[ps.movement.WingMovement(base_wing=example_airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[0]), - ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[ - 1], sweeping_period=1.0, sweeping_amplitude=5.0, heaving_period=1.0, - heaving_amplitude=10.0, ), ], ), - ps.movement.WingMovement(base_wing=example_airplane.wings[1], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[ - 0]), ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[ - 1]), ], ), ], ) +example_airplane_movement = ps.movement.AirplaneMovement( + base_airplane=example_airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=example_airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=example_airplane.wings[ + 0 + ].wing_cross_sections[0] + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=example_airplane.wings[ + 0 + ].wing_cross_sections[1], + sweeping_period=1.0, + sweeping_amplitude=5.0, + heaving_period=1.0, + heaving_amplitude=10.0, + ), + ], + ), + ps.movement.WingMovement( + base_wing=example_airplane.wings[1], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=example_airplane.wings[ + 1 + ].wing_cross_sections[0] + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=example_airplane.wings[ + 1 + ].wing_cross_sections[1] + ), + ], + ), + ], +) # Create an operating point object for this example's problem using the default values. example_operating_point = ps.operating_point.OperatingPoint() @@ -48,22 +111,30 @@ # Create an operating point movement object. Read through the unsteady solver # examples for more details on this type of object. example_operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=example_operating_point) + base_operating_point=example_operating_point +) # Construct this example's movement and problem object. Only calculate final results # to speed up the solver. -example_movement = ps.movement.Movement(airplane_movements=[example_airplane_movement], - operating_point_movement=example_operating_point_movement, ) -example_problem = ps.problems.UnsteadyProblem(movement=example_movement, - only_final_results=False, ) +example_movement = ps.movement.Movement( + airplane_movements=[example_airplane_movement], + operating_point_movement=example_operating_point_movement, +) +example_problem = ps.problems.UnsteadyProblem( + movement=example_movement, + only_final_results=False, +) # Call the analyze_unsteady_trim function to search for a trim condition (thrust # balances drag, weight balances lift, and all moments are close to zero) within a # certain set of bounds. trim_conditions = ps.trim.analyze_unsteady_trim( airplane_movement=example_airplane_movement, - operating_point=example_operating_point, velocity_bounds=(5, 15), - alpha_bounds=(-10, 10), beta_bounds=(-0.1, 0.1), ) + operating_point=example_operating_point, + velocity_bounds=(5, 15), + alpha_bounds=(-10, 10), + beta_bounds=(-0.1, 0.1), +) # Log the trim conditions. If these display "nan", then the trim function couldn't # find a trimmed state. diff --git a/examples/steady_convergence_example.py b/examples/steady_convergence_example.py index d20c6bf0..d6516cc8 100644 --- a/examples/steady_convergence_example.py +++ b/examples/steady_convergence_example.py @@ -6,42 +6,132 @@ # Create two airplane objects. Read through the solver and formation examples for # more details on creating these objects. -leading_airplane = ps.geometry.Airplane(wings=[ - ps.geometry.Wing(symmetric=True, chordwise_spacing="uniform", wing_cross_sections=[ - ps.geometry.WingCrossSection(airfoil=ps.geometry.Airfoil(name="naca2412", ), - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=5.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), - ps.geometry.WingCrossSection(x_le=0.25, y_le=10.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), - ps.geometry.Wing(x_le=5.0, symmetric=True, chordwise_spacing="uniform", - wing_cross_sections=[ - ps.geometry.WingCrossSection(airfoil=ps.geometry.Airfoil(name="naca0012", ), - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=5.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) -trailing_airplane = ps.geometry.Airplane(x_ref=10, y_ref=-5, wings=[ - ps.geometry.Wing(x_le=10, y_le=-5, symmetric=True, chordwise_spacing="uniform", - wing_cross_sections=[ - ps.geometry.WingCrossSection(airfoil=ps.geometry.Airfoil(name="naca2412", ), - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=5.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), - ps.geometry.WingCrossSection(x_le=0.25, y_le=10.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), - ps.geometry.Wing(x_le=15.0, y_le=-5, symmetric=True, chordwise_spacing="uniform", - wing_cross_sections=[ - ps.geometry.WingCrossSection(airfoil=ps.geometry.Airfoil(name="naca0012", ), - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=5.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) +leading_airplane = ps.geometry.Airplane( + wings=[ + ps.geometry.Wing( + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=5.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.25, + y_le=10.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), + ps.geometry.Wing( + x_le=5.0, + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=5.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) +trailing_airplane = ps.geometry.Airplane( + x_ref=10, + y_ref=-5, + wings=[ + ps.geometry.Wing( + x_le=10, + y_le=-5, + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=5.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.25, + y_le=10.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), + ps.geometry.Wing( + x_le=15.0, + y_le=-5, + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=5.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Create an operating point object. operating_point = ps.operating_point.OperatingPoint() # Create a steady problem. We will pass this into the convergence function. -problem = ps.problems.SteadyProblem(airplanes=[leading_airplane, trailing_airplane], - operating_point=operating_point, ) +problem = ps.problems.SteadyProblem( + airplanes=[leading_airplane, trailing_airplane], + operating_point=operating_point, +) del leading_airplane del trailing_airplane @@ -54,9 +144,13 @@ # return the parameters it found to result in a converged solution. See the # analyze_steady_convergence function docstring for more details. The progress and # results are displayed to the console. -ps.convergence.analyze_steady_convergence(ref_problem=problem, - solver_type="steady ring vortex lattice method", panel_aspect_ratio_bounds=(4, 1), - num_chordwise_panels_bounds=(3, 8), convergence_criteria=1.0, ) +ps.convergence.analyze_steady_convergence( + ref_problem=problem, + solver_type="steady ring vortex lattice method", + panel_aspect_ratio_bounds=(4, 1), + num_chordwise_panels_bounds=(3, 8), + convergence_criteria=1.0, +) # Check the console that the convergence analysis found that the solution converged # with the following parameters: diff --git a/examples/steady_horseshoe_vortex_lattice_method_solver.py b/examples/steady_horseshoe_vortex_lattice_method_solver.py index d7b259ba..a204067f 100644 --- a/examples/steady_horseshoe_vortex_lattice_method_solver.py +++ b/examples/steady_horseshoe_vortex_lattice_method_solver.py @@ -18,110 +18,158 @@ # program is in SI units. Note: these values are relative to the global # coordinate system fixed front left corner of the first airplane's first wing's # root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, # Give the reference dimensions of this aircraft. "s_ref" is the reference area # in meters squared, "b_ref" is the reference span in meters, and "c_ref" is the # reference chord in meters. I set these values to None, which is their default, # so that they will be populated by the first wing object's calculated # characteristics. Note that the reference area used in this program is the # wetted area of the wing's mean-camberline surface. - s_ref=None, b_ref=None, c_ref=None, wings=[ps.geometry.Wing(name="Main Wing", - # Define the location of the leading edge of the wing relative to the - # global coordinate system fixed front left corner of the first - # airplane's first wing's root wing cross section. These values all - # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, - # Declare that this wing is symmetric. This means that the geometry will - # be reflected across plane of this wing's root wing cross section. Note - # that the geometry coordinates are defined as such: If you were riding - # in the airplane, the positive x direction would point behind you, - # the positive y direction would point out of your right wing, and the - # positive z direction would point upwards, out of your chair. These - # directions form a right-handed coordinate system. The default value of - # "symmetric" is False. - symmetric=True, - # Define the number of chordwise panels on the wing, and the spacing - # between them. The number of chordwise panels defaults to 8 panels. The - # spacing defaults to "cosine", which makes the panels relatively finer, - # in the chordwise direction, near the leading and trailing edges. The - # other option is "uniform". - num_chordwise_panels=6, chordwise_spacing="cosine", - # Every wing has a list of wing cross sections. In order for the geometry - # output to be sensible, each wing must have at least two wing cross - # sections. - wing_cross_sections=[ps.geometry.WingCrossSection( - # Define the location of the leading edge of the wing cross - # section relative to the wing's leading edge. These values all + s_ref=None, + b_ref=None, + c_ref=None, + wings=[ + ps.geometry.Wing( + name="Main Wing", + # Define the location of the leading edge of the wing relative to the + # global coordinate system fixed front left corner of the first + # airplane's first wing's root wing cross section. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, - # Define the twist of the wing cross section in degrees. This is - # equivalent to incidence angle of cross section. The twist is - # about the leading edge. Note that the twist is only stable up - # to 45.0 degrees. Values above that produce unexpected results. - # This will be fixed in a future release. The default value is - # 0.0 degrees. Positive twist corresponds to positive rotation - # about the y axis, as defined by the right-hand rule. - twist=0.0, # Define the type of control surface. The options are "symmetric" - # and "asymmetric". This is only applicable if your wing is also - # symmetric. If so, symmetric control surfaces will deflect in - # the same direction, like flaps, while asymmetric control - # surfaces will deflect in opposite directions, like ailerons. - # The default value is "symmetric". - control_surface_type="symmetric", - # Define the point on the airfoil where the control surface - # hinges. This is expressed as a faction of the chord length, - # back from the leading edge. The default value is 0.75. - control_surface_hinge_point=0.75, - # Define the deflection of the control surface in degrees. The - # default is 0.0 degrees. - control_surface_deflection=0.0, - # Define the number of spanwise panels on the wing cross section, - # and the spacing between them. The number of spanwise panels - # defaults to 8 panels. The spacing defaults to "cosine", - # which makes the panels relatively finer, in the spanwise - # direction, near the cross section ends. The other option is - # "uniform". - num_spanwise_panels=8, spanwise_spacing="cosine", - # Set the chord of this cross section to be 1.75 meters. This - # value defaults to 1.0 meter. - chord=1.75, airfoil=ps.geometry.Airfoil( - # Give the airfoil a name. This defaults to "Untitled - # Airfoil". This name should correspond to a name in the - # airfoil directory or a NACA four series airfoil, unless you - # are passing in your own coordinates. - name="naca2412", - # If you wish to pass in coordinates, set this to an N x 2 - # array of the airfoil's coordinates, where N is the number - # of coordinates. Treat this as an immutable, don't edit - # directly after initialization. If you wish to load - # coordinates from the airfoil directory, leave this as None. - # The default is None. Make sure that any airfoil coordinates - # used range in x from 0 to 1. - coordinates=None, - # This is the variable that determines whether you would like - # to repanel the airfoil coordinates. This applies to - # coordinates passed in by the user or to the directory - # coordinates. I highly recommended setting this to True. The - # default is True. - repanel=True, - # This is number of points to use if repaneling the airfoil. - # It is ignored if the repanel is False. The default is 400. - n_points_per_side=400, ), ), - # Define the next wing cross section. From here on out, - # the declarations will not be as commented as the previous. See the - # above comments if you have questions. - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, z_le=1.0, chord=1.5, - twist=5.0, airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), + x_le=0.0, + y_le=0.0, + z_le=0.0, + # Declare that this wing is symmetric. This means that the geometry will + # be reflected across plane of this wing's root wing cross section. Note + # that the geometry coordinates are defined as such: If you were riding + # in the airplane, the positive x direction would point behind you, + # the positive y direction would point out of your right wing, and the + # positive z direction would point upwards, out of your chair. These + # directions form a right-handed coordinate system. The default value of + # "symmetric" is False. + symmetric=True, + # Define the number of chordwise panels on the wing, and the spacing + # between them. The number of chordwise panels defaults to 8 panels. The + # spacing defaults to "cosine", which makes the panels relatively finer, + # in the chordwise direction, near the leading and trailing edges. The + # other option is "uniform". + num_chordwise_panels=6, + chordwise_spacing="cosine", + # Every wing has a list of wing cross sections. In order for the geometry + # output to be sensible, each wing must have at least two wing cross + # sections. + wing_cross_sections=[ + ps.geometry.WingCrossSection( + # Define the location of the leading edge of the wing cross + # section relative to the wing's leading edge. These values all + # default to 0.0 meters. + x_le=0.0, + y_le=0.0, + z_le=0.0, + # Define the twist of the wing cross section in degrees. This is + # equivalent to incidence angle of cross section. The twist is + # about the leading edge. Note that the twist is only stable up + # to 45.0 degrees. Values above that produce unexpected results. + # This will be fixed in a future release. The default value is + # 0.0 degrees. Positive twist corresponds to positive rotation + # about the y axis, as defined by the right-hand rule. + twist=0.0, # Define the type of control surface. The options are "symmetric" + # and "asymmetric". This is only applicable if your wing is also + # symmetric. If so, symmetric control surfaces will deflect in + # the same direction, like flaps, while asymmetric control + # surfaces will deflect in opposite directions, like ailerons. + # The default value is "symmetric". + control_surface_type="symmetric", + # Define the point on the airfoil where the control surface + # hinges. This is expressed as a faction of the chord length, + # back from the leading edge. The default value is 0.75. + control_surface_hinge_point=0.75, + # Define the deflection of the control surface in degrees. The + # default is 0.0 degrees. + control_surface_deflection=0.0, + # Define the number of spanwise panels on the wing cross section, + # and the spacing between them. The number of spanwise panels + # defaults to 8 panels. The spacing defaults to "cosine", + # which makes the panels relatively finer, in the spanwise + # direction, near the cross section ends. The other option is + # "uniform". + num_spanwise_panels=8, + spanwise_spacing="cosine", + # Set the chord of this cross section to be 1.75 meters. This + # value defaults to 1.0 meter. + chord=1.75, + airfoil=ps.geometry.Airfoil( + # Give the airfoil a name. This defaults to "Untitled + # Airfoil". This name should correspond to a name in the + # airfoil directory or a NACA four series airfoil, unless you + # are passing in your own coordinates. + name="naca2412", + # If you wish to pass in coordinates, set this to an N x 2 + # array of the airfoil's coordinates, where N is the number + # of coordinates. Treat this as an immutable, don't edit + # directly after initialization. If you wish to load + # coordinates from the airfoil directory, leave this as None. + # The default is None. Make sure that any airfoil coordinates + # used range in x from 0 to 1. + coordinates=None, + # This is the variable that determines whether you would like + # to repanel the airfoil coordinates. This applies to + # coordinates passed in by the user or to the directory + # coordinates. I highly recommended setting this to True. The + # default is True. + repanel=True, + # This is number of points to use if repaneling the airfoil. + # It is ignored if the repanel is False. The default is 400. + n_points_per_side=400, + ), + ), + # Define the next wing cross section. From here on out, + # the declarations will not be as commented as the previous. See the + # above comments if you have questions. + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + z_le=1.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="V-Tail", x_le=6.75, z_le=0.25, symmetric=True, + ps.geometry.Wing( + name="V-Tail", + x_le=6.75, + z_le=0.25, + symmetric=True, # Define this wing's root wing cross section. - wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + # Give the root wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, z_le=1.0, chord=1.0, + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + z_le=1.0, + chord=1.0, twist=-5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Define a new operating point object. This defines the state at which the airplane # object is operating. @@ -137,7 +185,8 @@ velocity=10.0, # Define the angle of attack the airplane is experiencing. This defaults to 5.0 # degrees. - alpha=1.0, ) + alpha=1.0, +) # Define a new steady problem. A steady problem contains an airplane object and an # operating point object. @@ -145,7 +194,8 @@ # Set this steady problem's airplane object to be the one we just created. airplanes=[example_airplane], # Set this steady problem's operating point object ot be the one we just created. - operating_point=example_operating_point, ) + operating_point=example_operating_point, +) # Now, the airplane and operating point object exist within the steady problem # object. I like to delete the external pointers to these objects to ease debugging. @@ -155,10 +205,10 @@ # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. -example_solver = (ps.steady_horseshoe_vortex_lattice_method -.SteadyHorseshoeVortexLatticeMethodSolver( +example_solver = ps.steady_horseshoe_vortex_lattice_method.SteadyHorseshoeVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - steady_problem=example_problem)) + steady_problem=example_problem +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. Again, this is unnecessary, I just like to do this to ease debugging. @@ -169,13 +219,15 @@ # This parameter determines the detail of information that the solver's logger # will output while running. The options are, in order of detail and severity, # "Debug", "Info", "Warning", "Error", "Critical". The default value is "Warning". - logging_level="Warning", ) + logging_level="Warning", +) # Call this function from the output module to print the results. ps.output.print_steady_results(steady_solver=example_solver) # Call the software's draw function on the solver. -ps.output.draw(solver=example_solver, +ps.output.draw( + solver=example_solver, # Tell the draw function to color the aircraft's wing panels with the local lift # coefficient. The valid arguments for this parameter are None, "induced drag", # "side force", or "lift". @@ -188,7 +240,8 @@ show_wake_vortices=False, # Tell the draw function to not save the drawing as an image file. This way, # the drawing will still be displayed but not saved. This value defaults to false. - save=False, ) + save=False, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/examples/steady_ring_vortex_lattice_method_solver.py b/examples/steady_ring_vortex_lattice_method_solver.py index 04c321e1..3e296353 100644 --- a/examples/steady_ring_vortex_lattice_method_solver.py +++ b/examples/steady_ring_vortex_lattice_method_solver.py @@ -18,122 +18,186 @@ # program is in SI units. Note: these values are relative to the global # coordinate system fixed front left corner of the first airplane's first wing's # root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, # Give the reference dimensions of this aircraft. "s_ref" is the reference area # in meters squared, "b_ref" is the reference span in meters, and "c_ref" is the # reference chord in meters. I set these values to None, which is their default, # so that they will be populated by the first wing object's calculated # characteristics. Note that the reference area used in this program is the # wetted area of the wing's mean-camberline surface. - s_ref=None, b_ref=None, c_ref=None, wings=[ps.geometry.Wing(name="Main Wing", - # Define the location of the leading edge of the wing relative to the - # global coordinate system fixed front left corner of the first - # airplane's first wing's root wing cross section. These values all - # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, - # Declare that this wing is symmetric. This means that the geometry will - # be reflected across plane of this wing's root wing cross section. Note - # that the geometry coordinates are defined as such: If you were riding - # in the airplane, the positive x direction would point behind you, - # the positive y direction would point out of your right wing, and the - # positive z direction would point upwards, out of your chair. These - # directions form a right-handed coordinate system. The default value of - # "symmetric" is false. - symmetric=True, - # Define the number of chordwise panels on the wing, and the spacing - # between them. The number of chordwise panels defaults to 8 panels. The - # spacing defaults to "cosine", which makes the panels relatively finer, - # in the chordwise direction, near the leading and trailing edges. The - # other option is "uniform". - num_chordwise_panels=8, chordwise_spacing="cosine", - # Every wing has a list of wing cross sections. In order for the geometry - # output to be sensible, each wing must have at least two wing cross - # sections. - wing_cross_sections=[ps.geometry.WingCrossSection( - # Define the location of the leading edge of the wing cross - # section relative to the wing's leading edge. These values all + s_ref=None, + b_ref=None, + c_ref=None, + wings=[ + ps.geometry.Wing( + name="Main Wing", + # Define the location of the leading edge of the wing relative to the + # global coordinate system fixed front left corner of the first + # airplane's first wing's root wing cross section. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, - # Define the twist of the wing cross section in degrees. This is - # equivalent to incidence angle of cross section. The twist is - # about the leading edge. Note that the twist is only stable up - # to 45.0 degrees. Values above that produce unexpected results. - # This will be fixed in a future release. The default value is - # 0.0 degrees. Positive twist corresponds to positive rotation - # about the y axis, as defined by the right-hand rule. - twist=0.0, # Define the type of control surface. The options are "symmetric" - # and "asymmetric". This is only applicable if your wing is also - # symmetric. If so, symmetric control surfaces will deflect in - # the same direction, like flaps, while asymmetric control - # surfaces will deflect in opposite directions, like ailerons. - # The default value is "symmetric". - control_surface_type="asymmetric", - # Define the point on the airfoil where the control surface - # hinges. This is expressed as a faction of the chord length, - # back from the leading edge. The default value is 0.75. - control_surface_hinge_point=0.75, - # Define the deflection of the control surface in degrees. The - # default is 0.0 degrees. We'll set it to 10.0 degrees to show an - # example of an aileron deflection. - control_surface_deflection=10.0, - # Define the number of spanwise panels on the wing cross section, - # and the spacing between them. The number of spanwise panels - # defaults to 8 panels. The spacing defaults to "cosine", - # which makes the panels relatively finer, in the spanwise - # direction, near the cross section ends. The other option is - # "uniform". - num_spanwise_panels=8, spanwise_spacing="cosine", - # Set the chord of this cross section to be 1.75 meters. This - # value defaults to 1.0 meter. - chord=1.5, airfoil=ps.geometry.Airfoil( - # Give the airfoil a name. This defaults to "Untitled - # Airfoil". This name should correspond to a name in the - # airfoil directory or a NACA four series airfoil, unless you - # are passing in your own coordinates. - name="naca2412", - # If you wish to pass in coordinates, set this to an N x 2 - # array of the airfoil's coordinates, where N is the number - # of coordinates. Treat this as an immutable, don't edit - # directly after initialization. If you wish to load - # coordinates from the airfoil directory, leave this as None. - # The default is None. Make sure that any airfoil coordinates - # used range in x from 0 to 1. - coordinates=None, - # This is the variable that determines whether you would like - # to repanel the airfoil coordinates. This applies to - # coordinates passed in by the user or to the directory - # coordinates. I highly recommended setting this to True. The - # default is True. - repanel=True, - # This is number of points to use if repaneling the airfoil. - # It is ignored if the repanel is False. The default is 400. - n_points_per_side=400, ), ), - # Define the next wing cross section. From here on out, - # the declarations will not be as commented as the previous. See the - # above comments if you have questions. - ps.geometry.WingCrossSection(x_le=1.5, y_le=6.0, z_le=0.5, chord=0.75, - control_surface_type="asymmetric", control_surface_hinge_point=0.75, - control_surface_deflection=10.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), + x_le=0.0, + y_le=0.0, + z_le=0.0, + # Declare that this wing is symmetric. This means that the geometry will + # be reflected across plane of this wing's root wing cross section. Note + # that the geometry coordinates are defined as such: If you were riding + # in the airplane, the positive x direction would point behind you, + # the positive y direction would point out of your right wing, and the + # positive z direction would point upwards, out of your chair. These + # directions form a right-handed coordinate system. The default value of + # "symmetric" is false. + symmetric=True, + # Define the number of chordwise panels on the wing, and the spacing + # between them. The number of chordwise panels defaults to 8 panels. The + # spacing defaults to "cosine", which makes the panels relatively finer, + # in the chordwise direction, near the leading and trailing edges. The + # other option is "uniform". + num_chordwise_panels=8, + chordwise_spacing="cosine", + # Every wing has a list of wing cross sections. In order for the geometry + # output to be sensible, each wing must have at least two wing cross + # sections. + wing_cross_sections=[ + ps.geometry.WingCrossSection( + # Define the location of the leading edge of the wing cross + # section relative to the wing's leading edge. These values all + # default to 0.0 meters. + x_le=0.0, + y_le=0.0, + z_le=0.0, + # Define the twist of the wing cross section in degrees. This is + # equivalent to incidence angle of cross section. The twist is + # about the leading edge. Note that the twist is only stable up + # to 45.0 degrees. Values above that produce unexpected results. + # This will be fixed in a future release. The default value is + # 0.0 degrees. Positive twist corresponds to positive rotation + # about the y axis, as defined by the right-hand rule. + twist=0.0, # Define the type of control surface. The options are "symmetric" + # and "asymmetric". This is only applicable if your wing is also + # symmetric. If so, symmetric control surfaces will deflect in + # the same direction, like flaps, while asymmetric control + # surfaces will deflect in opposite directions, like ailerons. + # The default value is "symmetric". + control_surface_type="asymmetric", + # Define the point on the airfoil where the control surface + # hinges. This is expressed as a faction of the chord length, + # back from the leading edge. The default value is 0.75. + control_surface_hinge_point=0.75, + # Define the deflection of the control surface in degrees. The + # default is 0.0 degrees. We'll set it to 10.0 degrees to show an + # example of an aileron deflection. + control_surface_deflection=10.0, + # Define the number of spanwise panels on the wing cross section, + # and the spacing between them. The number of spanwise panels + # defaults to 8 panels. The spacing defaults to "cosine", + # which makes the panels relatively finer, in the spanwise + # direction, near the cross section ends. The other option is + # "uniform". + num_spanwise_panels=8, + spanwise_spacing="cosine", + # Set the chord of this cross section to be 1.75 meters. This + # value defaults to 1.0 meter. + chord=1.5, + airfoil=ps.geometry.Airfoil( + # Give the airfoil a name. This defaults to "Untitled + # Airfoil". This name should correspond to a name in the + # airfoil directory or a NACA four series airfoil, unless you + # are passing in your own coordinates. + name="naca2412", + # If you wish to pass in coordinates, set this to an N x 2 + # array of the airfoil's coordinates, where N is the number + # of coordinates. Treat this as an immutable, don't edit + # directly after initialization. If you wish to load + # coordinates from the airfoil directory, leave this as None. + # The default is None. Make sure that any airfoil coordinates + # used range in x from 0 to 1. + coordinates=None, + # This is the variable that determines whether you would like + # to repanel the airfoil coordinates. This applies to + # coordinates passed in by the user or to the directory + # coordinates. I highly recommended setting this to True. The + # default is True. + repanel=True, + # This is number of points to use if repaneling the airfoil. + # It is ignored if the repanel is False. The default is 400. + n_points_per_side=400, + ), + ), + # Define the next wing cross section. From here on out, + # the declarations will not be as commented as the previous. See the + # above comments if you have questions. + ps.geometry.WingCrossSection( + x_le=1.5, + y_le=6.0, + z_le=0.5, + chord=0.75, + control_surface_type="asymmetric", + control_surface_hinge_point=0.75, + control_surface_deflection=10.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="Horizontal Stabilizer", x_le=6.75, z_le=0.25, - symmetric=True, wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), + ps.geometry.Wing( + name="Horizontal Stabilizer", + x_le=6.75, + z_le=0.25, + symmetric=True, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + # Give the root wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, chord=1.0, twist=-5.0, + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + chord=1.0, + twist=-5.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="Vertical Stabilizer", x_le=6.75, z_le=0.5, - symmetric=False, wing_cross_sections=[ - ps.geometry.WingCrossSection(chord=1.5, + ps.geometry.Wing( + name="Vertical Stabilizer", + x_le=6.75, + z_le=0.5, + symmetric=False, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, z_le=2.0, chord=1.0, + ps.geometry.WingCrossSection( + x_le=0.5, + z_le=2.0, + chord=1.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Define a new operating point object. This defines the state at which the airplane # object is operating. @@ -149,7 +213,8 @@ velocity=10.0, # Define the angle of attack the airplane is experiencing. This defaults to 5.0 # degrees. - alpha=1.0, ) + alpha=1.0, +) # Define a new steady problem. A steady problem contains an airplane object and an # operating point object. @@ -157,7 +222,8 @@ # Set this steady problem's list of airplane objects to be the one we just created. airplanes=[example_airplane], # Set this steady problem's operating point object ot be the one we just created. - operating_point=example_operating_point, ) + operating_point=example_operating_point, +) # Now, the airplane and operating point object exist within the steady problem # object. I like to delete the external pointers to these objects to ease debugging. @@ -167,10 +233,10 @@ # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. -example_solver = (ps.steady_ring_vortex_lattice_method -.SteadyRingVortexLatticeMethodSolver( +example_solver = ps.steady_ring_vortex_lattice_method.SteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - steady_problem=example_problem)) + steady_problem=example_problem +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. Again, this is unnecessary, I just like to do this to ease debugging. @@ -181,13 +247,15 @@ # This parameter determines the detail of information that the solver's logger # will output while running. The options are, in order of detail and severity, # "Debug", "Info", "Warning", "Error", "Critical". The default value is "Warning". - logging_level="Warning", ) + logging_level="Warning", +) # Call this function from the output module to print the results. ps.output.print_steady_results(steady_solver=example_solver) # Call the software's draw function on the solver. -ps.output.draw(solver=example_solver, +ps.output.draw( + solver=example_solver, # Tell the draw function to color the aircraft's wing panels with the local # lift coefficient. The valid arguments for this parameter are None, "induced drag", # "side force", or "lift". @@ -200,7 +268,8 @@ show_wake_vortices=False, # Tell the draw function to not save the drawing as an image file. This way, # the drawing will still be displayed but not saved. This value defaults to false. - save=False, ) + save=False, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/examples/unsteady_ring_vortex_lattice_method_solver_static.py b/examples/unsteady_ring_vortex_lattice_method_solver_static.py index 1133f73e..dcd5ece2 100644 --- a/examples/unsteady_ring_vortex_lattice_method_solver_static.py +++ b/examples/unsteady_ring_vortex_lattice_method_solver_static.py @@ -18,22 +18,28 @@ # program is in SI units. Note: these values are relative to the global # coordinate system fixed front left corner of the first airplane's first wing's # root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, # Give the reference dimensions of this aircraft. "s_ref" is the reference area # in meters squared, "b_ref" is the reference span in meters, and "c_ref" is the # reference chord in meters. I set these values to None, which is their default, # so that they will be populated by the first wing object's calculated # characteristics. Note that the reference area used in this program is the # wetted area of the wing's mean-camberline surface. - s_ref=None, b_ref=None, c_ref=None, # All airplane objects have a list of wings. - wings=[# Create the first wing object in this airplane. - ps.geometry.Wing(# Give the wing a name, this defaults to "Untitled Wing". + s_ref=None, + b_ref=None, + c_ref=None, # All airplane objects have a list of wings. + wings=[ # Create the first wing object in this airplane. + ps.geometry.Wing( # Give the wing a name, this defaults to "Untitled Wing". name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Declare that this wing is symmetric. This means that the geometry will # be reflected across plane of this wing's root wing cross section. Note # that the geometry coordinates are defined as such: If you were riding @@ -49,16 +55,19 @@ # in the chordwise direction, near the leading and trailing edges. The # other option is "uniform". I set this value to "uniform" here as it # increase the accuracy of unsteady solvers. - num_chordwise_panels=6, chordwise_spacing="uniform", + num_chordwise_panels=6, + chordwise_spacing="uniform", # Every wing has a list of wing cross sections. In order for the geometry # output to be sensible, each wing must have at least two wing cross # sections. - wing_cross_sections=[# Create a new wing cross section object. + wing_cross_sections=[ # Create a new wing cross section object. ps.geometry.WingCrossSection( # Define the location of the leading edge of the wing cross # section relative to the wing's leading edge. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Define the twist of the wing cross section in degrees. This is # equivalent to incidence angle of cross section. The twist is # about the leading edge. Note that the twist is only stable up @@ -87,10 +96,11 @@ # which makes the panels relatively finer, in the spanwise # direction, near the cross section ends. The other option is # "uniform". - num_spanwise_panels=8, spanwise_spacing="cosine", + num_spanwise_panels=8, + spanwise_spacing="cosine", # Set the chord of this cross section to be 1.75 meters. This # value defaults to 1.0 meter. - chord=1.75, # Every wing cross section has an airfoil object. + chord=1.75, # Every wing cross section has an airfoil object. airfoil=ps.geometry.Airfoil( # Give the airfoil a name. This defaults to "Untitled # Airfoil". This name should correspond to a name in the @@ -113,24 +123,57 @@ repanel=True, # This is number of points to use if repaneling the airfoil. # It is ignored if the repanel is False. The default is 400. - n_points_per_side=400, ), ), + n_points_per_side=400, + ), + ), # Define the next wing cross section. From here on out, # the declarations will not be as commented as the previous. See the # above comments if you have questions. - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, z_le=1.0, chord=1.5, - twist=5.0, # Give this wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + z_le=1.0, + chord=1.5, + twist=5.0, # Give this wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="V-Tail", x_le=6.75, z_le=0.25, num_chordwise_panels=6, - chordwise_spacing="uniform", symmetric=True, + ps.geometry.Wing( + name="V-Tail", + x_le=6.75, + z_le=0.25, + num_chordwise_panels=6, + chordwise_spacing="uniform", + symmetric=True, # Define this wing's root wing cross section. - wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + # Give the root wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, z_le=1.0, chord=1.0, - twist=-5.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + z_le=1.0, + chord=1.0, + twist=-5.0, # Give the tip wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Now define the main wing's root wing cross section's movement. Cross sections can # move in three ways: sweeping, pitching, and heaving. Sweeping is defined as the @@ -170,33 +213,39 @@ heaving_period=0.0, # Define the time step spacing of the heaving. This is "sine" by default. The # options are "sine" and "uniform". - heaving_spacing="sine", ) + heaving_spacing="sine", +) # Define the main wing's tip wing cross section's movement. As the example has static # geometry, the movement attributes can be excluded, and the default values will # suffice. main_wing_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], ) + base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], +) # Define the v-tail's root wing cross section's movement. As the example has static # geometry, the movement attributes can be excluded, and the default values will # suffice. v_tail_root_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], +) # Define the v-tail's tip wing cross section's movement. As the example has static # geometry, the movement attributes can be excluded, and the default values will # suffice. v_tail_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], +) # Now define the main wing's movement. In addition to their wing cross sections' # relative movements, wings' leading edge positions can move as well. main_wing_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[0], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[main_wing_root_wing_cross_section_movement, - main_wing_tip_wing_cross_section_movement, ], + wing_cross_sections_movements=[ + main_wing_root_wing_cross_section_movement, + main_wing_tip_wing_cross_section_movement, + ], # Define the amplitude of the leading edge's change in x position. This value is # in meters. This is set to 0.0 meters, which is the default value. x_le_amplitude=0.0, @@ -223,7 +272,8 @@ z_le_period=0.0, # Define the time step spacing of the leading edge's change in z position. This # is "sine" by default. The options are "sine" and "uniform". - z_le_spacing="sine", ) + z_le_spacing="sine", +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -232,11 +282,14 @@ del main_wing_tip_wing_cross_section_movement # Make the v-tail's wing movement object. -v_tail_movement = ps.movement.WingMovement(# Define the base wing object. +v_tail_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[1], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[v_tail_root_wing_cross_section_movement, - v_tail_tip_wing_cross_section_movement, ], ) + wing_cross_sections_movements=[ + v_tail_root_wing_cross_section_movement, + v_tail_tip_wing_cross_section_movement, + ], +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -246,8 +299,8 @@ # Now define the airplane's movement object. In addition to their wing's and wing # cross sections' relative movements, airplane's reference positions can move as well. -airplane_movement = ps.movement.AirplaneMovement(# Define the base airplane object. - base_airplane=example_airplane, # Add the list of wing movement objects. +airplane_movement = ps.movement.AirplaneMovement( # Define the base airplane object. + base_airplane=example_airplane, # Add the list of wing movement objects. wing_movements=[main_wing_movement, v_tail_movement], # Define the amplitude of the reference position's change in x position. This # value is in meters. This is set to 0.0 meters, which is the default value. @@ -275,7 +328,8 @@ z_ref_period=0.0, # Define the time step spacing of the reference position's change in z position. # This is "sine" by default. The options are "sine" and "uniform". - z_ref_spacing="sine", ) + z_ref_spacing="sine", +) # Delete the extraneous wing movement objects, as these are now contained within the # airplane movement object. @@ -300,7 +354,8 @@ # Define the kinematic viscosity of the air in meters squared per second. This # defaults to 15.06e-6 meters squared per second, which corresponds to an air # temperature of 20 degrees Celsius. - nu=15.06e-6, ) + nu=15.06e-6, +) # Define the operating point's movement. The operating point's velocity can change # with respect to time. @@ -315,12 +370,13 @@ velocity_period=0.0, # Define the time step spacing of the velocity's change in time. This is "sine" # by default. The options are "sine" and "uniform". - velocity_spacing="sine", ) + velocity_spacing="sine", +) # Define the movement object. This contains the airplane movement and the operating # point movement. -movement = ps.movement.Movement(# Add the airplane movement. - airplane_movements=[airplane_movement], # Add the operating point movement. +movement = ps.movement.Movement( # Add the airplane movement. + airplane_movements=[airplane_movement], # Add the operating point movement. operating_point_movement=operating_point_movement, # Leave the number of time steps and the length of each time step unspecified. # The solver will automatically set the length of the time steps so that the wake @@ -329,7 +385,9 @@ # the number of steps will be set such that the wake extends ten chord lengths # back from the main wing. If the geometry isn't static, the number of steps will # be set such that three periods of the slowest movement oscillation complete. - num_steps=None, delta_time=None, ) + num_steps=None, + delta_time=None, +) # Delete the extraneous airplane and operating point movement objects, as these are # now contained within the movement object. @@ -337,15 +395,17 @@ del operating_point_movement # Define the unsteady example problem. -example_problem = ps.problems.UnsteadyProblem(movement=movement, ) +example_problem = ps.problems.UnsteadyProblem( + movement=movement, +) # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. -example_solver = (ps.unsteady_ring_vortex_lattice_method -.UnsteadyRingVortexLatticeMethodSolver( +example_solver = ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - unsteady_problem=example_problem, )) + unsteady_problem=example_problem, +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. @@ -358,11 +418,12 @@ # "Debug", "Info", "Warning", "Error", "Critical". The default value is "Warning". logging_level="Warning", # Use a prescribed wake model. This is faster, but may be slightly less accurate. - prescribed_wake=True, ) + prescribed_wake=True, +) # Call the software's draw function on the solver. Press "q" to close the plotter # after it draws the output. -ps.output.draw(# Set the solver to the one we just ran. +ps.output.draw( # Set the solver to the one we just ran. solver=example_solver, # Tell the draw function to color the aircraft's wing panels with the local lift # coefficient. The valid arguments for this parameter are None, "induced drag", @@ -376,12 +437,13 @@ show_wake_vortices=False, # Tell the draw function to not save the drawing as an image file. This way, # the drawing will still be displayed but not saved. This value defaults to False. - save=False, ) + save=False, +) # Call the software's animate function on the solver. This produces a GIF of the wake # being shed. The GIF is saved in the same directory as this script. Press "q", # after orienting the view, to begin the animation. -ps.output.animate(# Set the unsteady solver to the one we just ran. +ps.output.animate( # Set the unsteady solver to the one we just ran. unsteady_solver=example_solver, # Tell the animate function to color the aircraft's wing panels with the local # lift coefficient. The valid arguments for this parameter are None, "induced drag", @@ -393,18 +455,21 @@ # Tell the animate function to not save the animation as file. This way, # the animation will still be displayed but not saved. This value defaults to # False. - save=False, ) + save=False, +) # Call the software's plotting function on the solver. This produces graphs of the # output forces and moments with respect to time. -ps.output.plot_results_versus_time(# Set the unsteady solver to the one we just ran. +ps.output.plot_results_versus_time( # Set the unsteady solver to the one we just ran. unsteady_solver=example_solver, # Set the show attribute to True, which is the default value. With this set to # show, some IDEs (such as PyCharm in "Scientific Mode") will display the plots # in a sidebar. Other IDEs may not display the plots, in which case you should # set the save attribute to True, and open the files after they've been saved to # the current directory. - show=True, save=False, ) + show=True, + save=False, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/examples/unsteady_ring_vortex_lattice_method_solver_variable.py b/examples/unsteady_ring_vortex_lattice_method_solver_variable.py index f103ef46..128e647d 100644 --- a/examples/unsteady_ring_vortex_lattice_method_solver_variable.py +++ b/examples/unsteady_ring_vortex_lattice_method_solver_variable.py @@ -18,22 +18,28 @@ # program is in SI units. Note: these values are relative to the global # coordinate system fixed front left corner of the first airplane's first wing's # root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, # Give the reference dimensions of this aircraft. "s_ref" is the reference area # in meters squared, "b_ref" is the reference span in meters, and "c_ref" is the # reference chord in meters. I set these values to None, which is their default, # so that they will be populated by the first wing object's calculated # characteristics. Note that the reference area used in this program is the # wetted area of the wing's mean-camberline surface. - s_ref=None, b_ref=None, c_ref=None, # All airplane objects have a list of wings. - wings=[# Create the first wing object in this airplane. + s_ref=None, + b_ref=None, + c_ref=None, # All airplane objects have a list of wings. + wings=[ # Create the first wing object in this airplane. ps.geometry.Wing( # Give the wing a name, this defaults to "Untitled Wing". name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Declare that this wing is symmetric. This means that the geometry will # be reflected across plane of this wing's root wing cross section. Note # that the geometry coordinates are defined as such: If you were riding @@ -49,16 +55,19 @@ # in the chordwise direction, near the leading and trailing edges. The # other option is "uniform". I set this value to "uniform" here as it # increase the accuracy of unsteady solvers. - num_chordwise_panels=6, chordwise_spacing="uniform", + num_chordwise_panels=6, + chordwise_spacing="uniform", # Every wing has a list of wing cross sections. In order for the geometry # output to be sensible, each wing must have at least two wing cross # sections. - wing_cross_sections=[# Create a new wing cross section object. + wing_cross_sections=[ # Create a new wing cross section object. ps.geometry.WingCrossSection( # Define the location of the leading edge of the wing cross # section relative to the wing's leading edge. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Define the twist of the wing cross section in degrees. This is # equivalent to incidence angle of cross section. The twist is # about the leading edge. Note that the twist is only stable up @@ -87,10 +96,11 @@ # which makes the panels relatively finer, in the spanwise # direction, near the cross section ends. The other option is # "uniform". - num_spanwise_panels=8, spanwise_spacing="cosine", + num_spanwise_panels=8, + spanwise_spacing="cosine", # Set the chord of this cross section to be 1.75 meters. This # value defaults to 1.0 meter. - chord=1.75, # Every wing cross section has an airfoil object. + chord=1.75, # Every wing cross section has an airfoil object. airfoil=ps.geometry.Airfoil( # Give the airfoil a name. This defaults to "Untitled # Airfoil". This name should correspond to a name in the @@ -113,24 +123,57 @@ repanel=True, # This is number of points to use if repaneling the airfoil. # It is ignored if the repanel is False. The default is 400. - n_points_per_side=400, ), ), + n_points_per_side=400, + ), + ), # Define the next wing cross section. From here on out, # the declarations will not be as commented as the previous. See the # above comments if you have questions. - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, z_le=1.0, chord=1.5, - twist=5.0, # Give this wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + z_le=1.0, + chord=1.5, + twist=5.0, # Give this wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="V-Tail", x_le=6.75, z_le=0.25, num_chordwise_panels=6, - chordwise_spacing="uniform", symmetric=True, + ps.geometry.Wing( + name="V-Tail", + x_le=6.75, + z_le=0.25, + num_chordwise_panels=6, + chordwise_spacing="uniform", + symmetric=True, # Define this wing's root wing cross section. - wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + # Give the root wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, z_le=1.0, chord=1.0, - twist=-5.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + z_le=1.0, + chord=1.0, + twist=-5.0, # Give the tip wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Now define the main wing's root wing cross section's movement. Cross sections can # move in three ways: sweeping, pitching, and heaving. Sweeping is defined as the @@ -170,32 +213,44 @@ heaving_period=0.0, # Define the time step spacing of the heaving. This is "sine" by default. The # options are "sine" and "uniform". - heaving_spacing="sine", ) + heaving_spacing="sine", +) # Define the main wing's tip wing cross section's movement. main_wing_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=30.0, sweeping_period=1.0, sweeping_spacing="sine", - pitching_amplitude=15.0, pitching_period=1.0, pitching_spacing="sine", - heaving_amplitude=0.0, heaving_period=0.0, heaving_spacing="sine", ) + sweeping_amplitude=30.0, + sweeping_period=1.0, + sweeping_spacing="sine", + pitching_amplitude=15.0, + pitching_period=1.0, + pitching_spacing="sine", + heaving_amplitude=0.0, + heaving_period=0.0, + heaving_spacing="sine", +) # Define the v-tail's root wing cross section's movement. This wing will be static, # so the movement attributes can be excluded, and the default values will suffice. v_tail_root_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], +) # Define the v-tail's root wing cross section's movement. This wing will be static, # so the movement attributes can be excluded, and the default values will suffice. v_tail_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], +) # Now define the main wing's movement. In addition to their wing cross sections' # relative movements, wings' leading edge positions can move as well. main_wing_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[0], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[main_wing_root_wing_cross_section_movement, - main_wing_tip_wing_cross_section_movement, ], + wing_cross_sections_movements=[ + main_wing_root_wing_cross_section_movement, + main_wing_tip_wing_cross_section_movement, + ], # Define the amplitude of the leading edge's change in x position. This value is # in meters. This is set to 0.0 meters, which is the default value. x_le_amplitude=0.0, @@ -222,7 +277,8 @@ z_le_period=0.0, # Define the time step spacing of the leading edge's change in z position. This # is "sine" by default. The options are "sine" and "uniform". - z_le_spacing="sine", ) + z_le_spacing="sine", +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -231,11 +287,14 @@ del main_wing_tip_wing_cross_section_movement # Make the v-tail's wing movement object. -v_tail_movement = ps.movement.WingMovement(# Define the base wing object. +v_tail_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[1], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[v_tail_root_wing_cross_section_movement, - v_tail_tip_wing_cross_section_movement, ], ) + wing_cross_sections_movements=[ + v_tail_root_wing_cross_section_movement, + v_tail_tip_wing_cross_section_movement, + ], +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -245,8 +304,8 @@ # Now define the airplane's movement object. In addition to their wing's and wing # cross sections' relative movements, airplane's reference positions can move as well. -airplane_movement = ps.movement.AirplaneMovement(# Define the base airplane object. - base_airplane=example_airplane, # Add the list of wing movement objects. +airplane_movement = ps.movement.AirplaneMovement( # Define the base airplane object. + base_airplane=example_airplane, # Add the list of wing movement objects. wing_movements=[main_wing_movement, v_tail_movement], # Define the amplitude of the reference position's change in x position. This # value is in meters. This is set to 0.0 meters, which is the default value. @@ -274,7 +333,8 @@ z_ref_period=0.0, # Define the time step spacing of the reference position's change in z position. # This is "sine" by default. The options are "sine" and "uniform". - z_ref_spacing="sine", ) + z_ref_spacing="sine", +) # Delete the extraneous wing movement objects, as these are now contained within the # airplane movement object. @@ -299,7 +359,8 @@ # Define the kinematic viscosity of the air in meters squared per second. This # defaults to 15.06e-6 meters squared per second, which corresponds to an air # temperature of 20 degrees Celsius. - nu=15.06e-6, ) + nu=15.06e-6, +) # Define the operating point's movement. The operating point's velocity can change # with respect to time. @@ -314,12 +375,13 @@ velocity_period=0.0, # Define the time step spacing of the velocity's change in time. This is "sine" # by default. The options are "sine" and "uniform". - velocity_spacing="sine", ) + velocity_spacing="sine", +) # Define the movement object. This contains the airplane movement and the operating # point movement. -movement = ps.movement.Movement(# Add the airplane movement. - airplane_movements=[airplane_movement], # Add the operating point movement. +movement = ps.movement.Movement( # Add the airplane movement. + airplane_movements=[airplane_movement], # Add the operating point movement. operating_point_movement=operating_point_movement, # Leave the number of time steps and the length of each time step unspecified. # The solver will automatically set the length of the time steps so that the wake @@ -328,7 +390,9 @@ # the number of steps will be set such that the wake extends ten chord lengths # back from the main wing. If the geometry isn't static, the number of steps will # be set such that three periods of the slowest movement oscillation complete. - num_steps=None, delta_time=None, ) + num_steps=None, + delta_time=None, +) # Delete the extraneous airplane and operating point movement objects, as these are # now contained within the movement object. @@ -336,15 +400,17 @@ del operating_point_movement # Define the unsteady example problem. -example_problem = ps.problems.UnsteadyProblem(movement=movement, ) +example_problem = ps.problems.UnsteadyProblem( + movement=movement, +) # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. -example_solver = (ps.unsteady_ring_vortex_lattice_method -.UnsteadyRingVortexLatticeMethodSolver( +example_solver = ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - unsteady_problem=example_problem, )) + unsteady_problem=example_problem, +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. @@ -357,12 +423,13 @@ # "Debug", "Info", "Warning", "Error", "Critical". The default value is "Warning". logging_level="Warning", # Use a prescribed wake model. This is faster, but may be slightly less accurate. - prescribed_wake=True, ) + prescribed_wake=True, +) # Call the software's animate function on the solver. This produces a GIF of the wake # being shed. The GIF is saved in the same directory as this script. Press "q", # after orienting the view, to begin the animation. -ps.output.animate(# Set the unsteady solver to the one we just ran. +ps.output.animate( # Set the unsteady solver to the one we just ran. unsteady_solver=example_solver, # Tell the animate function to color the aircraft's wing panels with the local # lift coefficient. The valid arguments for this parameter are None, "induced drag", @@ -374,7 +441,8 @@ # Tell the animate function to not save the animation as file. This way, # the animation will still be displayed but not saved. This value defaults to # False. - save=False, ) + save=False, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/examples/unsteady_ring_vortex_lattice_method_solver_variable_formation.py b/examples/unsteady_ring_vortex_lattice_method_solver_variable_formation.py index 01ec8f12..89ea3d0c 100644 --- a/examples/unsteady_ring_vortex_lattice_method_solver_variable_formation.py +++ b/examples/unsteady_ring_vortex_lattice_method_solver_variable_formation.py @@ -10,127 +10,248 @@ y_spacing = 13 # Create the lead airplane object. -lead_airplane = ps.geometry.Airplane(name="Lead Airplane", +lead_airplane = ps.geometry.Airplane( + name="Lead Airplane", # Specify the location of the lead airplane's center of gravity. This is the # point around about which the solver will calculate the moments on the airplane. # These three values default to 0.0 meters. Note: these values are relative to # the global coordinate system fixed front left corner of the first airplane's # first wing's root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, wings=[ps.geometry.Wing(name="Main Wing", - # Define the location of the leading edge of the wing relative to the - # global coordinate system fixed front left corner of the first - # airplane's first wing's root wing cross section. - x_le=0.0, y_le=0.0, - # Declare that this wing is symmetric. This means that the geometry will - # be reflected across plane of this wing's root wing cross section. Note - # that the geometry coordinates are defined as such: If you were riding - # in the airplane, the positive x direction would point behind you, - # the positive y direction would point out of your right wing, and the - # positive z direction would point upwards, out of your chair. These - # directions form a right-handed coordinate system. The default value of - # "symmetric" is false. - symmetric=True, - # Define the chordwise spacing of the wing panels to be "uniform" as this - # increase the accuracy of unsteady solvers. - chordwise_spacing="uniform", num_chordwise_panels=4, wing_cross_sections=[ - ps.geometry.WingCrossSection( - # Define the location of the leading edge of the wing cross - # section relative to the wing's leading edge. These values all - # default to 0.0 meters. - x_le=0.0, y_le=0.0, - # Assign the twist of this wing cross section. Note: when - # assigning angles of attack to multiple airplanes, it is better - # to set the operating point's angle of attack to zero, and then - # use offset the twist values of all the wing cross sections to - # simulate each aircraft having an angle of attack. - twist=5.0, chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, chord=1.5, twist=5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, + wings=[ + ps.geometry.Wing( + name="Main Wing", + # Define the location of the leading edge of the wing relative to the + # global coordinate system fixed front left corner of the first + # airplane's first wing's root wing cross section. + x_le=0.0, + y_le=0.0, + # Declare that this wing is symmetric. This means that the geometry will + # be reflected across plane of this wing's root wing cross section. Note + # that the geometry coordinates are defined as such: If you were riding + # in the airplane, the positive x direction would point behind you, + # the positive y direction would point out of your right wing, and the + # positive z direction would point upwards, out of your chair. These + # directions form a right-handed coordinate system. The default value of + # "symmetric" is false. + symmetric=True, + # Define the chordwise spacing of the wing panels to be "uniform" as this + # increase the accuracy of unsteady solvers. + chordwise_spacing="uniform", + num_chordwise_panels=4, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + # Define the location of the leading edge of the wing cross + # section relative to the wing's leading edge. These values all + # default to 0.0 meters. + x_le=0.0, + y_le=0.0, + # Assign the twist of this wing cross section. Note: when + # assigning angles of attack to multiple airplanes, it is better + # to set the operating point's angle of attack to zero, and then + # use offset the twist values of all the wing cross sections to + # simulate each aircraft having an angle of attack. + twist=5.0, + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Now define the lead airplane's movement object. -lead_airplane_movement = ps.movement.AirplaneMovement(base_airplane=lead_airplane, - wing_movements=[# Define the main wing's movement. - ps.movement.WingMovement(base_wing=lead_airplane.wings[0], +lead_airplane_movement = ps.movement.AirplaneMovement( + base_airplane=lead_airplane, + wing_movements=[ # Define the main wing's movement. + ps.movement.WingMovement( + base_wing=lead_airplane.wings[0], # Add the list of wing cross section movement objects. wing_cross_sections_movements=[ # Define the root wing cross section's movement object. ps.movement.WingCrossSectionMovement( base_wing_cross_section=lead_airplane.wings[0].wing_cross_sections[ - 0], ), # Define the tip wing cross section's movement object. + 0 + ], + ), # Define the tip wing cross section's movement object. ps.movement.WingCrossSectionMovement( base_wing_cross_section=lead_airplane.wings[0].wing_cross_sections[ - 1], sweeping_amplitude=15.0, sweeping_period=1.5, - sweeping_spacing="sine", ), ], ), ], ) + 1 + ], + sweeping_amplitude=15.0, + sweeping_period=1.5, + sweeping_spacing="sine", + ), + ], + ), + ], +) # Create the trailing right airplane object. -right_airplane = ps.geometry.Airplane(name="Right Airplane", +right_airplane = ps.geometry.Airplane( + name="Right Airplane", # Specify the location of the right airplane's center of gravity. This is the # point around about which the solver will calculate the moments on the airplane. # These three values default to 0.0 meters. Note: these values are relative to # the global coordinate system fixed front left corner of the first airplane's # first wing's root wing cross section. - x_ref=x_spacing, y_ref=y_spacing, z_ref=0.0, wings=[ - ps.geometry.Wing(name="Main Wing", + x_ref=x_spacing, + y_ref=y_spacing, + z_ref=0.0, + wings=[ + ps.geometry.Wing( + name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. - x_le=x_spacing, y_le=y_spacing, symmetric=True, chordwise_spacing="uniform", - num_chordwise_panels=4, wing_cross_sections=[ - ps.geometry.WingCrossSection(twist=5.0, chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, chord=1.5, twist=5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + x_le=x_spacing, + y_le=y_spacing, + symmetric=True, + chordwise_spacing="uniform", + num_chordwise_panels=4, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + twist=5.0, + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Now define the trailing right airplane's movement object. -right_airplane_movement = ps.movement.AirplaneMovement(base_airplane=right_airplane, - wing_movements=[ps.movement.WingMovement(base_wing=right_airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section=right_airplane.wings[0].wing_cross_sections[0], ), - ps.movement.WingCrossSectionMovement( - base_wing_cross_section=right_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1.5, - sweeping_spacing="sine", ), ], ), ], ) +right_airplane_movement = ps.movement.AirplaneMovement( + base_airplane=right_airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=right_airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=right_airplane.wings[0].wing_cross_sections[ + 0 + ], + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=right_airplane.wings[0].wing_cross_sections[ + 1 + ], + sweeping_amplitude=15.0, + sweeping_period=1.5, + sweeping_spacing="sine", + ), + ], + ), + ], +) # Create the trailing left airplane object. -left_airplane = ps.geometry.Airplane(name="Left Airplane", +left_airplane = ps.geometry.Airplane( + name="Left Airplane", # Specify the location of the left airplane's center of gravity. This is the # point around about which the solver will calculate the moments on the airplane. # These three values default to 0.0 meters. Note: these values are relative to # the global coordinate system fixed front left corner of the first airplane's # first wing's root wing cross section. - x_ref=x_spacing, y_ref=-y_spacing, z_ref=0.0, wings=[ - ps.geometry.Wing(name="Main Wing", + x_ref=x_spacing, + y_ref=-y_spacing, + z_ref=0.0, + wings=[ + ps.geometry.Wing( + name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. - x_le=x_spacing, y_le=-y_spacing, symmetric=True, - chordwise_spacing="uniform", num_chordwise_panels=4, wing_cross_sections=[ - ps.geometry.WingCrossSection(twist=5.0, chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, chord=1.5, twist=5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + x_le=x_spacing, + y_le=-y_spacing, + symmetric=True, + chordwise_spacing="uniform", + num_chordwise_panels=4, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + twist=5.0, + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Now define the trailing left airplane's movement object. -left_airplane_movement = ps.movement.AirplaneMovement(base_airplane=left_airplane, - wing_movements=[ps.movement.WingMovement(base_wing=left_airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section=left_airplane.wings[0].wing_cross_sections[0], ), - ps.movement.WingCrossSectionMovement( - base_wing_cross_section=left_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1.5, - sweeping_spacing="sine", ), ], ), ], ) +left_airplane_movement = ps.movement.AirplaneMovement( + base_airplane=left_airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=left_airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=left_airplane.wings[0].wing_cross_sections[ + 0 + ], + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=left_airplane.wings[0].wing_cross_sections[ + 1 + ], + sweeping_amplitude=15.0, + sweeping_period=1.5, + sweeping_spacing="sine", + ), + ], + ), + ], +) # Define a new operating point object. This defines the state at which all the # airplanes objects are operating. Note: when assigning angles of attack to multiple # airplanes, it is better to set the operating point's angle of attack to zero, # and then use offset the twist values of all the wing cross sections to simulate # each aircraft having an angle of attack. -operating_point = ps.operating_point.OperatingPoint(velocity=10.0, alpha=0.0, ) +operating_point = ps.operating_point.OperatingPoint( + velocity=10.0, + alpha=0.0, +) # Define the operating point's movement. operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=operating_point, ) + base_operating_point=operating_point, +) # Delete the extraneous airplane and operating point objects, as these are now # contained within their respective movement objects. @@ -142,9 +263,14 @@ # Define the movement object. This contains each airplane's movement and the operating # point movement. movement = ps.movement.Movement( - airplane_movements=[lead_airplane_movement, right_airplane_movement, - left_airplane_movement, ], operating_point_movement=operating_point_movement, - num_cycles=2, ) + airplane_movements=[ + lead_airplane_movement, + right_airplane_movement, + left_airplane_movement, + ], + operating_point_movement=operating_point_movement, + num_cycles=2, +) # Delete the extraneous airplane and operating point movement objects, as these are # now contained within the movement object. @@ -154,30 +280,39 @@ del operating_point_movement # Define the unsteady example problem. -problem = ps.problems.UnsteadyProblem(movement=movement, ) +problem = ps.problems.UnsteadyProblem( + movement=movement, +) # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. solver = ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - unsteady_problem=problem, ) + unsteady_problem=problem, +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. del problem # Run the example solver. -solver.run(prescribed_wake=False, ) +solver.run( + prescribed_wake=False, +) # Call the software's animate function on the solver. This produces a WebP animation # of the wake being shed. The animation is saved in the same directory as this # script. Press "q", after orienting the view, to begin the animation. -ps.output.animate(unsteady_solver=solver, scalar_type="lift", show_wake_vortices=True, +ps.output.animate( + unsteady_solver=solver, + scalar_type="lift", + show_wake_vortices=True, # Tell the animate function to not save the animation as file. This way, # the animation will still be displayed but not saved. This value defaults to # false. - save=True, ) + save=True, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/examples/unsteady_static_convergence_example.py b/examples/unsteady_static_convergence_example.py index 1c1a9a6f..0524e291 100644 --- a/examples/unsteady_static_convergence_example.py +++ b/examples/unsteady_static_convergence_example.py @@ -6,30 +6,64 @@ # Create an airplane and airplane movement object. Read through the unsteady solver # examples for more details on creating this object. -airplane = ps.geometry.Airplane(wings=[ - ps.geometry.Wing(symmetric=True, chordwise_spacing="uniform", wing_cross_sections=[ - ps.geometry.WingCrossSection(x_le=0.0, y_le=0.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), - spanwise_spacing="uniform", ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=3.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), ], ) -airplane_movement = ps.movement.AirplaneMovement(base_airplane=airplane, - wing_movements=[ps.movement.WingMovement(base_wing=airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section=airplane.wings[0].wing_cross_sections[0], ), - ps.movement.WingCrossSectionMovement( - base_wing_cross_section=airplane.wings[0].wing_cross_sections[ - 1], ), ], )], ) +airplane = ps.geometry.Airplane( + wings=[ + ps.geometry.Wing( + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=0.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + spanwise_spacing="uniform", + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=3.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), + ], +) +airplane_movement = ps.movement.AirplaneMovement( + base_airplane=airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=airplane.wings[0].wing_cross_sections[0], + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=airplane.wings[0].wing_cross_sections[1], + ), + ], + ) + ], +) # Create an operating point and an operating point movement object. operating_point = ps.operating_point.OperatingPoint() operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=operating_point) + base_operating_point=operating_point +) # Create a movement object from the airplane movement and operating point movement # objects. -movement = ps.movement.Movement(airplane_movements=[airplane_movement], - operating_point_movement=operating_point_movement, ) +movement = ps.movement.Movement( + airplane_movements=[airplane_movement], + operating_point_movement=operating_point_movement, +) del airplane_movement del operating_point_movement @@ -46,9 +80,15 @@ # absolute percent error), it will return the parameters it found to result in a # converged solution. See the analyze_unsteady_convergence function docstring for # more details. The progress and results are displayed to the console. -ps.convergence.analyze_unsteady_convergence(ref_problem=problem, prescribed_wake=True, - free_wake=True, num_chords_bounds=(5, 8), panel_aspect_ratio_bounds=(2, 1), - num_chordwise_panels_bounds=(3, 6), convergence_criteria=1.0, ) +ps.convergence.analyze_unsteady_convergence( + ref_problem=problem, + prescribed_wake=True, + free_wake=True, + num_chords_bounds=(5, 8), + panel_aspect_ratio_bounds=(2, 1), + num_chordwise_panels_bounds=(3, 6), + convergence_criteria=1.0, +) # Check the console that the convergence analysis found that the solution converged # with the following parameters: diff --git a/examples/unsteady_variable_convergence_example.py b/examples/unsteady_variable_convergence_example.py index 7f51e7fa..1b0ba048 100644 --- a/examples/unsteady_variable_convergence_example.py +++ b/examples/unsteady_variable_convergence_example.py @@ -6,30 +6,66 @@ # Create an airplane and airplane movement object. Read through the unsteady solver # examples for more details on creating this object. -airplane = ps.geometry.Airplane(wings=[ - ps.geometry.Wing(symmetric=True, chordwise_spacing="uniform", wing_cross_sections=[ - ps.geometry.WingCrossSection(x_le=0.0, y_le=0.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), - spanwise_spacing="uniform", ), - ps.geometry.WingCrossSection(x_le=0.0, y_le=3.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), ], ) -airplane_movement = ps.movement.AirplaneMovement(base_airplane=airplane, - wing_movements=[ps.movement.WingMovement(base_wing=airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section=airplane.wings[0].wing_cross_sections[0], ), - ps.movement.WingCrossSectionMovement( - base_wing_cross_section=airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1.0, ), ], )], ) +airplane = ps.geometry.Airplane( + wings=[ + ps.geometry.Wing( + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=0.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + spanwise_spacing="uniform", + ), + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=3.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), + ], +) +airplane_movement = ps.movement.AirplaneMovement( + base_airplane=airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=airplane.wings[0].wing_cross_sections[0], + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=airplane.wings[0].wing_cross_sections[1], + sweeping_amplitude=15.0, + sweeping_period=1.0, + ), + ], + ) + ], +) # Create an operating point and an operating point movement object. operating_point = ps.operating_point.OperatingPoint() operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=operating_point) + base_operating_point=operating_point +) # Create a movement object from the airplane movement and operating point movement # objects. -movement = ps.movement.Movement(airplane_movements=[airplane_movement], - operating_point_movement=operating_point_movement, ) +movement = ps.movement.Movement( + airplane_movements=[airplane_movement], + operating_point_movement=operating_point_movement, +) del airplane_movement del operating_point_movement @@ -46,9 +82,15 @@ # absolute percent error), it will return the parameters it found to result in a # converged solution. See the analyze_unsteady_convergence function docstring for # more details. The progress and results are displayed to the console. -ps.convergence.analyze_unsteady_convergence(ref_problem=problem, prescribed_wake=True, - free_wake=True, num_cycles_bounds=(1, 4), panel_aspect_ratio_bounds=(2, 1), - num_chordwise_panels_bounds=(4, 7), convergence_criteria=1.0, ) +ps.convergence.analyze_unsteady_convergence( + ref_problem=problem, + prescribed_wake=True, + free_wake=True, + num_cycles_bounds=(1, 4), + panel_aspect_ratio_bounds=(2, 1), + num_chordwise_panels_bounds=(4, 7), + convergence_criteria=1.0, +) # Check the console that the convergence analysis found that the solution converged # with the following parameters: diff --git a/formation flight/formation_flight.py b/formation flight/formation_flight.py index c5cb6ee4..efa7a205 100644 --- a/formation flight/formation_flight.py +++ b/formation flight/formation_flight.py @@ -28,9 +28,13 @@ root_to_mid_chord = root_chord mid_to_tip_chord = (root_chord + tip_chord) / 2 -this_operating_point = ps.operating_point.OperatingPoint(velocity=speed, alpha=0.0, ) +this_operating_point = ps.operating_point.OperatingPoint( + velocity=speed, + alpha=0.0, +) this_operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=this_operating_point, ) + base_operating_point=this_operating_point, +) del this_operating_point print("Prescribed Wake:", prescribed_wake) @@ -41,7 +45,8 @@ mid_to_tip_panel_chord = mid_to_tip_chord / num_chord root_to_mid_num_span = round( - root_to_mid_span / (aspect_ratio * root_to_mid_panel_chord)) + root_to_mid_span / (aspect_ratio * root_to_mid_panel_chord) +) mid_to_tip_num_span = round(mid_to_tip_span / (aspect_ratio * mid_to_tip_panel_chord)) these_airplane_movements = [] @@ -65,66 +70,126 @@ offset = row - 1 - this_airplane = ps.geometry.Airplane(name=this_name, x_ref=offset * x_spacing, - y_ref=offset_sign * offset * y_spacing, wings=[ - ps.geometry.Wing(name="Main Wing", symmetric=True, - chordwise_spacing="uniform", x_le=offset * x_spacing, - y_le=offset_sign * offset * y_spacing, num_chordwise_panels=num_chord, + this_airplane = ps.geometry.Airplane( + name=this_name, + x_ref=offset * x_spacing, + y_ref=offset_sign * offset * y_spacing, + wings=[ + ps.geometry.Wing( + name="Main Wing", + symmetric=True, + chordwise_spacing="uniform", + x_le=offset * x_spacing, + y_le=offset_sign * offset * y_spacing, + num_chordwise_panels=num_chord, wing_cross_sections=[ - ps.geometry.WingCrossSection(twist=alpha, chord=root_chord, + ps.geometry.WingCrossSection( + twist=alpha, + chord=root_chord, airfoil=ps.geometry.Airfoil(name="naca0012"), num_spanwise_panels=root_to_mid_num_span, - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(twist=alpha, y_le=root_to_mid_span, - chord=root_chord, airfoil=ps.geometry.Airfoil(name="naca0012"), + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + twist=alpha, + y_le=root_to_mid_span, + chord=root_chord, + airfoil=ps.geometry.Airfoil(name="naca0012"), num_spanwise_panels=mid_to_tip_num_span, - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(twist=alpha, - y_le=root_to_mid_span + mid_to_tip_span, chord=tip_chord, - airfoil=ps.geometry.Airfoil(name="naca0012"), ), ], ), ], ) - - this_airplane_movement = ps.movement.AirplaneMovement(base_airplane=this_airplane, - wing_movements=[ps.movement.WingMovement(base_wing=this_airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section=this_airplane.wings[0].wing_cross_sections[ - 0], ), ps.movement.WingCrossSectionMovement( - base_wing_cross_section=this_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=flapping_amplitude, sweeping_period=period, - sweeping_spacing="sine", ), ps.movement.WingCrossSectionMovement( - base_wing_cross_section=this_airplane.wings[0].wing_cross_sections[2], - sweeping_amplitude=flapping_amplitude, sweeping_period=period, - sweeping_spacing="sine", ), ], )], ) + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + twist=alpha, + y_le=root_to_mid_span + mid_to_tip_span, + chord=tip_chord, + airfoil=ps.geometry.Airfoil(name="naca0012"), + ), + ], + ), + ], + ) + + this_airplane_movement = ps.movement.AirplaneMovement( + base_airplane=this_airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=this_airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=this_airplane.wings[ + 0 + ].wing_cross_sections[0], + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=this_airplane.wings[ + 0 + ].wing_cross_sections[1], + sweeping_amplitude=flapping_amplitude, + sweeping_period=period, + sweeping_spacing="sine", + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=this_airplane.wings[ + 0 + ].wing_cross_sections[2], + sweeping_amplitude=flapping_amplitude, + sweeping_period=period, + sweeping_spacing="sine", + ), + ], + ) + ], + ) these_airplane_movements.append(this_airplane_movement) del this_airplane del this_airplane_movement -this_movement = ps.movement.Movement(airplane_movements=these_airplane_movements, - operating_point_movement=this_operating_point_movement, num_steps=None, - num_cycles=num_flaps, delta_time=None, ) +this_movement = ps.movement.Movement( + airplane_movements=these_airplane_movements, + operating_point_movement=this_operating_point_movement, + num_steps=None, + num_cycles=num_flaps, + delta_time=None, +) del these_airplane_movements -this_problem = ps.problems.UnsteadyProblem(movement=this_movement, - only_final_results=False, ) +this_problem = ps.problems.UnsteadyProblem( + movement=this_movement, + only_final_results=False, +) del this_movement this_solver = ( ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( - unsteady_problem=this_problem, )) + unsteady_problem=this_problem, + ) +) del this_problem -this_solver.run(prescribed_wake=prescribed_wake, calculate_streamlines=False, ) +this_solver.run( + prescribed_wake=prescribed_wake, + calculate_streamlines=False, +) ps.output.print_unsteady_results(unsteady_solver=this_solver) ps.output.plot_results_versus_time(unsteady_solver=this_solver, save=True) -ps.output.draw(solver=this_solver, scalar_type="lift", show_wake_vortices=True, - save=True, ) - -ps.output.animate(unsteady_solver=this_solver, scalar_type="lift", - show_wake_vortices=True, save=True, ) +ps.output.draw( + solver=this_solver, + scalar_type="lift", + show_wake_vortices=True, + save=True, +) + +ps.output.animate( + unsteady_solver=this_solver, + scalar_type="lift", + show_wake_vortices=True, + save=True, +) diff --git a/formation flight/formation_flight_convergence.py b/formation flight/formation_flight_convergence.py index 7693ef4d..c22a93ef 100644 --- a/formation flight/formation_flight_convergence.py +++ b/formation flight/formation_flight_convergence.py @@ -43,9 +43,11 @@ num_chord_list = [i for i in range(min_num_chord, max_num_chord + 1)] rms_lifts = np.zeros( - (len(wake_state_list), len(num_flaps_list), len(num_chord_list), num_airplanes)) + (len(wake_state_list), len(num_flaps_list), len(num_chord_list), num_airplanes) +) rms_drags = np.zeros( - (len(wake_state_list), len(num_flaps_list), len(num_chord_list), num_airplanes)) + (len(wake_state_list), len(num_flaps_list), len(num_chord_list), num_airplanes) +) iter_times = np.zeros((len(wake_state_list), len(num_flaps_list), len(num_chord_list))) iteration = 0 @@ -65,9 +67,13 @@ wake_saturated = None this_solver = None -this_operating_point = ps.operating_point.OperatingPoint(velocity=speed, alpha=0.0, ) +this_operating_point = ps.operating_point.OperatingPoint( + velocity=speed, + alpha=0.0, +) this_operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=this_operating_point, ) + base_operating_point=this_operating_point, +) del this_operating_point for wake_state_id, prescribed_wake in enumerate(wake_state_list): @@ -84,9 +90,11 @@ mid_to_tip_panel_chord = mid_to_tip_chord / num_chord root_to_mid_num_span = round( - root_to_mid_span / (aspect_ratio * root_to_mid_panel_chord)) + root_to_mid_span / (aspect_ratio * root_to_mid_panel_chord) + ) mid_to_tip_num_span = round( - mid_to_tip_span / (aspect_ratio * mid_to_tip_panel_chord)) + mid_to_tip_span / (aspect_ratio * mid_to_tip_panel_chord) + ) these_airplane_movements = [] row = None @@ -109,44 +117,76 @@ offset = row - 1 - this_airplane = ps.geometry.Airplane(name=this_name, - x_ref=offset * x_spacing, y_ref=offset_sign * offset * y_spacing, - wings=[ps.geometry.Wing(name="Main Wing", symmetric=True, - chordwise_spacing="uniform", x_le=offset * x_spacing, - y_le=offset_sign * offset * y_spacing, - num_chordwise_panels=num_chord, wing_cross_sections=[ - ps.geometry.WingCrossSection(twist=alpha, chord=root_chord, - airfoil=ps.geometry.Airfoil(name="naca0012"), - num_spanwise_panels=root_to_mid_num_span, - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(twist=alpha, - y_le=root_to_mid_span, chord=root_chord, - airfoil=ps.geometry.Airfoil(name="naca0012"), - num_spanwise_panels=mid_to_tip_num_span, - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(twist=alpha, - y_le=root_to_mid_span + mid_to_tip_span, - chord=tip_chord, airfoil=ps.geometry.Airfoil( - name="naca0012"), ), ], ), ], ) + this_airplane = ps.geometry.Airplane( + name=this_name, + x_ref=offset * x_spacing, + y_ref=offset_sign * offset * y_spacing, + wings=[ + ps.geometry.Wing( + name="Main Wing", + symmetric=True, + chordwise_spacing="uniform", + x_le=offset * x_spacing, + y_le=offset_sign * offset * y_spacing, + num_chordwise_panels=num_chord, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + twist=alpha, + chord=root_chord, + airfoil=ps.geometry.Airfoil(name="naca0012"), + num_spanwise_panels=root_to_mid_num_span, + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + twist=alpha, + y_le=root_to_mid_span, + chord=root_chord, + airfoil=ps.geometry.Airfoil(name="naca0012"), + num_spanwise_panels=mid_to_tip_num_span, + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + twist=alpha, + y_le=root_to_mid_span + mid_to_tip_span, + chord=tip_chord, + airfoil=ps.geometry.Airfoil(name="naca0012"), + ), + ], + ), + ], + ) this_airplane_movement = ps.movement.AirplaneMovement( - base_airplane=this_airplane, wing_movements=[ - ps.movement.WingMovement(base_wing=this_airplane.wings[0], + base_airplane=this_airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=this_airplane.wings[0], wing_cross_sections_movements=[ ps.movement.WingCrossSectionMovement( - base_wing_cross_section= - this_airplane.wings[0].wing_cross_sections[0], ), + base_wing_cross_section=this_airplane.wings[ + 0 + ].wing_cross_sections[0], + ), ps.movement.WingCrossSectionMovement( - base_wing_cross_section= - this_airplane.wings[0].wing_cross_sections[1], + base_wing_cross_section=this_airplane.wings[ + 0 + ].wing_cross_sections[1], sweeping_amplitude=flapping_amplitude, - sweeping_period=period, sweeping_spacing="sine", ), + sweeping_period=period, + sweeping_spacing="sine", + ), ps.movement.WingCrossSectionMovement( - base_wing_cross_section= - this_airplane.wings[0].wing_cross_sections[2], + base_wing_cross_section=this_airplane.wings[ + 0 + ].wing_cross_sections[2], sweeping_amplitude=flapping_amplitude, sweeping_period=period, - sweeping_spacing="sine", ), ], )], ) + sweeping_spacing="sine", + ), + ], + ) + ], + ) these_airplane_movements.append(this_airplane_movement) @@ -155,26 +195,34 @@ this_movement = ps.movement.Movement( airplane_movements=these_airplane_movements, - operating_point_movement=this_operating_point_movement, num_steps=None, - num_cycles=num_flaps, delta_time=None, ) + operating_point_movement=this_operating_point_movement, + num_steps=None, + num_cycles=num_flaps, + delta_time=None, + ) del these_airplane_movements - this_problem = ps.problems.UnsteadyProblem(movement=this_movement, - only_final_results=True, ) + this_problem = ps.problems.UnsteadyProblem( + movement=this_movement, + only_final_results=True, + ) del this_movement - this_solver = ( - ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( - unsteady_problem=this_problem, )) + this_solver = ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( + unsteady_problem=this_problem, + ) del this_problem iter_start = time.time() - this_solver.run(logging_level="Critical", prescribed_wake=prescribed_wake, - calculate_streamlines=False, ) + this_solver.run( + logging_level="Critical", + prescribed_wake=prescribed_wake, + calculate_streamlines=False, + ) iter_stop = time.time() iter_time = round((iter_stop - iter_start), 2) @@ -193,9 +241,11 @@ airplanes = this_solver.steady_problems[step].airplanes for airplane_id, airplane in enumerate(airplanes): total_forces[airplane_id, :, results_step] = ( - airplane.total_near_field_force_wind_axes) + airplane.total_near_field_force_wind_axes + ) total_moments[airplane_id, :, results_step] = ( - airplane.total_near_field_moment_wind_axes) + airplane.total_near_field_moment_wind_axes + ) results_step += 1 these_s_drags = total_forces[:, 0, :] ** 2 @@ -204,8 +254,8 @@ these_ms_drags = np.mean(these_s_drags, axis=-1) these_ms_lifts = np.mean(these_s_lifts, axis=-1) - these_rms_drags = these_ms_drags ** 0.5 - these_rms_lifts = these_ms_lifts ** 0.5 + these_rms_drags = these_ms_drags**0.5 + these_rms_lifts = these_ms_lifts**0.5 rms_drags[wake_state_id, num_flaps_id, num_chord_id, :] = these_rms_drags rms_lifts[wake_state_id, num_flaps_id, num_chord_id, :] = these_rms_lifts @@ -215,56 +265,80 @@ max_chord_rmspc = np.inf if wake_state_id > 0: - last_wake_rms_lifts = rms_lifts[wake_state_id - 1, num_flaps_id, - num_chord_id, :] - last_wake_rms_drags = rms_drags[wake_state_id - 1, num_flaps_id, - num_chord_id, :] + last_wake_rms_lifts = rms_lifts[ + wake_state_id - 1, num_flaps_id, num_chord_id, : + ] + last_wake_rms_drags = rms_drags[ + wake_state_id - 1, num_flaps_id, num_chord_id, : + ] wake_lift_rmspcs = 100 * np.abs( - (these_rms_lifts - last_wake_rms_lifts) / last_wake_rms_lifts) + (these_rms_lifts - last_wake_rms_lifts) / last_wake_rms_lifts + ) wake_drag_rmspcs = 100 * np.abs( - (these_rms_drags - last_wake_rms_drags) / last_wake_rms_drags) + (these_rms_drags - last_wake_rms_drags) / last_wake_rms_drags + ) max_wake_lift_rmspc = np.max(wake_lift_rmspcs) max_wake_drag_rmspc = np.max(wake_drag_rmspcs) max_wake_rmspc = max(max_wake_lift_rmspc, max_wake_drag_rmspc) - print("\t\t\t\tMax Wake RMSPC: ", round(max_wake_rmspc, 2), "%", - sep="", ) + print( + "\t\t\t\tMax Wake RMSPC: ", + round(max_wake_rmspc, 2), + "%", + sep="", + ) else: print("\t\t\t\tMax Wake RMSPC:", max_wake_rmspc) if num_flaps_id > 0: - last_flap_rms_lifts = rms_lifts[wake_state_id, num_flaps_id - 1, - num_chord_id, :] - last_flap_rms_drags = rms_drags[wake_state_id, num_flaps_id - 1, - num_chord_id, :] + last_flap_rms_lifts = rms_lifts[ + wake_state_id, num_flaps_id - 1, num_chord_id, : + ] + last_flap_rms_drags = rms_drags[ + wake_state_id, num_flaps_id - 1, num_chord_id, : + ] flap_lift_rmspcs = 100 * np.abs( - (these_rms_lifts - last_flap_rms_lifts) / last_flap_rms_lifts) + (these_rms_lifts - last_flap_rms_lifts) / last_flap_rms_lifts + ) flap_drag_rmspcs = 100 * np.abs( - (these_rms_drags - last_flap_rms_drags) / last_flap_rms_drags) + (these_rms_drags - last_flap_rms_drags) / last_flap_rms_drags + ) max_flap_lift_rmspc = np.max(flap_lift_rmspcs) max_flap_drag_rmspc = np.max(flap_drag_rmspcs) max_flap_rmspc = max(max_flap_lift_rmspc, max_flap_drag_rmspc) - print("\t\t\t\tMax Flap RMSPC: ", round(max_flap_rmspc, 2), "%", - sep="", ) + print( + "\t\t\t\tMax Flap RMSPC: ", + round(max_flap_rmspc, 2), + "%", + sep="", + ) else: print("\t\t\t\tMax Flap RMSPC:", max_flap_rmspc) if num_chord_id > 0: - last_chord_rms_lifts = rms_lifts[wake_state_id, num_flaps_id, - num_chord_id - 1, :] - last_chord_rms_drags = rms_drags[wake_state_id, num_flaps_id, - num_chord_id - 1, :] + last_chord_rms_lifts = rms_lifts[ + wake_state_id, num_flaps_id, num_chord_id - 1, : + ] + last_chord_rms_drags = rms_drags[ + wake_state_id, num_flaps_id, num_chord_id - 1, : + ] chord_lift_rmspcs = 100 * np.abs( - (these_rms_lifts - last_chord_rms_lifts) / last_chord_rms_lifts) + (these_rms_lifts - last_chord_rms_lifts) / last_chord_rms_lifts + ) chord_drag_rmspcs = 100 * np.abs( - (these_rms_drags - last_chord_rms_drags) / last_chord_rms_drags) + (these_rms_drags - last_chord_rms_drags) / last_chord_rms_drags + ) max_chord_lift_rmspc = np.max(chord_lift_rmspcs) max_chord_drag_rmspc = np.max(chord_drag_rmspcs) max_chord_rmspc = max(max_chord_lift_rmspc, max_chord_drag_rmspc) - print("\t\t\t\tMax Chord RMSPC: ", round(max_chord_rmspc, 2), "%", - sep="", ) + print( + "\t\t\t\tMax Chord RMSPC: ", + round(max_chord_rmspc, 2), + "%", + sep="", + ) else: print("\t\t\t\tMax Chord RMSPC:", max_chord_rmspc) @@ -330,7 +404,10 @@ converged_num_flaps = num_flaps_list[converged_num_flaps_id] converged_num_chord = num_chord_list[converged_num_chord_id] this_iter_time = iter_times[ - converged_wake_state_id, converged_num_flaps_id, converged_num_chord_id,] + converged_wake_state_id, + converged_num_flaps_id, + converged_num_chord_id, + ] plot_wake_state_id = converged_wake_state_id plot_num_flaps_id = converged_num_flaps_id @@ -356,15 +433,33 @@ else: continue - these_rms_lifts = rms_lifts[plot_wake_state_id, plot_num_flaps_id, - : (plot_num_chord_id + 2), airplane_id, ] - these_rms_drags = rms_drags[plot_wake_state_id, plot_num_flaps_id, - : (plot_num_chord_id + 2), airplane_id, ] - - lift_axes.plot(num_chord_list[: (plot_num_chord_id + 2)], these_rms_lifts, - label="Row " + str(row), marker="o", linestyle="--", ) - drag_axes.plot(num_chord_list[: (plot_num_chord_id + 2)], these_rms_drags, - label="Row " + str(row), marker="o", linestyle="--", ) + these_rms_lifts = rms_lifts[ + plot_wake_state_id, + plot_num_flaps_id, + : (plot_num_chord_id + 2), + airplane_id, + ] + these_rms_drags = rms_drags[ + plot_wake_state_id, + plot_num_flaps_id, + : (plot_num_chord_id + 2), + airplane_id, + ] + + lift_axes.plot( + num_chord_list[: (plot_num_chord_id + 2)], + these_rms_lifts, + label="Row " + str(row), + marker="o", + linestyle="--", + ) + drag_axes.plot( + num_chord_list[: (plot_num_chord_id + 2)], + these_rms_drags, + label="Row " + str(row), + marker="o", + linestyle="--", + ) lift_axes.set_xlabel("Number of Chordwise Panels") drag_axes.set_xlabel("Number of Chordwise Panels") @@ -400,15 +495,33 @@ else: continue - these_rms_lifts = rms_lifts[plot_wake_state_id, : (plot_num_flaps_id + 2), - plot_num_chord_id, airplane_id, ] - these_rms_drags = rms_drags[plot_wake_state_id, : (plot_num_flaps_id + 2), - plot_num_chord_id, airplane_id, ] - - lift_axes.plot(num_flaps_list[: (plot_num_flaps_id + 2)], these_rms_lifts, - label="Row " + str(row), marker="o", linestyle="--", ) - drag_axes.plot(num_flaps_list[: (plot_num_flaps_id + 2)], these_rms_drags, - label="Row " + str(row), marker="o", linestyle="--", ) + these_rms_lifts = rms_lifts[ + plot_wake_state_id, + : (plot_num_flaps_id + 2), + plot_num_chord_id, + airplane_id, + ] + these_rms_drags = rms_drags[ + plot_wake_state_id, + : (plot_num_flaps_id + 2), + plot_num_chord_id, + airplane_id, + ] + + lift_axes.plot( + num_flaps_list[: (plot_num_flaps_id + 2)], + these_rms_lifts, + label="Row " + str(row), + marker="o", + linestyle="--", + ) + drag_axes.plot( + num_flaps_list[: (plot_num_flaps_id + 2)], + these_rms_drags, + label="Row " + str(row), + marker="o", + linestyle="--", + ) lift_axes.set_xlabel("Number of Flap Cycles") drag_axes.set_xlabel("Number of Flap Cycles") diff --git a/pterasoftware/aerodynamics.py b/pterasoftware/aerodynamics.py index 7ebc3372..48d4ce8e 100644 --- a/pterasoftware/aerodynamics.py +++ b/pterasoftware/aerodynamics.py @@ -54,7 +54,7 @@ # dramatically affects the stability of the result. I'm using this value, as cited # for use in flapping-wing vehicles in "Role of Filament Strain in the Free-Vortex # Modeling of Rotor Wakes" (Ananthan and Leishman, 2004). It is unitless. -squire = 10 ** -4 +squire = 10**-4 # Set the value of Lamb's constant that will be used by the induced velocity # functions. Lamb's constant relates to the size of the vortex cores and the rate at @@ -117,8 +117,14 @@ class HorseshoeVortex: This class is not meant to be subclassed. """ - def __init__(self, finite_leg_origin, finite_leg_termination, strength, - infinite_leg_direction, infinite_leg_length, ): + def __init__( + self, + finite_leg_origin, + finite_leg_termination, + strength, + infinite_leg_direction, + infinite_leg_length, + ): """This is the initialization method. :param finite_leg_origin: 1D array @@ -144,18 +150,28 @@ def __init__(self, finite_leg_origin, finite_leg_termination, strength, self.infinite_leg_direction = infinite_leg_direction self.infinite_leg_length = infinite_leg_length self.right_leg_origin = ( - self.finite_leg_origin + infinite_leg_direction * infinite_leg_length) + self.finite_leg_origin + infinite_leg_direction * infinite_leg_length + ) self.left_leg_termination = ( - self.finite_leg_termination + infinite_leg_direction * - infinite_leg_length) + self.finite_leg_termination + infinite_leg_direction * infinite_leg_length + ) # Initialize a line vortex to represent the horseshoe's finite leg. - self.right_leg = LineVortex(origin=self.right_leg_origin, - termination=self.finite_leg_origin, strength=self.strength, ) - self.finite_leg = LineVortex(origin=self.finite_leg_origin, - termination=self.finite_leg_termination, strength=self.strength, ) - self.left_leg = LineVortex(origin=self.finite_leg_termination, - termination=self.left_leg_termination, strength=self.strength, ) + self.right_leg = LineVortex( + origin=self.right_leg_origin, + termination=self.finite_leg_origin, + strength=self.strength, + ) + self.finite_leg = LineVortex( + origin=self.finite_leg_origin, + termination=self.finite_leg_termination, + strength=self.strength, + ) + self.left_leg = LineVortex( + origin=self.finite_leg_termination, + termination=self.left_leg_termination, + strength=self.strength, + ) def update_strength(self, strength): """This method updates the strength of this horseshoe vortex object, and the @@ -189,8 +205,14 @@ class RingVortex: This class is not meant to be subclassed. """ - def __init__(self, front_left_vertex, front_right_vertex, back_left_vertex, - back_right_vertex, strength, ): + def __init__( + self, + front_left_vertex, + front_right_vertex, + back_left_vertex, + back_right_vertex, + strength, + ): """This is the initialization method. :param front_left_vertex: 1D array @@ -215,18 +237,34 @@ def __init__(self, front_left_vertex, front_right_vertex, back_left_vertex, self.strength = strength # Initialize the line vortices that make up the ring vortex. - self.front_leg = LineVortex(origin=self.front_right_vertex, - termination=self.front_left_vertex, strength=self.strength, ) - self.left_leg = LineVortex(origin=self.front_left_vertex, - termination=self.back_left_vertex, strength=self.strength, ) - self.back_leg = LineVortex(origin=self.back_left_vertex, - termination=self.back_right_vertex, strength=self.strength, ) - self.right_leg = LineVortex(origin=self.back_right_vertex, - termination=self.front_right_vertex, strength=self.strength, ) + self.front_leg = LineVortex( + origin=self.front_right_vertex, + termination=self.front_left_vertex, + strength=self.strength, + ) + self.left_leg = LineVortex( + origin=self.front_left_vertex, + termination=self.back_left_vertex, + strength=self.strength, + ) + self.back_leg = LineVortex( + origin=self.back_left_vertex, + termination=self.back_right_vertex, + strength=self.strength, + ) + self.right_leg = LineVortex( + origin=self.back_right_vertex, + termination=self.front_right_vertex, + strength=self.strength, + ) # Initialize a variable to hold the centroid of the ring vortex. - self.center = functions.numba_centroid_of_quadrilateral(self.front_left_vertex, - self.front_right_vertex, self.back_left_vertex, self.back_right_vertex, ) + self.center = functions.numba_centroid_of_quadrilateral( + self.front_left_vertex, + self.front_right_vertex, + self.back_left_vertex, + self.back_right_vertex, + ) # Initialize a variable to hold the age of the ring vortex in seconds. self.age = 0 @@ -246,8 +284,9 @@ def update_strength(self, strength): self.left_leg.strength = strength self.back_leg.strength = strength - def update_position(self, front_left_vertex, front_right_vertex, back_left_vertex, - back_right_vertex): + def update_position( + self, front_left_vertex, front_right_vertex, back_left_vertex, back_right_vertex + ): """This method updates the position of the ring vortex, and the positions of all its attributes. @@ -271,24 +310,47 @@ def update_position(self, front_left_vertex, front_right_vertex, back_left_verte self.back_right_vertex = back_right_vertex # Initialize the line vortices that make up the ring vortex. - self.front_leg = LineVortex(origin=self.front_right_vertex, - termination=self.front_left_vertex, strength=self.strength, ) - self.left_leg = LineVortex(origin=self.front_left_vertex, - termination=self.back_left_vertex, strength=self.strength, ) - self.back_leg = LineVortex(origin=self.back_left_vertex, - termination=self.back_right_vertex, strength=self.strength, ) - self.right_leg = LineVortex(origin=self.back_right_vertex, - termination=self.front_right_vertex, strength=self.strength, ) + self.front_leg = LineVortex( + origin=self.front_right_vertex, + termination=self.front_left_vertex, + strength=self.strength, + ) + self.left_leg = LineVortex( + origin=self.front_left_vertex, + termination=self.back_left_vertex, + strength=self.strength, + ) + self.back_leg = LineVortex( + origin=self.back_left_vertex, + termination=self.back_right_vertex, + strength=self.strength, + ) + self.right_leg = LineVortex( + origin=self.back_right_vertex, + termination=self.front_right_vertex, + strength=self.strength, + ) # Initialize a variable to hold the centroid of the ring vortex. - self.center = functions.numba_centroid_of_quadrilateral(self.front_left_vertex, - self.front_right_vertex, self.back_left_vertex, self.back_right_vertex, ) + self.center = functions.numba_centroid_of_quadrilateral( + self.front_left_vertex, + self.front_right_vertex, + self.back_left_vertex, + self.back_right_vertex, + ) @njit(cache=True, fastmath=False) -def collapsed_velocities_from_horseshoe_vortices(points, back_right_vortex_vertices, - front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, strengths, ages=None, nu=0.0, ): +def collapsed_velocities_from_horseshoe_vortices( + points, + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + strengths, + ages=None, + nu=0.0, +): """This function takes in a group of points, and the attributes of a group of horseshoe vortices. At every point, it finds the cumulative induced velocity due to all the horseshoe vortices. @@ -337,24 +399,42 @@ def collapsed_velocities_from_horseshoe_vortices(points, back_right_vortex_verti velocity at each of the N points due to all the horseshoe vortices. The units are meters per second. """ - origins_list = [back_right_vortex_vertices, front_right_vortex_vertices, - front_left_vortex_vertices, ] - terminations_list = [front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, ] + origins_list = [ + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + ] + terminations_list = [ + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + ] induced_velocities = np.zeros((points.shape[0], 3)) # Get the velocity induced by each leg of the ring vortex. for i in range(3): - induced_velocities += collapsed_velocities_from_line_vortices(points=points, - origins=origins_list[i], terminations=terminations_list[i], - strengths=strengths, ages=ages, nu=nu, ) + induced_velocities += collapsed_velocities_from_line_vortices( + points=points, + origins=origins_list[i], + terminations=terminations_list[i], + strengths=strengths, + ages=ages, + nu=nu, + ) return induced_velocities @njit(cache=True, fastmath=False) -def expanded_velocities_from_horseshoe_vortices(points, back_right_vortex_vertices, - front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, strengths, ages=None, nu=0.0, ): +def expanded_velocities_from_horseshoe_vortices( + points, + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + strengths, + ages=None, + nu=0.0, +): """This function takes in a group of points, and the attributes of a group of horseshoe vortices. At every point, it finds the induced velocity due to each horseshoe vortex. @@ -403,24 +483,42 @@ def expanded_velocities_from_horseshoe_vortices(points, back_right_vortex_vertic the velocity induced at one point by one of the horseshoe vortices. The units are meters per second. """ - origins_list = [back_right_vortex_vertices, front_right_vortex_vertices, - front_left_vortex_vertices, ] - terminations_list = [front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, ] + origins_list = [ + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + ] + terminations_list = [ + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + ] induced_velocities = np.zeros((points.shape[0], strengths.shape[0], 3)) # Get the velocity induced by each leg of the ring vortex. for i in range(3): - induced_velocities += expanded_velocities_from_line_vortices(points=points, - origins=origins_list[i], terminations=terminations_list[i], - strengths=strengths, ages=ages, nu=nu, ) + induced_velocities += expanded_velocities_from_line_vortices( + points=points, + origins=origins_list[i], + terminations=terminations_list[i], + strengths=strengths, + ages=ages, + nu=nu, + ) return induced_velocities @njit(cache=True, fastmath=False) -def collapsed_velocities_from_ring_vortices(points, back_right_vortex_vertices, - front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, strengths, ages=None, nu=0.0, ): +def collapsed_velocities_from_ring_vortices( + points, + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + strengths, + ages=None, + nu=0.0, +): """This function takes in a group of points, and the attributes of a group of ring vortices. At every point, it finds the cumulative induced velocity due to all the ring vortices. @@ -469,25 +567,44 @@ def collapsed_velocities_from_ring_vortices(points, back_right_vortex_vertices, velocity at each of the N points due to all the ring vortices. The units are meters per second. """ - origins_list = [back_right_vortex_vertices, front_right_vortex_vertices, - front_left_vortex_vertices, back_left_vortex_vertices, ] - terminations_list = [front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, back_right_vortex_vertices, ] + origins_list = [ + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + ] + terminations_list = [ + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + back_right_vortex_vertices, + ] induced_velocities = np.zeros((points.shape[0], 3)) # Get the velocity induced by each leg of the ring vortex. for i in range(4): - induced_velocities += collapsed_velocities_from_line_vortices(points=points, - origins=origins_list[i], terminations=terminations_list[i], - strengths=strengths, ages=ages, nu=nu, ) + induced_velocities += collapsed_velocities_from_line_vortices( + points=points, + origins=origins_list[i], + terminations=terminations_list[i], + strengths=strengths, + ages=ages, + nu=nu, + ) return induced_velocities @njit(cache=True, fastmath=False) -def collapsed_velocities_from_ring_vortices_chordwise_segments(points, - back_right_vortex_vertices, front_right_vortex_vertices, - front_left_vortex_vertices, back_left_vortex_vertices, strengths, ages=None, - nu=0.0, ): +def collapsed_velocities_from_ring_vortices_chordwise_segments( + points, + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + strengths, + ages=None, + nu=0.0, +): """This function takes in a group of points, and the attributes of a group of ring vortices. At every point, it finds the cumulative induced velocity due to all the ring vortices' chordwise segments. @@ -536,22 +653,40 @@ def collapsed_velocities_from_ring_vortices_chordwise_segments(points, velocity at each of the N points due to all the ring vortices' spanwise segments. The units are meters per second. """ - origins_list = [back_right_vortex_vertices, front_left_vortex_vertices, ] - terminations_list = [front_right_vortex_vertices, back_left_vortex_vertices, ] + origins_list = [ + back_right_vortex_vertices, + front_left_vortex_vertices, + ] + terminations_list = [ + front_right_vortex_vertices, + back_left_vortex_vertices, + ] induced_velocities = np.zeros((points.shape[0], 3)) # Get the velocity induced by each leg of the ring vortex. for i in range(2): - induced_velocities += collapsed_velocities_from_line_vortices(points=points, - origins=origins_list[i], terminations=terminations_list[i], - strengths=strengths, ages=ages, nu=nu, ) + induced_velocities += collapsed_velocities_from_line_vortices( + points=points, + origins=origins_list[i], + terminations=terminations_list[i], + strengths=strengths, + ages=ages, + nu=nu, + ) return induced_velocities @njit(cache=True, fastmath=False) -def expanded_velocities_from_ring_vortices(points, back_right_vortex_vertices, - front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, strengths, ages=None, nu=0.0, ): +def expanded_velocities_from_ring_vortices( + points, + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + strengths, + ages=None, + nu=0.0, +): """This function takes in a group of points, and the attributes of a group of ring vortices. At every point, it finds the induced velocity due to each ring vortex. @@ -600,23 +735,42 @@ def expanded_velocities_from_ring_vortices(points, back_right_vortex_vertices, the velocity induced at one point by one of the ring vortices. The units are meters per second. """ - origins_list = [back_right_vortex_vertices, front_right_vortex_vertices, - front_left_vortex_vertices, back_left_vortex_vertices, ] - terminations_list = [front_right_vortex_vertices, front_left_vortex_vertices, - back_left_vortex_vertices, back_right_vortex_vertices, ] + origins_list = [ + back_right_vortex_vertices, + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + ] + terminations_list = [ + front_right_vortex_vertices, + front_left_vortex_vertices, + back_left_vortex_vertices, + back_right_vortex_vertices, + ] induced_velocities = np.zeros((points.shape[0], strengths.shape[0], 3)) # Get the velocity induced by each leg of the ring vortex. for i in range(4): - induced_velocities += expanded_velocities_from_line_vortices(points=points, - origins=origins_list[i], terminations=terminations_list[i], - strengths=strengths, ages=ages, nu=nu, ) + induced_velocities += expanded_velocities_from_line_vortices( + points=points, + origins=origins_list[i], + terminations=terminations_list[i], + strengths=strengths, + ages=ages, + nu=nu, + ) return induced_velocities @njit(cache=True, fastmath=False) -def collapsed_velocities_from_line_vortices(points, origins, terminations, strengths, - ages=None, nu=0.0, ): +def collapsed_velocities_from_line_vortices( + points, + origins, + terminations, + strengths, + ages=None, + nu=0.0, +): """This function takes in a group of points, and the attributes of a group of line vortices. At every point, it finds the cumulative induced velocity due to all the line vortices. @@ -692,10 +846,10 @@ def collapsed_velocities_from_line_vortices(points, origins, terminations, stren r_0_z = termination[2] - origin[2] # Find the r_0 vector's length. - r_0 = math.sqrt(r_0_x ** 2 + r_0_y ** 2 + r_0_z ** 2) + r_0 = math.sqrt(r_0_x**2 + r_0_y**2 + r_0_z**2) c_1 = strength / (4 * math.pi) - c_2 = r_0 ** 2 * r_c ** 2 + c_2 = r_0**2 * r_c**2 for point_id in range(num_points): point = points[point_id] @@ -716,9 +870,9 @@ def collapsed_velocities_from_line_vortices(points, origins, terminations, stren r_3_z = r_1_x * r_2_y - r_1_y * r_2_x # Find the r_1, r_2, and r_3 vectors' lengths. - r_1 = math.sqrt(r_1_x ** 2 + r_1_y ** 2 + r_1_z ** 2) - r_2 = math.sqrt(r_2_x ** 2 + r_2_y ** 2 + r_2_z ** 2) - r_3 = math.sqrt(r_3_x ** 2 + r_3_y ** 2 + r_3_z ** 2) + r_1 = math.sqrt(r_1_x**2 + r_1_y**2 + r_1_z**2) + r_2 = math.sqrt(r_2_x**2 + r_2_y**2 + r_2_z**2) + r_3 = math.sqrt(r_3_x**2 + r_3_y**2 + r_3_z**2) c_3 = r_1_x * r_2_x + r_1_y * r_2_y + r_1_z * r_2_z @@ -726,11 +880,12 @@ def collapsed_velocities_from_line_vortices(points, origins, terminations, stren # within machine epsilon), there is a removable discontinuity. In this # case, continue to the next point because there is no velocity induced # by the current vortex at this point. - if r_1 < eps or r_2 < eps or r_3 ** 2 < eps: + if r_1 < eps or r_2 < eps or r_3**2 < eps: continue else: - c_4 = (c_1 * (r_1 + r_2) * (r_1 * r_2 - c_3) / ( - r_1 * r_2 * (r_3 ** 2 + c_2))) + c_4 = ( + c_1 * (r_1 + r_2) * (r_1 * r_2 - c_3) / (r_1 * r_2 * (r_3**2 + c_2)) + ) velocities[point_id, 0] += c_4 * r_3_x velocities[point_id, 1] += c_4 * r_3_y velocities[point_id, 2] += c_4 * r_3_z @@ -739,8 +894,14 @@ def collapsed_velocities_from_line_vortices(points, origins, terminations, stren @njit(cache=True, fastmath=False) -def expanded_velocities_from_line_vortices(points, origins, terminations, strengths, - ages=None, nu=0.0, ): +def expanded_velocities_from_line_vortices( + points, + origins, + terminations, + strengths, + ages=None, + nu=0.0, +): """This function takes in a group of points, and the attributes of a group of line vortices. At every point, it finds the induced velocity due to each line vortex. @@ -816,10 +977,10 @@ def expanded_velocities_from_line_vortices(points, origins, terminations, streng r_0_z = termination[2] - origin[2] # Find the r_0 vector's length. - r_0 = math.sqrt(r_0_x ** 2 + r_0_y ** 2 + r_0_z ** 2) + r_0 = math.sqrt(r_0_x**2 + r_0_y**2 + r_0_z**2) c_1 = strength / (4 * math.pi) - c_2 = r_0 ** 2 * r_c ** 2 + c_2 = r_0**2 * r_c**2 for point_id in range(num_points): point = points[point_id] @@ -840,9 +1001,9 @@ def expanded_velocities_from_line_vortices(points, origins, terminations, streng r_3_z = r_1_x * r_2_y - r_1_y * r_2_x # Find the r_1, r_2, and r_3 vectors' lengths. - r_1 = math.sqrt(r_1_x ** 2 + r_1_y ** 2 + r_1_z ** 2) - r_2 = math.sqrt(r_2_x ** 2 + r_2_y ** 2 + r_2_z ** 2) - r_3 = math.sqrt(r_3_x ** 2 + r_3_y ** 2 + r_3_z ** 2) + r_1 = math.sqrt(r_1_x**2 + r_1_y**2 + r_1_z**2) + r_2 = math.sqrt(r_2_x**2 + r_2_y**2 + r_2_z**2) + r_3 = math.sqrt(r_3_x**2 + r_3_y**2 + r_3_z**2) c_3 = r_1_x * r_2_x + r_1_y * r_2_y + r_1_z * r_2_z @@ -850,11 +1011,12 @@ def expanded_velocities_from_line_vortices(points, origins, terminations, streng # within machine epsilon), there is a removable discontinuity. In this # case, set the velocity components to their true values, which are 0.0 # meters per second. - if r_1 < eps or r_2 < eps or r_3 ** 2 < eps: + if r_1 < eps or r_2 < eps or r_3**2 < eps: continue else: - c_4 = (c_1 * (r_1 + r_2) * (r_1 * r_2 - c_3) / ( - r_1 * r_2 * (r_3 ** 2 + c_2))) + c_4 = ( + c_1 * (r_1 + r_2) * (r_1 * r_2 - c_3) / (r_1 * r_2 * (r_3**2 + c_2)) + ) velocities[point_id, vortex_id, 0] = c_4 * r_3_x velocities[point_id, vortex_id, 1] = c_4 * r_3_y velocities[point_id, vortex_id, 2] = c_4 * r_3_z diff --git a/pterasoftware/geometry.py b/pterasoftware/geometry.py index fd5e2c97..52f3051d 100644 --- a/pterasoftware/geometry.py +++ b/pterasoftware/geometry.py @@ -46,8 +46,18 @@ class Airplane: This class is not meant to be subclassed. """ - def __init__(self, name="Untitled", x_ref=0.0, y_ref=0.0, z_ref=0.0, weight=0.0, - wings=None, s_ref=None, c_ref=None, b_ref=None, ): + def __init__( + self, + name="Untitled", + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, + weight=0.0, + wings=None, + s_ref=None, + c_ref=None, + b_ref=None, + ): """This is the initialization method. :param name: str, optional @@ -175,9 +185,17 @@ class Wing: This class is not meant to be subclassed. """ - def __init__(self, name="Untitled Wing", x_le=0.0, y_le=0.0, z_le=0.0, - wing_cross_sections=None, symmetric=False, num_chordwise_panels=8, - chordwise_spacing="cosine", ): + def __init__( + self, + name="Untitled Wing", + x_le=0.0, + y_le=0.0, + z_le=0.0, + wing_cross_sections=None, + symmetric=False, + num_chordwise_panels=8, + chordwise_spacing="cosine", + ): """This is the initialization method. :param name: str, optional @@ -271,9 +289,11 @@ def projected_area(self): # Iterate through the wing cross sections and add the area of their # corresponding wing sections to the total projected area. for wing_cross_section_id, wing_cross_section in enumerate( - self.wing_cross_sections[:-1]): + self.wing_cross_sections[:-1] + ): next_wing_cross_section = self.wing_cross_sections[ - wing_cross_section_id + 1] + wing_cross_section_id + 1 + ] span = abs(next_wing_cross_section.y_le - wing_cross_section.y_le) @@ -359,9 +379,11 @@ def mean_aerodynamic_chord(self): # Iterate through the wing cross sections to add the contribution of their # corresponding wing section to the piecewise integral. for wing_cross_section_id, wing_cross_section in enumerate( - self.wing_cross_sections[:-1]): + self.wing_cross_sections[:-1] + ): next_wing_cross_section = self.wing_cross_sections[ - wing_cross_section_id + 1] + wing_cross_section_id + 1 + ] root_chord = wing_cross_section.chord tip_chord = next_wing_cross_section.chord @@ -371,8 +393,11 @@ def mean_aerodynamic_chord(self): # projected on to the body-frame's XY plane). For a trapezoid, # the integral from the cited equation can be shown to evaluate to the # following. - integral += (section_length * ( - root_chord ** 2 + root_chord * tip_chord + tip_chord ** 2) / 3) + integral += ( + section_length + * (root_chord**2 + root_chord * tip_chord + tip_chord**2) + / 3 + ) # Multiply the integral's value by the coefficients from the cited equation. if self.symmetric: @@ -399,10 +424,20 @@ class WingCrossSection: This class is not meant to be subclassed. """ - def __init__(self, x_le=0.0, y_le=0.0, z_le=0.0, chord=1.0, twist=0.0, airfoil=None, - control_surface_type="symmetric", control_surface_hinge_point=0.75, - control_surface_deflection=0.0, num_spanwise_panels=8, - spanwise_spacing="cosine", ): + def __init__( + self, + x_le=0.0, + y_le=0.0, + z_le=0.0, + chord=1.0, + twist=0.0, + airfoil=None, + control_surface_type="symmetric", + control_surface_hinge_point=0.75, + control_surface_deflection=0.0, + num_spanwise_panels=8, + spanwise_spacing="cosine", + ): """This is the initialization method. :param x_le: float, optional @@ -485,8 +520,9 @@ def trailing_edge(self): """ # Find the rotation matrix given the cross section's twist. - rotation_matrix = functions.angle_axis_rotation_matrix(self.twist * np.pi / 180, - np.array([0, 1, 0])) + rotation_matrix = functions.angle_axis_rotation_matrix( + self.twist * np.pi / 180, np.array([0, 1, 0]) + ) # Use the rotation matrix and the leading edge coordinates to calculate the # trailing edge coordinates. @@ -540,8 +576,13 @@ class Airfoil: This class is not meant to be subclassed. """ - def __init__(self, name="Untitled Airfoil", coordinates=None, repanel=True, - n_points_per_side=400, ): + def __init__( + self, + name="Untitled Airfoil", + coordinates=None, + repanel=True, + n_points_per_side=400, + ): """This is the initialization method. :param name: str, optional @@ -623,29 +664,55 @@ def populate_coordinates(self): # Make uncambered coordinates and generate cosine-spaced points. x_t = functions.cosspace(0, 1, n_points_per_side) - y_t = (5 * thickness * (+0.2969 * np.power(x_t, - 0.5) - 0.1260 * x_t - 0.3516 * np.power( - x_t, 2) + 0.2843 * np.power(x_t, 3) - 0.1015 * np.power(x_t, - 4))) + y_t = ( + 5 + * thickness + * ( + +0.2969 * np.power(x_t, 0.5) + - 0.1260 * x_t + - 0.3516 * np.power(x_t, 2) + + 0.2843 * np.power(x_t, 3) + - 0.1015 * np.power(x_t, 4) + ) + ) # Prevent divide by zero errors for airfoils like the NACA 0012. if camber_loc == 0: camber_loc = 0.5 # Get the camber. - y_c_piece1 = (max_camber / camber_loc ** 2 * ( - 2 * camber_loc * x_t[x_t <= camber_loc] - x_t[ - x_t <= camber_loc] ** 2)) - y_c_piece2 = (max_camber / (1 - camber_loc) ** 2 * ( - (1 - 2 * camber_loc) + 2 * camber_loc * x_t[ - x_t > camber_loc] - x_t[x_t > camber_loc] ** 2)) + y_c_piece1 = ( + max_camber + / camber_loc**2 + * ( + 2 * camber_loc * x_t[x_t <= camber_loc] + - x_t[x_t <= camber_loc] ** 2 + ) + ) + y_c_piece2 = ( + max_camber + / (1 - camber_loc) ** 2 + * ( + (1 - 2 * camber_loc) + + 2 * camber_loc * x_t[x_t > camber_loc] + - x_t[x_t > camber_loc] ** 2 + ) + ) y_c = np.hstack((y_c_piece1, y_c_piece2)) # Get camber slope. - first_piece_slope = (2 * max_camber / camber_loc ** 2 * ( - camber_loc - x_t[x_t <= camber_loc])) - second_piece_slope = (2 * max_camber / (1 - camber_loc) ** 2 * ( - camber_loc - x_t[x_t > camber_loc])) + first_piece_slope = ( + 2 + * max_camber + / camber_loc**2 + * (camber_loc - x_t[x_t <= camber_loc]) + ) + second_piece_slope = ( + 2 + * max_camber + / (1 - camber_loc) ** 2 + * (camber_loc - x_t[x_t > camber_loc]) + ) slope = np.hstack((first_piece_slope, second_piece_slope)) theta = np.arctan(slope) @@ -674,21 +741,25 @@ def populate_coordinates(self): try: # Import the airfoils package as "airfoils". - airfoils = importlib.import_module(name=".airfoils", - package="pterasoftware", ) + airfoils = importlib.import_module( + name=".airfoils", + package="pterasoftware", + ) # Read the text from the airfoil file. raw_text = importlib.resources.read_text(airfoils, name + ".dat") # Trim the text at the return characters. - trimmed_text = raw_text[raw_text.find("\n"):] + trimmed_text = raw_text[raw_text.find("\n") :] # Input the coordinates into a 1D array. coordinates_1d = np.fromstring(trimmed_text, sep="\n") # Check to make sure the number of elements in the array is even. - assert len(coordinates_1d) % 2 == 0, ("File was found in airfoil database, " - "but it could not be read correctly.") + assert len(coordinates_1d) % 2 == 0, ( + "File was found in airfoil database, " + "but it could not be read correctly." + ) # Reshape the 1D coordinates array into an N x 2 array, where N is the # number of rows. @@ -764,7 +835,7 @@ def lower_coordinates(self): """ # Find the lower coordinates. - lower_coordinates = self.coordinates[self.leading_edge_index():, :] + lower_coordinates = self.coordinates[self.leading_edge_index() :, :] # Return the lower coordinates. return lower_coordinates @@ -805,27 +876,32 @@ def get_downsampled_mcl(self, mcl_fractions): # Find the distances between points along the mean camber line, assuming # linear interpolation. mcl_distances_between_points = np.sqrt( - np.power(mcl[:-1, 0] - mcl[1:, 0], 2) + np.power(mcl[:-1, 1] - mcl[1:, 1], - 2)) + np.power(mcl[:-1, 0] - mcl[1:, 0], 2) + + np.power(mcl[:-1, 1] - mcl[1:, 1], 2) + ) # Create a horizontal 1D array that contains the distance along the mean # camber line of each point. mcl_distances_cumulative = np.hstack( - (0, np.cumsum(mcl_distances_between_points))) + (0, np.cumsum(mcl_distances_between_points)) + ) # Normalize the 1D array so that it ranges from 0 to 1. mcl_distances_cumulative_normalized = ( - mcl_distances_cumulative / mcl_distances_cumulative[-1]) + mcl_distances_cumulative / mcl_distances_cumulative[-1] + ) # Linearly interpolate to find the x coordinates of the mean camber line at # the given mean camber line fractions. - mcl_downsampled_x = np.interp(x=mcl_fractions, - xp=mcl_distances_cumulative_normalized, fp=mcl[:, 0]) + mcl_downsampled_x = np.interp( + x=mcl_fractions, xp=mcl_distances_cumulative_normalized, fp=mcl[:, 0] + ) # Linearly interpolate to find the y coordinates of the mean camber line at # the given mean camber line fractions. - mcl_downsampled_y = np.interp(x=mcl_fractions, - xp=mcl_distances_cumulative_normalized, fp=mcl[:, 1]) + mcl_downsampled_y = np.interp( + x=mcl_fractions, xp=mcl_distances_cumulative_normalized, fp=mcl[:, 1] + ) # Combine the x and y coordinates of the downsampled mean camber line. mcl_downsampled = np.column_stack((mcl_downsampled_x, mcl_downsampled_y)) @@ -847,8 +923,12 @@ def get_camber_at_chord_fraction(self, chord_fraction): # Create a function that interpolates between the x and y coordinates of the # mean camber line. - camber_function = sp_interp.interp1d(x=self.mcl_coordinates[:, 0], - y=self.mcl_coordinates[:, 1], copy=False, fill_value="extrapolate", ) + camber_function = sp_interp.interp1d( + x=self.mcl_coordinates[:, 0], + y=self.mcl_coordinates[:, 1], + copy=False, + fill_value="extrapolate", + ) # Find the value of the camber (the y coordinate) of the airfoil at the # requested chord fraction. @@ -883,16 +963,23 @@ def repanel_current_airfoil(self, n_points_per_side=100): # surfaces as a function of the chord fractions upper_func = sp_interp.PchipInterpolator( x=np.flip(upper_original_coordinates[:, 0]), - y=np.flip(upper_original_coordinates[:, 1]), ) - lower_func = sp_interp.PchipInterpolator(x=lower_original_coordinates[:, 0], - y=lower_original_coordinates[:, 1]) + y=np.flip(upper_original_coordinates[:, 1]), + ) + lower_func = sp_interp.PchipInterpolator( + x=lower_original_coordinates[:, 0], y=lower_original_coordinates[:, 1] + ) # Find the x and y coordinates of the upper and lower surfaces at each of the # cosine-spaced x values. x_coordinates = np.hstack( - (np.flip(cosine_spaced_x_values), cosine_spaced_x_values[1:])) - y_coordinates = np.hstack((upper_func(np.flip(cosine_spaced_x_values)), - lower_func(cosine_spaced_x_values[1:]),)) + (np.flip(cosine_spaced_x_values), cosine_spaced_x_values[1:]) + ) + y_coordinates = np.hstack( + ( + upper_func(np.flip(cosine_spaced_x_values)), + lower_func(cosine_spaced_x_values[1:]), + ) + ) # Stack the coordinates together and return them. coordinates = np.column_stack((x_coordinates, y_coordinates)) @@ -924,7 +1011,8 @@ def add_control_surface(self, deflection=0.0, hinge_point=0.75): # Find y coordinate at the hinge point x coordinate and make it a vector. hinge_point = np.array( - (hinge_point, self.get_camber_at_chord_fraction(hinge_point))) + (hinge_point, self.get_camber_at_chord_fraction(hinge_point)) + ) # Split the airfoil into the sections before and after the hinge. split_index = np.where(self.mcl_coordinates[:, 0] > hinge_point[0])[0][0] @@ -935,23 +1023,31 @@ def add_control_surface(self, deflection=0.0, hinge_point=0.75): # Rotate the mean camber line coordinates and upper minus mean camber line # vectors. - new_mcl_coordinates_after = (np.transpose(rotation_matrix @ np.transpose( - mcl_coordinates_after - hinge_point)) + hinge_point) + new_mcl_coordinates_after = ( + np.transpose( + rotation_matrix @ np.transpose(mcl_coordinates_after - hinge_point) + ) + + hinge_point + ) new_upper_minus_mcl_after = np.transpose( - rotation_matrix @ np.transpose(upper_minus_mcl_after)) + rotation_matrix @ np.transpose(upper_minus_mcl_after) + ) # Assemble the new, flapped airfoil. new_mcl_coordinates = np.vstack( - (mcl_coordinates_before, new_mcl_coordinates_after)) + (mcl_coordinates_before, new_mcl_coordinates_after) + ) new_upper_minus_mcl = np.vstack( - (upper_minus_mcl_before, new_upper_minus_mcl_after)) + (upper_minus_mcl_before, new_upper_minus_mcl_after) + ) upper_coordinates = np.flipud(new_mcl_coordinates + new_upper_minus_mcl) lower_coordinates = new_mcl_coordinates - new_upper_minus_mcl coordinates = np.vstack((upper_coordinates, lower_coordinates[1:, :])) # Initialize the new, flapped airfoil and return it. - flapped_airfoil = Airfoil(name=self.name + " flapped", coordinates=coordinates, - repanel=False) + flapped_airfoil = Airfoil( + name=self.name + " flapped", coordinates=coordinates, repanel=False + ) return flapped_airfoil def draw(self): diff --git a/pterasoftware/models/steady_horseshoe_vortex_lattice_method_solver.py b/pterasoftware/models/steady_horseshoe_vortex_lattice_method_solver.py index 8decbfd6..bdb8c680 100644 --- a/pterasoftware/models/steady_horseshoe_vortex_lattice_method_solver.py +++ b/pterasoftware/models/steady_horseshoe_vortex_lattice_method_solver.py @@ -6,47 +6,113 @@ def __init__(self): var = "Variables" def runSolver(self): - example_airplane = ps.geometry.Airplane(name="Example Airplane", x_ref=0.0, - y_ref=0.0, z_ref=0.0, s_ref=None, b_ref=None, c_ref=None, wings=[ - ps.geometry.Wing(name="Main Wing", x_le=0.0, y_le=0.0, z_le=0.0, - symmetric=True, num_chordwise_panels=8, chordwise_spacing="cosine", + example_airplane = ps.geometry.Airplane( + name="Example Airplane", + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, + s_ref=None, + b_ref=None, + c_ref=None, + wings=[ + ps.geometry.Wing( + name="Main Wing", + x_le=0.0, + y_le=0.0, + z_le=0.0, + symmetric=True, + num_chordwise_panels=8, + chordwise_spacing="cosine", wing_cross_sections=[ - ps.geometry.WingCrossSection(x_le=0.0, y_le=0.0, z_le=0.0, - twist=0.0, control_surface_type="symmetric", + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=0.0, + z_le=0.0, + twist=0.0, + control_surface_type="symmetric", control_surface_hinge_point=0.75, - control_surface_deflection=0.0, num_spanwise_panels=8, - spanwise_spacing="cosine", chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca2412", - coordinates=None, repanel=True, - n_points_per_side=400, ), ), - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, z_le=1.0, - chord=1.5, twist=5.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), - ps.geometry.Wing(name="V-Tail", x_le=6.75, z_le=0.25, symmetric=True, - wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, z_le=1.0, - chord=1.0, twist=-5.0, airfoil=ps.geometry.Airfoil( - name="naca0012", ), ), ], ), ], ) - - example_operating_point = ps.operating_point.OperatingPoint(density=1.225, - beta=0.0, velocity=10.0, alpha=1.0, ) - - example_problem = ps.problems.SteadyProblem(airplanes=[example_airplane], - operating_point=example_operating_point, ) + control_surface_deflection=0.0, + num_spanwise_panels=8, + spanwise_spacing="cosine", + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca2412", + coordinates=None, + repanel=True, + n_points_per_side=400, + ), + ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + z_le=1.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), + ps.geometry.Wing( + name="V-Tail", + x_le=6.75, + z_le=0.25, + symmetric=True, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + z_le=1.0, + chord=1.0, + twist=-5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], + ) + + example_operating_point = ps.operating_point.OperatingPoint( + density=1.225, + beta=0.0, + velocity=10.0, + alpha=1.0, + ) + + example_problem = ps.problems.SteadyProblem( + airplanes=[example_airplane], + operating_point=example_operating_point, + ) del example_airplane del example_operating_point - example_solver = (ps.steady_horseshoe_vortex_lattice_method - .SteadyHorseshoeVortexLatticeMethodSolver( - steady_problem=example_problem)) + example_solver = ps.steady_horseshoe_vortex_lattice_method.SteadyHorseshoeVortexLatticeMethodSolver( + steady_problem=example_problem + ) del example_problem - example_solver.run(logging_level="Warning", ) + example_solver.run( + logging_level="Warning", + ) ps.output.print_steady_results(steady_solver=example_solver) - ps.output.draw(solver=example_solver, scalar_type="lift", show_streamlines=True, - show_wake_vortices=False, save=False, ) + ps.output.draw( + solver=example_solver, + scalar_type="lift", + show_streamlines=True, + show_wake_vortices=False, + save=False, + ) diff --git a/pterasoftware/models/steady_ring_vortex_lattice_method_solver.py b/pterasoftware/models/steady_ring_vortex_lattice_method_solver.py index f2ac67b2..98baefbc 100644 --- a/pterasoftware/models/steady_ring_vortex_lattice_method_solver.py +++ b/pterasoftware/models/steady_ring_vortex_lattice_method_solver.py @@ -18,122 +18,186 @@ # program is in SI units. Note: these values are relative to the global # coordinate system fixed front left corner of the first airplane's first wing's # root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, # Give the reference dimensions of this aircraft. "s_ref" is the reference area # in meters squared, "b_ref" is the reference span in meters, and "c_ref" is the # reference chord in meters. I set these values to None, which is their default, # so that they will be populated by the first wing object's calculated # characteristics. Note that the reference area used in this program is the # wetted area of the wing's mean-camberline surface. - s_ref=None, b_ref=None, c_ref=None, wings=[ps.geometry.Wing(name="Main Wing", - # Define the location of the leading edge of the wing relative to the - # global coordinate system fixed front left corner of the first - # airplane's first wing's root wing cross section. These values all - # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, - # Declare that this wing is symmetric. This means that the geometry will - # be reflected across plane of this wing's root wing cross section. Note - # that the geometry coordinates are defined as such: If you were riding - # in the airplane, the positive x direction would point behind you, - # the positive y direction would point out of your right wing, and the - # positive z direction would point upwards, out of your chair. These - # directions form a right-handed coordinate system. The default value of - # "symmetric" is false. - symmetric=True, - # Define the number of chordwise panels on the wing, and the spacing - # between them. The number of chordwise panels defaults to 8 panels. The - # spacing defaults to "cosine", which makes the panels relatively finer, - # in the chordwise direction, near the leading and trailing edges. The - # other option is "uniform". - num_chordwise_panels=8, chordwise_spacing="cosine", - # Every wing has a list of wing cross sections. In order for the geometry - # output to be sensible, each wing must have at least two wing cross - # sections. - wing_cross_sections=[ps.geometry.WingCrossSection( - # Define the location of the leading edge of the wing cross - # section relative to the wing's leading edge. These values all + s_ref=None, + b_ref=None, + c_ref=None, + wings=[ + ps.geometry.Wing( + name="Main Wing", + # Define the location of the leading edge of the wing relative to the + # global coordinate system fixed front left corner of the first + # airplane's first wing's root wing cross section. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, - # Define the twist of the wing cross section in degrees. This is - # equivalent to incidence angle of cross section. The twist is - # about the leading edge. Note that the twist is only stable up - # to 45.0 degrees. Values above that produce unexpected results. - # This will be fixed in a future release. The default value is - # 0.0 degrees. Positive twist corresponds to positive rotation - # about the y axis, as defined by the right-hand rule. - twist=0.0, # Define the type of control surface. The options are "symmetric" - # and "asymmetric". This is only applicable if your wing is also - # symmetric. If so, symmetric control surfaces will deflect in - # the same direction, like flaps, while asymmetric control - # surfaces will deflect in opposite directions, like ailerons. - # The default value is "symmetric". - control_surface_type="asymmetric", - # Define the point on the airfoil where the control surface - # hinges. This is expressed as a faction of the chord length, - # back from the leading edge. The default value is 0.75. - control_surface_hinge_point=0.75, - # Define the deflection of the control surface in degrees. The - # default is 0.0 degrees. We'll set it to 10.0 degrees to show an - # example of an aileron deflection. - control_surface_deflection=10.0, - # Define the number of spanwise panels on the wing cross section, - # and the spacing between them. The number of spanwise panels - # defaults to 8 panels. The spacing defaults to "cosine", - # which makes the panels relatively finer, in the spanwise - # direction, near the cross section ends. The other option is - # "uniform". - num_spanwise_panels=8, spanwise_spacing="cosine", - # Set the chord of this cross section to be 1.75 meters. This - # value defaults to 1.0 meter. - chord=1.5, airfoil=ps.geometry.Airfoil( - # Give the airfoil a name. This defaults to "Untitled - # Airfoil". This name should correspond to a name in the - # airfoil directory or a NACA four series airfoil, unless you - # are passing in your own coordinates. - name="naca2412", - # If you wish to pass in coordinates, set this to a N x 2 - # array of the airfoil's coordinates, where N is the number - # of coordinates. Treat this as an immutable, don't edit - # directly after initialization. If you wish to load - # coordinates from the airfoil directory, leave this as None. - # The default is None. Make sure that any airfoil coordinates - # used range in x from 0 to 1. - coordinates=None, - # This is the variable that determines whether or not you - # would like to repanel the airfoil coordinates. This applies - # to coordinates passed in by the user or to the directory - # coordinates. It is highly recommended to set this to True. - # The default is True. - repanel=True, - # This is number of points to use if repaneling the airfoil. - # It is ignored if the repanel is False. The default is 400. - n_points_per_side=400, ), ), - # Define the next wing cross section. From here on out, - # the declarations will not be as commented as the previous. See the - # above comments if you have questions. - ps.geometry.WingCrossSection(x_le=1.5, y_le=6.0, z_le=0.5, chord=0.75, - control_surface_type="asymmetric", control_surface_hinge_point=0.75, - control_surface_deflection=10.0, - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), + x_le=0.0, + y_le=0.0, + z_le=0.0, + # Declare that this wing is symmetric. This means that the geometry will + # be reflected across plane of this wing's root wing cross section. Note + # that the geometry coordinates are defined as such: If you were riding + # in the airplane, the positive x direction would point behind you, + # the positive y direction would point out of your right wing, and the + # positive z direction would point upwards, out of your chair. These + # directions form a right-handed coordinate system. The default value of + # "symmetric" is false. + symmetric=True, + # Define the number of chordwise panels on the wing, and the spacing + # between them. The number of chordwise panels defaults to 8 panels. The + # spacing defaults to "cosine", which makes the panels relatively finer, + # in the chordwise direction, near the leading and trailing edges. The + # other option is "uniform". + num_chordwise_panels=8, + chordwise_spacing="cosine", + # Every wing has a list of wing cross sections. In order for the geometry + # output to be sensible, each wing must have at least two wing cross + # sections. + wing_cross_sections=[ + ps.geometry.WingCrossSection( + # Define the location of the leading edge of the wing cross + # section relative to the wing's leading edge. These values all + # default to 0.0 meters. + x_le=0.0, + y_le=0.0, + z_le=0.0, + # Define the twist of the wing cross section in degrees. This is + # equivalent to incidence angle of cross section. The twist is + # about the leading edge. Note that the twist is only stable up + # to 45.0 degrees. Values above that produce unexpected results. + # This will be fixed in a future release. The default value is + # 0.0 degrees. Positive twist corresponds to positive rotation + # about the y axis, as defined by the right-hand rule. + twist=0.0, # Define the type of control surface. The options are "symmetric" + # and "asymmetric". This is only applicable if your wing is also + # symmetric. If so, symmetric control surfaces will deflect in + # the same direction, like flaps, while asymmetric control + # surfaces will deflect in opposite directions, like ailerons. + # The default value is "symmetric". + control_surface_type="asymmetric", + # Define the point on the airfoil where the control surface + # hinges. This is expressed as a faction of the chord length, + # back from the leading edge. The default value is 0.75. + control_surface_hinge_point=0.75, + # Define the deflection of the control surface in degrees. The + # default is 0.0 degrees. We'll set it to 10.0 degrees to show an + # example of an aileron deflection. + control_surface_deflection=10.0, + # Define the number of spanwise panels on the wing cross section, + # and the spacing between them. The number of spanwise panels + # defaults to 8 panels. The spacing defaults to "cosine", + # which makes the panels relatively finer, in the spanwise + # direction, near the cross section ends. The other option is + # "uniform". + num_spanwise_panels=8, + spanwise_spacing="cosine", + # Set the chord of this cross section to be 1.75 meters. This + # value defaults to 1.0 meter. + chord=1.5, + airfoil=ps.geometry.Airfoil( + # Give the airfoil a name. This defaults to "Untitled + # Airfoil". This name should correspond to a name in the + # airfoil directory or a NACA four series airfoil, unless you + # are passing in your own coordinates. + name="naca2412", + # If you wish to pass in coordinates, set this to a N x 2 + # array of the airfoil's coordinates, where N is the number + # of coordinates. Treat this as an immutable, don't edit + # directly after initialization. If you wish to load + # coordinates from the airfoil directory, leave this as None. + # The default is None. Make sure that any airfoil coordinates + # used range in x from 0 to 1. + coordinates=None, + # This is the variable that determines whether or not you + # would like to repanel the airfoil coordinates. This applies + # to coordinates passed in by the user or to the directory + # coordinates. It is highly recommended to set this to True. + # The default is True. + repanel=True, + # This is number of points to use if repaneling the airfoil. + # It is ignored if the repanel is False. The default is 400. + n_points_per_side=400, + ), + ), + # Define the next wing cross section. From here on out, + # the declarations will not be as commented as the previous. See the + # above comments if you have questions. + ps.geometry.WingCrossSection( + x_le=1.5, + y_le=6.0, + z_le=0.5, + chord=0.75, + control_surface_type="asymmetric", + control_surface_hinge_point=0.75, + control_surface_deflection=10.0, + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="Horizontal Stabilizer", x_le=6.75, z_le=0.25, - symmetric=True, wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), + ps.geometry.Wing( + name="Horizontal Stabilizer", + x_le=6.75, + z_le=0.25, + symmetric=True, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + # Give the root wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, chord=1.0, twist=-5.0, + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + chord=1.0, + twist=-5.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="Vertical Stabilizer", x_le=6.75, z_le=0.5, - symmetric=False, wing_cross_sections=[ - ps.geometry.WingCrossSection(chord=1.5, + ps.geometry.Wing( + name="Vertical Stabilizer", + x_le=6.75, + z_le=0.5, + symmetric=False, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, z_le=2.0, chord=1.0, + ps.geometry.WingCrossSection( + x_le=0.5, + z_le=2.0, + chord=1.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Define a new operating point object. This defines the state at which the airplane # object is operating. @@ -149,7 +213,8 @@ velocity=10.0, # Define the angle of attack the airplane is experiencing. This defaults to 5.0 # degrees. - alpha=1.0, ) + alpha=1.0, +) # Define a new steady problem. A steady problem contains an airplane object and an # operating point object. @@ -157,7 +222,8 @@ # Set this steady problem's list of airplane objects to be the one we just created. airplanes=[example_airplane], # Set this steady problem's operating point object ot be the one we just created. - operating_point=example_operating_point, ) + operating_point=example_operating_point, +) # Now, the airplane and operating point object exist within the steady problem # object. I like to delete the external pointers to these objects to ease debugging. @@ -167,10 +233,10 @@ # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. -example_solver = (ps.steady_ring_vortex_lattice_method -.SteadyRingVortexLatticeMethodSolver( +example_solver = ps.steady_ring_vortex_lattice_method.SteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - steady_problem=example_problem)) + steady_problem=example_problem +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. Again, this is unnecessary, I just like to do this to ease debugging. @@ -181,13 +247,15 @@ # This parameter determines the detail of information that the solver's logger # will output while running. The options are, in order of detail and severity, # "Debug", "Info", "Warning", "Error", "Critical". The default value is "Warning". - logging_level="Warning", ) + logging_level="Warning", +) # Call this function from the output module to print the results. ps.output.print_steady_results(steady_solver=example_solver) # Call the software's draw function on the solver. -ps.output.draw(solver=example_solver, +ps.output.draw( + solver=example_solver, # Tell the draw function to color the aircraft's wing panels with the local # lift coefficient. The valid arguments for this parameter are None, "induced drag", # "side force", or "lift". @@ -200,7 +268,8 @@ show_wake_vortices=False, # The the draw function to not save the drawing as an image file. This way, # the drawing will still be displayed but not saved. This value defaults to false. - save=False, ) + save=False, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_static.py b/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_static.py index 39c5d655..79b4a44a 100644 --- a/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_static.py +++ b/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_static.py @@ -18,22 +18,28 @@ # program is in SI units. Note: these values are relative to the global # coordinate system fixed front left corner of the first airplane's first wing's # root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, # Give the reference dimensions of this aircraft. "s_ref" is the reference area # in meters squared, "b_ref" is the reference span in meters, and "c_ref" is the # reference chord in meters. I set these values to None, which is their default, # so that they will be populated by the first wing object's calculated # characteristics. Note that the reference area used in this program is the # wetted area of the wing's mean-camberline surface. - s_ref=None, b_ref=None, c_ref=None, # All airplane objects have a list of wings. - wings=[# Create the first wing object in this airplane. - ps.geometry.Wing(# Give the wing a name, this defaults to "Untitled Wing". + s_ref=None, + b_ref=None, + c_ref=None, # All airplane objects have a list of wings. + wings=[ # Create the first wing object in this airplane. + ps.geometry.Wing( # Give the wing a name, this defaults to "Untitled Wing". name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Declare that this wing is symmetric. This means that the geometry will # be reflected across plane of this wing's root wing cross section. Note # that the geometry coordinates are defined as such: If you were riding @@ -49,16 +55,19 @@ # in the chordwise direction, near the leading and trailing edges. The # other option is "uniform". I set this value to "uniform" here as it # increase the accuracy of unsteady solvers. - num_chordwise_panels=6, chordwise_spacing="uniform", + num_chordwise_panels=6, + chordwise_spacing="uniform", # Every wing has a list of wing cross sections. In order for the geometry # output to be sensible, each wing must have at least two wing cross # sections. - wing_cross_sections=[# Create a new wing cross section object. + wing_cross_sections=[ # Create a new wing cross section object. ps.geometry.WingCrossSection( # Define the location of the leading edge of the wing cross # section relative to the wing's leading edge. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Define the twist of the wing cross section in degrees. This is # equivalent to incidence angle of cross section. The twist is # about the leading edge. Note that the twist is only stable up @@ -87,10 +96,11 @@ # which makes the panels relatively finer, in the spanwise # direction, near the cross section ends. The other option is # "uniform". - num_spanwise_panels=8, spanwise_spacing="cosine", + num_spanwise_panels=8, + spanwise_spacing="cosine", # Set the chord of this cross section to be 1.75 meters. This # value defaults to 1.0 meter. - chord=1.75, # Every wing cross section has an airfoil object. + chord=1.75, # Every wing cross section has an airfoil object. airfoil=ps.geometry.Airfoil( # Give the airfoil a name. This defaults to "Untitled # Airfoil". This name should correspond to a name in the @@ -113,24 +123,57 @@ repanel=True, # This is number of points to use if repaneling the airfoil. # It is ignored if the repanel is False. The default is 400. - n_points_per_side=400, ), ), + n_points_per_side=400, + ), + ), # Define the next wing cross section. From here on out, # the declarations will not be as commented as the previous. See the # above comments if you have questions. - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, z_le=1.0, chord=1.5, - twist=5.0, # Give this wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + z_le=1.0, + chord=1.5, + twist=5.0, # Give this wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="V-Tail", x_le=6.75, z_le=0.25, num_chordwise_panels=6, - chordwise_spacing="uniform", symmetric=True, + ps.geometry.Wing( + name="V-Tail", + x_le=6.75, + z_le=0.25, + num_chordwise_panels=6, + chordwise_spacing="uniform", + symmetric=True, # Define this wing's root wing cross section. - wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + # Give the root wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, z_le=1.0, chord=1.0, - twist=-5.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + z_le=1.0, + chord=1.0, + twist=-5.0, # Give the tip wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Now define the main wing's root wing cross section's movement. Cross sections can # move in three ways: sweeping, pitching, and heaving. Sweeping is defined as the @@ -170,33 +213,39 @@ heaving_period=0.0, # Define the time step spacing of the heaving. This is "sine" by default. The # options are "sine" and "uniform". - heaving_spacing="sine", ) + heaving_spacing="sine", +) # Define the main wing's tip wing cross section's movement. As the example has static # geometry, the movement attributes can be excluded, and the default values will # suffice. main_wing_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], ) + base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], +) # Define the v-tail's root wing cross section's movement. As the example has static # geometry, the movement attributes can be excluded, and the default values will # suffice. v_tail_root_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], +) # Define the v-tail's tip wing cross section's movement. As the example has static # geometry, the movement attributes can be excluded, and the default values will # suffice. v_tail_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], +) # Now define the main wing's movement. In addition to their wing cross sections' # relative movements, wings' leading edge positions can move as well. main_wing_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[0], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[main_wing_root_wing_cross_section_movement, - main_wing_tip_wing_cross_section_movement, ], + wing_cross_sections_movements=[ + main_wing_root_wing_cross_section_movement, + main_wing_tip_wing_cross_section_movement, + ], # Define the amplitude of the leading edge's change in x position. This value is # in meters. This is set to 0.0 meters, which is the default value. x_le_amplitude=0.0, @@ -223,7 +272,8 @@ z_le_period=0.0, # Define the time step spacing of the leading edge's change in z position. This # is "sine" by default. The options are "sine" and "uniform". - z_le_spacing="sine", ) + z_le_spacing="sine", +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -232,11 +282,14 @@ del main_wing_tip_wing_cross_section_movement # Make the v-tail's wing movement object. -v_tail_movement = ps.movement.WingMovement(# Define the base wing object. +v_tail_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[1], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[v_tail_root_wing_cross_section_movement, - v_tail_tip_wing_cross_section_movement, ], ) + wing_cross_sections_movements=[ + v_tail_root_wing_cross_section_movement, + v_tail_tip_wing_cross_section_movement, + ], +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -246,8 +299,8 @@ # Now define the airplane's movement object. In addition to their wing's and wing # cross sections' relative movements, airplane's reference positions can move as well. -airplane_movement = ps.movement.AirplaneMovement(# Define the base airplane object. - base_airplane=example_airplane, # Add the list of wing movement objects. +airplane_movement = ps.movement.AirplaneMovement( # Define the base airplane object. + base_airplane=example_airplane, # Add the list of wing movement objects. wing_movements=[main_wing_movement, v_tail_movement], # Define the amplitude of the reference position's change in x position. This # value is in meters. This is set to 0.0 meters, which is the default value. @@ -275,7 +328,8 @@ z_ref_period=0.0, # Define the time step spacing of the reference position's change in z position. # This is "sine" by default. The options are "sine" and "uniform". - z_ref_spacing="sine", ) + z_ref_spacing="sine", +) # Delete the extraneous wing movement objects, as these are now contained within the # airplane movement object. @@ -300,7 +354,8 @@ # Define the kinematic viscosity of the air in meters squared per second. This # defaults to 15.06e-6 meters squared per second, which corresponds to an air # temperature of 20 degrees Celsius. - nu=15.06e-6, ) + nu=15.06e-6, +) # Define the operating point's movement. The operating point's velocity can change # with respect to time. @@ -315,12 +370,13 @@ velocity_period=0.0, # Define the time step spacing of the velocity's change in time. This is "sine" # by default. The options are "sine" and "uniform". - velocity_spacing="sine", ) + velocity_spacing="sine", +) # Define the movement object. This contains the airplane movement and the operating # point movement. -movement = ps.movement.Movement(# Add the airplane movement. - airplane_movements=[airplane_movement], # Add the operating point movement. +movement = ps.movement.Movement( # Add the airplane movement. + airplane_movements=[airplane_movement], # Add the operating point movement. operating_point_movement=operating_point_movement, # Leave the number of time steps and the length of each time step unspecified. # The solver will automatically set the length of the time steps so that the wake @@ -330,7 +386,9 @@ # lengths back from the main wing. If the geometry isn't static, the number of # steps will be set such that three periods of the slowest movement oscillation # complete. - num_steps=None, delta_time=None, ) + num_steps=None, + delta_time=None, +) # Delete the extraneous airplane and operating point movement objects, as these are # now contained within the movement object. @@ -338,15 +396,17 @@ del operating_point_movement # Define the unsteady example problem. -example_problem = ps.problems.UnsteadyProblem(movement=movement, ) +example_problem = ps.problems.UnsteadyProblem( + movement=movement, +) # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. -example_solver = (ps.unsteady_ring_vortex_lattice_method -.UnsteadyRingVortexLatticeMethodSolver( +example_solver = ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - unsteady_problem=example_problem, )) + unsteady_problem=example_problem, +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. @@ -359,11 +419,12 @@ # "Debug", "Info", "Warning", "Error", "Critical". The default value is "Warning". logging_level="Warning", # Use a prescribed wake model. This is faster, but may be slightly less accurate. - prescribed_wake=True, ) + prescribed_wake=True, +) # Call the software's draw function on the solver. Press "q" to close the plotter # after it draws the output. -ps.output.draw(# Set the solver to the one we just ran. +ps.output.draw( # Set the solver to the one we just ran. solver=example_solver, # Tell the draw function to color the aircraft's wing panels with the local lift # coefficient. The valid arguments for this parameter are None, "induced drag", @@ -377,12 +438,13 @@ show_wake_vortices=False, # The the draw function to not save the drawing as an image file. This way, # the drawing will still be displayed but not saved. This value defaults to false. - save=False, ) + save=False, +) # Call the software's animate function on the solver. This produces a GIF of the wake # being shed. The GIF is saved in the same directory as this script. Press "q", # after orienting the view, to begin the animation. -ps.output.animate(# Set the unsteady solver to the one we just ran. +ps.output.animate( # Set the unsteady solver to the one we just ran. unsteady_solver=example_solver, # Tell the animate function to color the aircraft's wing panels with the local # lift coefficient. The valid arguments for this parameter are None, "induced drag", @@ -394,18 +456,21 @@ # The the animate function to not save the animation as file. This way, # the animation will still be displayed but not saved. This value defaults to # false. - save=False, ) + save=False, +) # Call the software's plotting function on the solver. This produces graphs of the # output forces and moments with respect to time. -ps.output.plot_results_versus_time(# Set the unsteady solver to the one we just ran. +ps.output.plot_results_versus_time( # Set the unsteady solver to the one we just ran. unsteady_solver=example_solver, # Set the show attribute to True, which is the default value. With this set to # show, some IDEs (such as PyCharm in "Scientific Mode") will display the plots # in a sidebar. Other IDEs may not display the plots, in which case you should # set the save attribute to True, and open the files after they've been saved to # the current directory. - show=True, save=False, ) + show=True, + save=False, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable.py b/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable.py index 7bcba6e4..b2d0311d 100644 --- a/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable.py +++ b/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable.py @@ -18,22 +18,28 @@ # program is in SI units. Note: these values are relative to the global # coordinate system fixed front left corner of the first airplane's first wing's # root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, # Give the reference dimensions of this aircraft. "s_ref" is the reference area # in meters squared, "b_ref" is the reference span in meters, and "c_ref" is the # reference chord in meters. I set these values to None, which is their default, # so that they will be populated by the first wing object's calculated # characteristics. Note that the reference area used in this program is the # wetted area of the wing's mean-camberline surface. - s_ref=None, b_ref=None, c_ref=None, # All airplane objects have a list of wings. - wings=[# Create the first wing object in this airplane. + s_ref=None, + b_ref=None, + c_ref=None, # All airplane objects have a list of wings. + wings=[ # Create the first wing object in this airplane. ps.geometry.Wing( # Give the wing a name, this defaults to "Untitled Wing". name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Declare that this wing is symmetric. This means that the geometry will # be reflected across plane of this wing's root wing cross section. Note # that the geometry coordinates are defined as such: If you were riding @@ -49,16 +55,19 @@ # in the chordwise direction, near the leading and trailing edges. The # other option is "uniform". I set this value to "uniform" here as it # increase the accuracy of unsteady solvers. - num_chordwise_panels=6, chordwise_spacing="uniform", + num_chordwise_panels=6, + chordwise_spacing="uniform", # Every wing has a list of wing cross sections. In order for the geometry # output to be sensible, each wing must have at least two wing cross # sections. - wing_cross_sections=[# Create a new wing cross section object. + wing_cross_sections=[ # Create a new wing cross section object. ps.geometry.WingCrossSection( # Define the location of the leading edge of the wing cross # section relative to the wing's leading edge. These values all # default to 0.0 meters. - x_le=0.0, y_le=0.0, z_le=0.0, + x_le=0.0, + y_le=0.0, + z_le=0.0, # Define the twist of the wing cross section in degrees. This is # equivalent to incidence angle of cross section. The twist is # about the leading edge. Note that the twist is only stable up @@ -87,10 +96,11 @@ # which makes the panels relatively finer, in the spanwise # direction, near the cross section ends. The other option is # "uniform". - num_spanwise_panels=8, spanwise_spacing="cosine", + num_spanwise_panels=8, + spanwise_spacing="cosine", # Set the chord of this cross section to be 1.75 meters. This # value defaults to 1.0 meter. - chord=1.75, # Every wing cross section has an airfoil object. + chord=1.75, # Every wing cross section has an airfoil object. airfoil=ps.geometry.Airfoil( # Give the airfoil a name. This defaults to "Untitled # Airfoil". This name should correspond to a name in the @@ -113,24 +123,57 @@ repanel=True, # This is number of points to use if repaneling the airfoil. # It is ignored if the repanel is False. The default is 400. - n_points_per_side=400, ), ), + n_points_per_side=400, + ), + ), # Define the next wing cross section. From here on out, # the declarations will not be as commented as the previous. See the # above comments if you have questions. - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, z_le=1.0, chord=1.5, - twist=5.0, # Give this wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca2412", ), ), ], ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + z_le=1.0, + chord=1.5, + twist=5.0, # Give this wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca2412", + ), + ), + ], + ), # Define the next wing. - ps.geometry.Wing(name="V-Tail", x_le=6.75, z_le=0.25, num_chordwise_panels=6, - chordwise_spacing="uniform", symmetric=True, + ps.geometry.Wing( + name="V-Tail", + x_le=6.75, + z_le=0.25, + num_chordwise_panels=6, + chordwise_spacing="uniform", + symmetric=True, # Define this wing's root wing cross section. - wing_cross_sections=[ps.geometry.WingCrossSection(chord=1.5, - # Give the root wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), twist=-5.0, ), + wing_cross_sections=[ + ps.geometry.WingCrossSection( + chord=1.5, + # Give the root wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + twist=-5.0, + ), # Define the wing's tip wing cross section. - ps.geometry.WingCrossSection(x_le=0.5, y_le=2.0, z_le=1.0, chord=1.0, - twist=-5.0, # Give the tip wing cross section an airfoil. - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=2.0, + z_le=1.0, + chord=1.0, + twist=-5.0, # Give the tip wing cross section an airfoil. + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], +) # Now define the main wing's root wing cross section's movement. Cross sections can # move in three ways: sweeping, pitching, and heaving. Sweeping is defined as the @@ -170,32 +213,44 @@ heaving_period=0.0, # Define the time step spacing of the heaving. This is "sine" by default. The # options are "sine" and "uniform". - heaving_spacing="sine", ) + heaving_spacing="sine", +) # Define the main wing's tip wing cross section's movement. main_wing_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( base_wing_cross_section=example_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=30.0, sweeping_period=1.0, sweeping_spacing="sine", - pitching_amplitude=15.0, pitching_period=1.0, pitching_spacing="sine", - heaving_amplitude=0.0, heaving_period=0.0, heaving_spacing="sine", ) + sweeping_amplitude=30.0, + sweeping_period=1.0, + sweeping_spacing="sine", + pitching_amplitude=15.0, + pitching_period=1.0, + pitching_spacing="sine", + heaving_amplitude=0.0, + heaving_period=0.0, + heaving_spacing="sine", +) # Define the v-tail's root wing cross section's movement. This wing will be static, # so the movement attributes can be excluded, and the default values will suffice. v_tail_root_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[0], +) # Define the v-tail's root wing cross section's movement. This wing will be static, # so the movement attributes can be excluded, and the default values will suffice. v_tail_tip_wing_cross_section_movement = ps.movement.WingCrossSectionMovement( - base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], ) + base_wing_cross_section=example_airplane.wings[1].wing_cross_sections[1], +) # Now define the main wing's movement. In addition to their wing cross sections' # relative movements, wings' leading edge positions can move as well. main_wing_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[0], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[main_wing_root_wing_cross_section_movement, - main_wing_tip_wing_cross_section_movement, ], + wing_cross_sections_movements=[ + main_wing_root_wing_cross_section_movement, + main_wing_tip_wing_cross_section_movement, + ], # Define the amplitude of the leading edge's change in x position. This value is # in meters. This is set to 0.0 meters, which is the default value. x_le_amplitude=0.0, @@ -222,7 +277,8 @@ z_le_period=0.0, # Define the time step spacing of the leading edge's change in z position. This # is "sine" by default. The options are "sine" and "uniform". - z_le_spacing="sine", ) + z_le_spacing="sine", +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -231,11 +287,14 @@ del main_wing_tip_wing_cross_section_movement # Make the v-tail's wing movement object. -v_tail_movement = ps.movement.WingMovement(# Define the base wing object. +v_tail_movement = ps.movement.WingMovement( # Define the base wing object. base_wing=example_airplane.wings[1], # Add the list of wing cross section movement objects. - wing_cross_sections_movements=[v_tail_root_wing_cross_section_movement, - v_tail_tip_wing_cross_section_movement, ], ) + wing_cross_sections_movements=[ + v_tail_root_wing_cross_section_movement, + v_tail_tip_wing_cross_section_movement, + ], +) # Delete the extraneous wing cross section movement objects, as these are now # contained within the wing movement object. This is unnecessary, but it can make @@ -245,8 +304,8 @@ # Now define the airplane's movement object. In addition to their wing's and wing # cross sections' relative movements, airplane's reference positions can move as well. -airplane_movement = ps.movement.AirplaneMovement(# Define the base airplane object. - base_airplane=example_airplane, # Add the list of wing movement objects. +airplane_movement = ps.movement.AirplaneMovement( # Define the base airplane object. + base_airplane=example_airplane, # Add the list of wing movement objects. wing_movements=[main_wing_movement, v_tail_movement], # Define the amplitude of the reference position's change in x position. This # value is in meters. This is set to 0.0 meters, which is the default value. @@ -274,7 +333,8 @@ z_ref_period=0.0, # Define the time step spacing of the reference position's change in z position. # This is "sine" by default. The options are "sine" and "uniform". - z_ref_spacing="sine", ) + z_ref_spacing="sine", +) # Delete the extraneous wing movement objects, as these are now contained within the # airplane movement object. @@ -299,7 +359,8 @@ # Define the kinematic viscosity of the air in meters squared per second. This # defaults to 15.06e-6 meters squared per second, which corresponds to an air # temperature of 20 degrees Celsius. - nu=15.06e-6, ) + nu=15.06e-6, +) # Define the operating point's movement. The operating point's velocity can change # with respect to time. @@ -314,12 +375,13 @@ velocity_period=0.0, # Define the time step spacing of the velocity's change in time. This is "sine" # by default. The options are "sine" and "uniform". - velocity_spacing="sine", ) + velocity_spacing="sine", +) # Define the movement object. This contains the airplane movement and the operating # point movement. -movement = ps.movement.Movement(# Add the airplane movement. - airplane_movements=[airplane_movement], # Add the operating point movement. +movement = ps.movement.Movement( # Add the airplane movement. + airplane_movements=[airplane_movement], # Add the operating point movement. operating_point_movement=operating_point_movement, # Leave the number of time steps and the length of each time step unspecified. # The solver will automatically set the length of the time steps so that the wake @@ -329,7 +391,9 @@ # lengths back from the main wing. If the geometry isn't static, the number of # steps will be set such that three periods of the slowest movement oscillation # complete. - num_steps=None, delta_time=None, ) + num_steps=None, + delta_time=None, +) # Delete the extraneous airplane and operating point movement objects, as these are # now contained within the movement object. @@ -337,15 +401,17 @@ del operating_point_movement # Define the unsteady example problem. -example_problem = ps.problems.UnsteadyProblem(movement=movement, ) +example_problem = ps.problems.UnsteadyProblem( + movement=movement, +) # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. -example_solver = (ps.unsteady_ring_vortex_lattice_method -.UnsteadyRingVortexLatticeMethodSolver( +example_solver = ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - unsteady_problem=example_problem, )) + unsteady_problem=example_problem, +) # Delete the extraneous pointer to the problem as it is now contained within the # solver. @@ -358,12 +424,13 @@ # "Debug", "Info", "Warning", "Error", "Critical". The default value is "Warning". logging_level="Warning", # Use a prescribed wake model. This is faster, but may be slightly less accurate. - prescribed_wake=True, ) + prescribed_wake=True, +) # Call the software's animate function on the solver. This produces a GIF of the wake # being shed. The GIF is saved in the same directory as this script. Press "q", # after orienting the view, to begin the animation. -ps.output.animate(# Set the unsteady solver to the one we just ran. +ps.output.animate( # Set the unsteady solver to the one we just ran. unsteady_solver=example_solver, # Tell the animate function to color the aircraft's wing panels with the local # lift coefficient. The valid arguments for this parameter are None, "induced drag", @@ -375,7 +442,8 @@ # The the animate function to not save the animation as file. This way, # the animation will still be displayed but not saved. This value defaults to # false. - save=False, ) + save=False, +) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable_formation.py b/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable_formation.py index 5a0639da..f1700ece 100644 --- a/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable_formation.py +++ b/pterasoftware/models/unsteady_ring_vortex_lattice_method_solver_variable_formation.py @@ -16,138 +16,250 @@ def runSolver(self): y_spacing = 13 # Create the lead airplane object. - lead_airplane = ps.geometry.Airplane(name="Lead Airplane", + lead_airplane = ps.geometry.Airplane( + name="Lead Airplane", # Specify the location of the lead airplane's center of gravity. This is the # point around about which the solver will calculate the moments on the # airplane. # These three values default to 0.0 meters. Note: these values are relative to # the global coordinate system fixed front left corner of the first airplane's # first wing's root wing cross section. - x_ref=0.0, y_ref=0.0, z_ref=0.0, wings=[ps.geometry.Wing(name="Main Wing", - # Define the location of the leading edge of the wing relative to the - # global coordinate system fixed front left corner of the first - # airplane's first wing's root wing cross section. - x_le=0.0, y_le=0.0, - # Declare that this wing is symmetric. This means that the geometry will - # be reflected across plane of this wing's root wing cross section. Note - # that the geometry coordinates are defined as such: If you were riding - # in the airplane, the positive x direction would point behind you, - # the positive y direction would point out of your right wing, and the - # positive z direction would point upwards, out of your chair. These - # directions form a right-handed coordinate system. The default value of - # "symmetric" is false. - symmetric=True, - # Define the chordwise spacing of the wing panels to be "uniform" as this - # increase the accuracy of unsteady solvers. - chordwise_spacing="uniform", num_chordwise_panels=4, - wing_cross_sections=[ps.geometry.WingCrossSection( - # Define the location of the leading edge of the wing cross - # section relative to the wing's leading edge. These values all - # default to 0.0 meters. - x_le=0.0, y_le=0.0, - # Assign the twist of this wing cross section. Note: when - # assigning angles of attack to multiple airplanes, it is better - # to set the operating point's angle of attack to zero, and then - # use offset the twist values of all the wing cross sections to - # simulate each aircraft having an angle of attack. - twist=5.0, chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, chord=1.5, - twist=5.0, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), ], ), ], ) + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, + wings=[ + ps.geometry.Wing( + name="Main Wing", + # Define the location of the leading edge of the wing relative to the + # global coordinate system fixed front left corner of the first + # airplane's first wing's root wing cross section. + x_le=0.0, + y_le=0.0, + # Declare that this wing is symmetric. This means that the geometry will + # be reflected across plane of this wing's root wing cross section. Note + # that the geometry coordinates are defined as such: If you were riding + # in the airplane, the positive x direction would point behind you, + # the positive y direction would point out of your right wing, and the + # positive z direction would point upwards, out of your chair. These + # directions form a right-handed coordinate system. The default value of + # "symmetric" is false. + symmetric=True, + # Define the chordwise spacing of the wing panels to be "uniform" as this + # increase the accuracy of unsteady solvers. + chordwise_spacing="uniform", + num_chordwise_panels=4, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + # Define the location of the leading edge of the wing cross + # section relative to the wing's leading edge. These values all + # default to 0.0 meters. + x_le=0.0, + y_le=0.0, + # Assign the twist of this wing cross section. Note: when + # assigning angles of attack to multiple airplanes, it is better + # to set the operating point's angle of attack to zero, and then + # use offset the twist values of all the wing cross sections to + # simulate each aircraft having an angle of attack. + twist=5.0, + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], + ) # Now define the lead airplane's movement object. lead_airplane_movement = ps.movement.AirplaneMovement( base_airplane=lead_airplane, - wing_movements=[# Define the main wing's movement. - ps.movement.WingMovement(base_wing=lead_airplane.wings[0], + wing_movements=[ # Define the main wing's movement. + ps.movement.WingMovement( + base_wing=lead_airplane.wings[0], # Add the list of wing cross section movement objects. wing_cross_sections_movements=[ # Define the root wing cross section's movement object. - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - lead_airplane.wings[0].wing_cross_sections[0], ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=lead_airplane.wings[ + 0 + ].wing_cross_sections[0], + ), # Define the tip wing cross section's movement object. - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - lead_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1.5, - sweeping_spacing="sine", ), ], ), ], ) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=lead_airplane.wings[ + 0 + ].wing_cross_sections[1], + sweeping_amplitude=15.0, + sweeping_period=1.5, + sweeping_spacing="sine", + ), + ], + ), + ], + ) # Create the trailing right airplane object. - right_airplane = ps.geometry.Airplane(name="Right Airplane", + right_airplane = ps.geometry.Airplane( + name="Right Airplane", # Specify the location of the right airplane's center of gravity. This is the # point around about which the solver will calculate the moments on the airplane. # These three values default to 0.0 meters. Note: these values are relative to # the global coordinate system fixed front left corner of the first airplane's # first wing's root wing cross section. - x_ref=x_spacing, y_ref=y_spacing, z_ref=0.0, wings=[ - ps.geometry.Wing(name="Main Wing", + x_ref=x_spacing, + y_ref=y_spacing, + z_ref=0.0, + wings=[ + ps.geometry.Wing( + name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. - x_le=x_spacing, y_le=y_spacing, symmetric=True, - chordwise_spacing="uniform", num_chordwise_panels=4, + x_le=x_spacing, + y_le=y_spacing, + symmetric=True, + chordwise_spacing="uniform", + num_chordwise_panels=4, wing_cross_sections=[ - ps.geometry.WingCrossSection(twist=5.0, chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, chord=1.5, - twist=5.0, airfoil=ps.geometry.Airfoil( - name="naca0012", ), ), ], ), ], ) + ps.geometry.WingCrossSection( + twist=5.0, + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], + ) # Now define the trailing right airplane's movement object. right_airplane_movement = ps.movement.AirplaneMovement( - base_airplane=right_airplane, wing_movements=[ - ps.movement.WingMovement(base_wing=right_airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section= - right_airplane.wings[0].wing_cross_sections[0], ), - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - right_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1.5, - sweeping_spacing="sine", ), ], ), ], ) + base_airplane=right_airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=right_airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=right_airplane.wings[ + 0 + ].wing_cross_sections[0], + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=right_airplane.wings[ + 0 + ].wing_cross_sections[1], + sweeping_amplitude=15.0, + sweeping_period=1.5, + sweeping_spacing="sine", + ), + ], + ), + ], + ) # Create the trailing left airplane object. - left_airplane = ps.geometry.Airplane(name="Left Airplane", + left_airplane = ps.geometry.Airplane( + name="Left Airplane", # Specify the location of the left airplane's center of gravity. This is the # point around about which the solver will calculate the moments on the airplane. # These three values default to 0.0 meters. Note: these values are relative to # the global coordinate system fixed front left corner of the first airplane's # first wing's root wing cross section. - x_ref=x_spacing, y_ref=-y_spacing, z_ref=0.0, wings=[ - ps.geometry.Wing(name="Main Wing", + x_ref=x_spacing, + y_ref=-y_spacing, + z_ref=0.0, + wings=[ + ps.geometry.Wing( + name="Main Wing", # Define the location of the leading edge of the wing relative to the # global coordinate system fixed front left corner of the first # airplane's first wing's root wing cross section. - x_le=x_spacing, y_le=-y_spacing, symmetric=True, - chordwise_spacing="uniform", num_chordwise_panels=4, + x_le=x_spacing, + y_le=-y_spacing, + symmetric=True, + chordwise_spacing="uniform", + num_chordwise_panels=4, wing_cross_sections=[ - ps.geometry.WingCrossSection(twist=5.0, chord=1.75, - airfoil=ps.geometry.Airfoil(name="naca0012", ), ), - ps.geometry.WingCrossSection(x_le=0.75, y_le=6.0, chord=1.5, - twist=5.0, airfoil=ps.geometry.Airfoil( - name="naca0012", ), ), ], ), ], ) + ps.geometry.WingCrossSection( + twist=5.0, + chord=1.75, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ps.geometry.WingCrossSection( + x_le=0.75, + y_le=6.0, + chord=1.5, + twist=5.0, + airfoil=ps.geometry.Airfoil( + name="naca0012", + ), + ), + ], + ), + ], + ) # Now define the trailing left airplane's movement object. left_airplane_movement = ps.movement.AirplaneMovement( - base_airplane=left_airplane, wing_movements=[ - ps.movement.WingMovement(base_wing=left_airplane.wings[0], - wing_cross_sections_movements=[ps.movement.WingCrossSectionMovement( - base_wing_cross_section= - left_airplane.wings[0].wing_cross_sections[0], ), - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - left_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=15.0, sweeping_period=1.5, - sweeping_spacing="sine", ), ], ), ], ) + base_airplane=left_airplane, + wing_movements=[ + ps.movement.WingMovement( + base_wing=left_airplane.wings[0], + wing_cross_sections_movements=[ + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=left_airplane.wings[ + 0 + ].wing_cross_sections[0], + ), + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=left_airplane.wings[ + 0 + ].wing_cross_sections[1], + sweeping_amplitude=15.0, + sweeping_period=1.5, + sweeping_spacing="sine", + ), + ], + ), + ], + ) # Define a new operating point object. This defines the state at which all the # airplanes objects are operating. Note: when assigning angles of attack to multiple # airplanes, it is better to set the operating point's angle of attack to zero, # and then use offset the twist values of all the wing cross sections to simulate # each aircraft having an angle of attack. - operating_point = ps.operating_point.OperatingPoint(velocity=10.0, alpha=0.0, ) + operating_point = ps.operating_point.OperatingPoint( + velocity=10.0, + alpha=0.0, + ) # Define the operating point's movement. operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=operating_point, ) + base_operating_point=operating_point, + ) # Delete the extraneous airplane and operating point objects, as these are now # contained within their respective movement objects. @@ -159,9 +271,14 @@ def runSolver(self): # Define the movement object. This contains each airplane's movement and the operating # point movement. movement = ps.movement.Movement( - airplane_movements=[lead_airplane_movement, right_airplane_movement, - left_airplane_movement, ], - operating_point_movement=operating_point_movement, num_cycles=2, ) + airplane_movements=[ + lead_airplane_movement, + right_airplane_movement, + left_airplane_movement, + ], + operating_point_movement=operating_point_movement, + num_cycles=2, + ) # Delete the extraneous airplane and operating point movement objects, as these are # now contained within the movement object. @@ -171,30 +288,38 @@ def runSolver(self): del operating_point_movement # Define the unsteady example problem. - problem = ps.problems.UnsteadyProblem(movement=movement, ) + problem = ps.problems.UnsteadyProblem( + movement=movement, + ) # Define a new solver. The available solver objects are the steady horseshoe vortex # lattice method solver, the steady ring vortex lattice method solver, and the # unsteady ring vortex lattice method solver. solver = ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( # Solvers just take in one attribute: the problem they are going to solve. - unsteady_problem=problem, ) + unsteady_problem=problem, + ) # Delete the extraneous pointer to the problem as it is now contained within the # solver. del problem # Run the example solver. - solver.run(prescribed_wake=False, ) + solver.run( + prescribed_wake=False, + ) # Call the software's animate function on the solver. This produces a GIF of the wake # being shed. The GIF is saved in the same directory as this script. Press "q", # after orienting the view, to begin the animation. - ps.output.animate(unsteady_solver=solver, scalar_type="lift", + ps.output.animate( + unsteady_solver=solver, + scalar_type="lift", show_wake_vortices=True, # The the animate function to not save the animation as file. This way, # the animation will still be displayed but not saved. This value defaults to # false. - save=False, ) + save=False, + ) # Compare the output you see with the expected outputs saved in the "docs/examples # expected output" directory. diff --git a/pterasoftware/movement.py b/pterasoftware/movement.py index d0043939..649c9887 100644 --- a/pterasoftware/movement.py +++ b/pterasoftware/movement.py @@ -57,8 +57,15 @@ class Movement: This class is not meant to be subclassed. """ - def __init__(self, airplane_movements, operating_point_movement, num_steps=None, - num_cycles=None, num_chords=None, delta_time=None, ): + def __init__( + self, + airplane_movements, + operating_point_movement, + num_steps=None, + num_cycles=None, + num_chords=None, + delta_time=None, + ): """This is the initialization method. :param airplane_movements: list of AirplaneMovement objects @@ -105,7 +112,8 @@ def __init__(self, airplane_movements, operating_point_movement, num_steps=None, if num_steps is not None or self.get_max_period() == 0: raise Exception( "Only specify the number of cycles if you haven't specified the " - "number of steps and the movement isn't static!") + "number of steps and the movement isn't static!" + ) self.num_cycles = num_cycles else: self.num_cycles = None @@ -116,7 +124,8 @@ def __init__(self, airplane_movements, operating_point_movement, num_steps=None, if num_steps is not None or self.get_max_period() != 0: raise Exception( "Only specify the number of chords if you haven't specified the " - "number of steps and the movement is static!") + "number of steps and the movement is static!" + ) self.num_chords = num_chords else: self.num_chords = None @@ -131,10 +140,11 @@ def __init__(self, airplane_movements, operating_point_movement, num_steps=None, # same chord length as the panels on the main wing. This is based on # the base airplane's reference chord length, its main wing's number # of chordwise panels, and its base operating point's velocity. - delta_times.append(airplane_movement.base_airplane.c_ref / - airplane_movement.base_airplane.wings[ - 0].num_chordwise_panels / - operating_point_movement.base_operating_point.velocity) + delta_times.append( + airplane_movement.base_airplane.c_ref + / airplane_movement.base_airplane.wings[0].num_chordwise_panels + / operating_point_movement.base_operating_point.velocity + ) # Set the delta time to be the average of the airplanes' ideal delta times. delta_time = sum(delta_times) / len(delta_times) @@ -162,7 +172,9 @@ def __init__(self, airplane_movements, operating_point_movement, num_steps=None, # that the wake extends back by some number of reference chord lengths. wake_length = self.num_chords * max_c_ref panel_length = ( - delta_time * self.operating_point_movement.base_operating_point.velocity) + delta_time + * self.operating_point_movement.base_operating_point.velocity + ) num_steps = math.ceil(wake_length / panel_length) else: @@ -184,13 +196,16 @@ def __init__(self, airplane_movements, operating_point_movement, num_steps=None, self.airplanes = [] for airplane_movement in self.airplane_movements: self.airplanes.append( - airplane_movement.generate_airplanes(num_steps=self.num_steps, - delta_time=self.delta_time)) + airplane_movement.generate_airplanes( + num_steps=self.num_steps, delta_time=self.delta_time + ) + ) # Generate a lists of operating point objects that are the steps through the # movement of this problem's operating point. self.operating_points = operating_point_movement.generate_operating_points( - num_steps=self.num_steps, delta_time=self.delta_time) + num_steps=self.num_steps, delta_time=self.delta_time + ) def get_max_period(self): """This method returns the longest period of this movement object's sub- @@ -208,8 +223,10 @@ def get_max_period(self): # The global max period is the maximum of the max airplane period and the max # operating point period. - return max(max_airplane_period, - self.operating_point_movement.get_max_period(), ) + return max( + max_airplane_period, + self.operating_point_movement.get_max_period(), + ) class AirplaneMovement: @@ -229,10 +246,20 @@ class AirplaneMovement: This class is not meant to be subclassed. """ - def __init__(self, base_airplane, wing_movements, x_ref_amplitude=0.0, - x_ref_period=0.0, x_ref_spacing="sine", y_ref_amplitude=0.0, - y_ref_period=0.0, y_ref_spacing="sine", z_ref_amplitude=0.0, - z_ref_period=0.0, z_ref_spacing="sine", ): + def __init__( + self, + base_airplane, + wing_movements, + x_ref_amplitude=0.0, + x_ref_period=0.0, + x_ref_spacing="sine", + y_ref_amplitude=0.0, + y_ref_period=0.0, + y_ref_spacing="sine", + z_ref_amplitude=0.0, + z_ref_period=0.0, + z_ref_spacing="sine", + ): """This is the initialization method. :param base_airplane: Airplane @@ -306,15 +333,23 @@ def generate_airplanes(self, num_steps=10, delta_time=0.1): if self.x_ref_spacing == "sine": # Create an array of points with a sinusoidal spacing. - x_ref_list = oscillating_sinspace(amplitude=self.x_ref_amplitude, - period=self.x_ref_period, base_value=self.x_ref_base, - num_steps=num_steps, delta_time=delta_time, ) + x_ref_list = oscillating_sinspace( + amplitude=self.x_ref_amplitude, + period=self.x_ref_period, + base_value=self.x_ref_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.x_ref_spacing == "uniform": # Create an array of points with a uniform spacing. - x_ref_list = oscillating_linspace(amplitude=self.x_ref_amplitude, - period=self.x_ref_period, base_value=self.x_ref_base, - num_steps=num_steps, delta_time=delta_time, ) + x_ref_list = oscillating_linspace( + amplitude=self.x_ref_amplitude, + period=self.x_ref_period, + base_value=self.x_ref_base, + num_steps=num_steps, + delta_time=delta_time, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -324,15 +359,23 @@ def generate_airplanes(self, num_steps=10, delta_time=0.1): if self.y_ref_spacing == "sine": # Create an array of points with a sinusoidal spacing. - y_ref_list = oscillating_sinspace(amplitude=self.y_ref_amplitude, - period=self.y_ref_period, base_value=self.y_ref_base, - num_steps=num_steps, delta_time=delta_time, ) + y_ref_list = oscillating_sinspace( + amplitude=self.y_ref_amplitude, + period=self.y_ref_period, + base_value=self.y_ref_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.y_ref_spacing == "uniform": # Create an array of points with a uniform spacing. - y_ref_list = oscillating_linspace(amplitude=self.y_ref_amplitude, - period=self.y_ref_period, base_value=self.y_ref_base, - num_steps=num_steps, delta_time=delta_time, ) + y_ref_list = oscillating_linspace( + amplitude=self.y_ref_amplitude, + period=self.y_ref_period, + base_value=self.y_ref_base, + num_steps=num_steps, + delta_time=delta_time, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -342,15 +385,23 @@ def generate_airplanes(self, num_steps=10, delta_time=0.1): if self.z_ref_spacing == "sine": # Create an array of points with a sinusoidal spacing. - z_ref_list = oscillating_sinspace(amplitude=self.z_ref_amplitude, - period=self.z_ref_period, base_value=self.z_ref_base, - num_steps=num_steps, delta_time=delta_time, ) + z_ref_list = oscillating_sinspace( + amplitude=self.z_ref_amplitude, + period=self.z_ref_period, + base_value=self.z_ref_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.z_ref_spacing == "uniform": # Create an array of points with a uniform spacing. - z_ref_list = oscillating_linspace(amplitude=self.z_ref_amplitude, - period=self.z_ref_period, base_value=self.z_ref_base, - num_steps=num_steps, delta_time=delta_time, ) + z_ref_list = oscillating_linspace( + amplitude=self.z_ref_amplitude, + period=self.z_ref_period, + base_value=self.z_ref_base, + num_steps=num_steps, + delta_time=delta_time, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -364,8 +415,8 @@ def generate_airplanes(self, num_steps=10, delta_time=0.1): for wing_movement_location, wing_movement in enumerate(self.wing_movements): # Generate this wing's vector of other wing's based on its movement. this_wings_list_of_wings = np.array( - wing_movement.generate_wings(num_steps=num_steps, - delta_time=delta_time)) + wing_movement.generate_wings(num_steps=num_steps, delta_time=delta_time) + ) # Add this vector the airplane's array of wing objects. wings[wing_movement_location, :] = this_wings_list_of_wings @@ -385,8 +436,9 @@ def generate_airplanes(self, num_steps=10, delta_time=0.1): these_wings = wings[:, step] # Make a new airplane object for this time step. - this_airplane = geometry.Airplane(name=name, x_ref=x_ref, y_ref=y_ref, - z_ref=z_ref, wings=these_wings) + this_airplane = geometry.Airplane( + name=name, x_ref=x_ref, y_ref=y_ref, z_ref=z_ref, wings=these_wings + ) # Add this new object to the list of airplanes. airplanes.append(this_airplane) @@ -407,8 +459,12 @@ def get_max_period(self): wing_movement_max_periods.append(wing_movement.get_max_period()) max_wing_movement_period = max(wing_movement_max_periods) - max_period = max(max_wing_movement_period, self.x_ref_period, self.y_ref_period, - self.z_ref_period, ) + max_period = max( + max_wing_movement_period, + self.x_ref_period, + self.y_ref_period, + self.z_ref_period, + ) return max_period @@ -430,10 +486,20 @@ class WingMovement: This class is not meant to be subclassed. """ - def __init__(self, base_wing, wing_cross_sections_movements, x_le_amplitude=0.0, - x_le_period=0.0, x_le_spacing="sine", y_le_amplitude=0.0, y_le_period=0.0, - y_le_spacing="sine", z_le_amplitude=0.0, z_le_period=0.0, - z_le_spacing="sine", ): + def __init__( + self, + base_wing, + wing_cross_sections_movements, + x_le_amplitude=0.0, + x_le_period=0.0, + x_le_spacing="sine", + y_le_amplitude=0.0, + y_le_period=0.0, + y_le_spacing="sine", + z_le_amplitude=0.0, + z_le_period=0.0, + z_le_spacing="sine", + ): """This is the initialization method. :param base_wing: Wing @@ -502,15 +568,23 @@ def generate_wings(self, num_steps=10, delta_time=0.1): if self.x_le_spacing == "sine": # Create an array of points with a sinusoidal spacing. - x_le_list = oscillating_sinspace(amplitude=self.x_le_amplitude, - period=self.x_le_period, base_value=self.x_le_base, num_steps=num_steps, - delta_time=delta_time, ) + x_le_list = oscillating_sinspace( + amplitude=self.x_le_amplitude, + period=self.x_le_period, + base_value=self.x_le_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.x_le_spacing == "uniform": # Create an array of points with a uniform spacing. - x_le_list = oscillating_linspace(amplitude=self.x_le_amplitude, - period=self.x_le_period, base_value=self.x_le_base, num_steps=num_steps, - delta_time=delta_time, ) + x_le_list = oscillating_linspace( + amplitude=self.x_le_amplitude, + period=self.x_le_period, + base_value=self.x_le_base, + num_steps=num_steps, + delta_time=delta_time, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -520,15 +594,23 @@ def generate_wings(self, num_steps=10, delta_time=0.1): if self.y_le_spacing == "sine": # Create an array of points with a sinusoidal spacing. - y_le_list = oscillating_sinspace(amplitude=self.y_le_amplitude, - period=self.y_le_period, base_value=self.y_le_base, num_steps=num_steps, - delta_time=delta_time, ) + y_le_list = oscillating_sinspace( + amplitude=self.y_le_amplitude, + period=self.y_le_period, + base_value=self.y_le_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.y_le_spacing == "uniform": # Create an array of points with a uniform spacing. - y_le_list = oscillating_linspace(amplitude=self.y_le_amplitude, - period=self.y_le_period, base_value=self.y_le_base, num_steps=num_steps, - delta_time=delta_time, ) + y_le_list = oscillating_linspace( + amplitude=self.y_le_amplitude, + period=self.y_le_period, + base_value=self.y_le_base, + num_steps=num_steps, + delta_time=delta_time, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -538,15 +620,23 @@ def generate_wings(self, num_steps=10, delta_time=0.1): if self.z_le_spacing == "sine": # Create an array of points with a sinusoidal spacing. - z_le_list = oscillating_sinspace(amplitude=self.z_le_amplitude, - period=self.z_le_period, base_value=self.z_le_base, num_steps=num_steps, - delta_time=delta_time, ) + z_le_list = oscillating_sinspace( + amplitude=self.z_le_amplitude, + period=self.z_le_period, + base_value=self.z_le_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.z_le_spacing == "uniform": # Create an array of points with a uniform spacing. - z_le_list = oscillating_linspace(amplitude=self.z_le_amplitude, - period=self.z_le_period, base_value=self.z_le_base, num_steps=num_steps, - delta_time=delta_time, ) + z_le_list = oscillating_linspace( + amplitude=self.z_le_amplitude, + period=self.z_le_period, + base_value=self.z_le_base, + num_steps=num_steps, + delta_time=delta_time, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -555,16 +645,18 @@ def generate_wings(self, num_steps=10, delta_time=0.1): # Create an empty array that will hold each of the wing's wing cross # section's vector of other wing cross section's based its movement. wing_cross_sections = np.empty( - (len(self.wing_cross_section_movements), num_steps), dtype=object) + (len(self.wing_cross_section_movements), num_steps), dtype=object + ) # Initialize a variable to hold the inner wing cross section's list of wing # cross sections for each time step. last_wing_cross_section_time_histories = None # Iterate through the wing cross section movement locations. - for (wing_cross_section_movement_location, - wing_cross_section_movement,) in enumerate( - self.wing_cross_section_movements): + for ( + wing_cross_section_movement_location, + wing_cross_section_movement, + ) in enumerate(self.wing_cross_section_movements): wing_is_vertical = False # Check if this is this wing's root cross section. @@ -572,13 +664,17 @@ def generate_wings(self, num_steps=10, delta_time=0.1): # Get the root cross section's sweeping and heaving attributes. first_wing_cross_section_movement_sweeping_amplitude = ( - wing_cross_section_movement.sweeping_amplitude) + wing_cross_section_movement.sweeping_amplitude + ) first_wing_cross_section_movement_sweeping_period = ( - wing_cross_section_movement.sweeping_period) + wing_cross_section_movement.sweeping_period + ) first_wing_cross_section_movement_heaving_amplitude = ( - wing_cross_section_movement.heaving_amplitude) + wing_cross_section_movement.heaving_amplitude + ) first_wing_cross_section_movement_heaving_period = ( - wing_cross_section_movement.heaving_period) + wing_cross_section_movement.heaving_period + ) # Check that the root cross section is not sweeping or heaving. assert first_wing_cross_section_movement_sweeping_amplitude == 0 @@ -598,7 +694,8 @@ def generate_wings(self, num_steps=10, delta_time=0.1): else: this_base_wing_cross_section = ( - wing_cross_section_movement.base_wing_cross_section) + wing_cross_section_movement.base_wing_cross_section + ) this_x_le = this_base_wing_cross_section.x_le this_y_le = this_base_wing_cross_section.y_le @@ -619,22 +716,32 @@ def generate_wings(self, num_steps=10, delta_time=0.1): # Find the span between this wing cross section and the inner wing # cross section. - wing_cross_section_span = np.sqrt((this_x_le - last_x_les[0]) ** 2 + ( - this_y_le - last_y_les[0]) ** 2 + (this_z_le - last_z_les[ - 0]) ** 2) + wing_cross_section_span = np.sqrt( + (this_x_le - last_x_les[0]) ** 2 + + (this_y_le - last_y_les[0]) ** 2 + + (this_z_le - last_z_les[0]) ** 2 + ) if this_y_le != last_y_les[0]: # Find the base sweep angle of this wing cross section compared # to the inner wing cross section at the first time step. - base_wing_cross_section_sweep = (np.arctan( - (this_z_le - last_z_les[0]) / ( - this_y_le - last_y_les[0])) * 180 / np.pi) + base_wing_cross_section_sweep = ( + np.arctan( + (this_z_le - last_z_les[0]) / (this_y_le - last_y_les[0]) + ) + * 180 + / np.pi + ) # Find the base heave angle of this wing cross section compared # to the inner wing cross section at the first time step. - base_wing_cross_section_heave = (np.arctan( - (this_x_le - last_x_les[0]) / ( - this_y_le - last_y_les[0])) * 180 / np.pi) + base_wing_cross_section_heave = ( + np.arctan( + (this_x_le - last_x_les[0]) / (this_y_le - last_y_les[0]) + ) + * 180 + / np.pi + ) else: base_wing_cross_section_sweep = 0.0 base_wing_cross_section_heave = 0.0 @@ -644,21 +751,28 @@ def generate_wings(self, num_steps=10, delta_time=0.1): # each time step based on its movement. this_wing_cross_sections_list_of_wing_cross_sections = np.array( wing_cross_section_movement.generate_wing_cross_sections( - num_steps=num_steps, delta_time=delta_time, + num_steps=num_steps, + delta_time=delta_time, cross_section_span=wing_cross_section_span, cross_section_sweep=base_wing_cross_section_sweep, cross_section_heave=base_wing_cross_section_heave, - last_x_les=last_x_les, last_y_les=last_y_les, last_z_les=last_z_les, - wing_is_vertical=wing_is_vertical, )) + last_x_les=last_x_les, + last_y_les=last_y_les, + last_z_les=last_z_les, + wing_is_vertical=wing_is_vertical, + ) + ) # Add this vector the wing's array of wing cross section objects. wing_cross_sections[wing_cross_section_movement_location, :] = ( - this_wing_cross_sections_list_of_wing_cross_sections) + this_wing_cross_sections_list_of_wing_cross_sections + ) # Update the inner wing cross section's list of wing cross sections for # each time step. last_wing_cross_section_time_histories = ( - this_wing_cross_sections_list_of_wing_cross_sections) + this_wing_cross_sections_list_of_wing_cross_sections + ) # Create an empty list of wings. wings = [] @@ -678,10 +792,16 @@ def generate_wings(self, num_steps=10, delta_time=0.1): cross_sections = wing_cross_sections[:, step] # Make a new wing object for this time step. - this_wing = geometry.Wing(name=name, x_le=x_le, y_le=y_le, z_le=z_le, - wing_cross_sections=cross_sections, symmetric=symmetric, + this_wing = geometry.Wing( + name=name, + x_le=x_le, + y_le=y_le, + z_le=z_le, + wing_cross_sections=cross_sections, + symmetric=symmetric, num_chordwise_panels=num_chordwise_panels, - chordwise_spacing=chordwise_spacing, ) + chordwise_spacing=chordwise_spacing, + ) # Add this new object to the list of wings. wings.append(this_wing) @@ -700,12 +820,18 @@ def get_max_period(self): wing_cross_section_movement_max_periods = [] for wing_cross_section_movement in self.wing_cross_section_movements: wing_cross_section_movement_max_periods.append( - wing_cross_section_movement.get_max_period()) + wing_cross_section_movement.get_max_period() + ) max_wing_cross_section_movement_period = max( - wing_cross_section_movement_max_periods) + wing_cross_section_movement_max_periods + ) - max_period = max(max_wing_cross_section_movement_period, self.x_le_period, - self.y_le_period, self.z_le_period, ) + max_period = max( + max_wing_cross_section_movement_period, + self.x_le_period, + self.y_le_period, + self.z_le_period, + ) return max_period @@ -728,11 +854,22 @@ class WingCrossSectionMovement: This class is not meant to be subclassed. """ - def __init__(self, base_wing_cross_section, sweeping_amplitude=0.0, - sweeping_period=0.0, sweeping_spacing="sine", custom_sweep_function=None, - pitching_amplitude=0.0, pitching_period=0.0, pitching_spacing="sine", - custom_pitch_function=None, heaving_amplitude=0.0, heaving_period=0.0, - heaving_spacing="sine", custom_heave_function=None, ): + def __init__( + self, + base_wing_cross_section, + sweeping_amplitude=0.0, + sweeping_period=0.0, + sweeping_spacing="sine", + custom_sweep_function=None, + pitching_amplitude=0.0, + pitching_period=0.0, + pitching_spacing="sine", + custom_pitch_function=None, + heaving_amplitude=0.0, + heaving_period=0.0, + heaving_spacing="sine", + custom_heave_function=None, + ): """This is the initialization method. :param base_wing_cross_section: WingCrossSection @@ -827,11 +964,21 @@ def __init__(self, base_wing_cross_section, sweeping_amplitude=0.0, self.z_le_base = self.base_wing_cross_section.z_le self.twist_base = self.base_wing_cross_section.twist self.control_surface_deflection_base = ( - self.base_wing_cross_section.control_surface_deflection) - - def generate_wing_cross_sections(self, num_steps=10, delta_time=0.1, - last_x_les=None, last_y_les=None, last_z_les=None, wing_is_vertical=False, - cross_section_span=0.0, cross_section_sweep=0.0, cross_section_heave=0.0, ): + self.base_wing_cross_section.control_surface_deflection + ) + + def generate_wing_cross_sections( + self, + num_steps=10, + delta_time=0.1, + last_x_les=None, + last_y_les=None, + last_z_les=None, + wing_is_vertical=False, + cross_section_span=0.0, + cross_section_sweep=0.0, + cross_section_heave=0.0, + ): """This method creates the wing cross section objects at each time current_step, and groups them into a list. @@ -879,28 +1026,41 @@ def generate_wing_cross_sections(self, num_steps=10, delta_time=0.1, if self.sweeping_spacing == "sine": # Create an array of points with a sinusoidal spacing. - sweeping_list = oscillating_sinspace(amplitude=self.sweeping_amplitude, - period=self.sweeping_period, base_value=cross_section_sweep, - num_steps=num_steps, delta_time=delta_time, ) + sweeping_list = oscillating_sinspace( + amplitude=self.sweeping_amplitude, + period=self.sweeping_period, + base_value=cross_section_sweep, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.sweeping_spacing == "uniform": # Create an array of points with a uniform spacing. - sweeping_list = oscillating_linspace(amplitude=self.sweeping_amplitude, - period=self.sweeping_period, base_value=cross_section_sweep, - num_steps=num_steps, delta_time=delta_time, ) + sweeping_list = oscillating_linspace( + amplitude=self.sweeping_amplitude, + period=self.sweeping_period, + base_value=cross_section_sweep, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.sweeping_spacing == "custom": # Raise an exception if the user did not declare a custom sweep function. if self.custom_sweep_function is None: raise Exception( "You can't declare custom sweep spacing without providing a " - "custom sweep function.") + "custom sweep function." + ) # Create an array of points with a uniform spacing. - sweeping_list = oscillating_customspace(amplitude=self.sweeping_amplitude, - period=self.sweeping_period, base_value=cross_section_sweep, - num_steps=num_steps, delta_time=delta_time, - custom_function=self.custom_sweep_function, ) + sweeping_list = oscillating_customspace( + amplitude=self.sweeping_amplitude, + period=self.sweeping_period, + base_value=cross_section_sweep, + num_steps=num_steps, + delta_time=delta_time, + custom_function=self.custom_sweep_function, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -910,28 +1070,41 @@ def generate_wing_cross_sections(self, num_steps=10, delta_time=0.1, if self.pitching_spacing == "sine": # Create an array of points with a sinusoidal spacing. - pitching_list = oscillating_sinspace(amplitude=self.pitching_amplitude, - period=self.pitching_period, base_value=self.pitching_base, - num_steps=num_steps, delta_time=delta_time, ) + pitching_list = oscillating_sinspace( + amplitude=self.pitching_amplitude, + period=self.pitching_period, + base_value=self.pitching_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.pitching_spacing == "uniform": # Create an array of points with a uniform spacing. - pitching_list = oscillating_linspace(amplitude=self.pitching_amplitude, - period=self.pitching_period, base_value=self.pitching_base, - num_steps=num_steps, delta_time=delta_time, ) + pitching_list = oscillating_linspace( + amplitude=self.pitching_amplitude, + period=self.pitching_period, + base_value=self.pitching_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.pitching_spacing == "custom": # Raise an exception if the user did not declare a custom pitch function. if self.custom_pitch_function is None: raise Exception( "You can't declare custom pitch spacing without providing a " - "custom pitch function.") + "custom pitch function." + ) # Create an array of points with a uniform spacing. - pitching_list = oscillating_customspace(amplitude=self.pitching_amplitude, - period=self.pitching_period, base_value=self.pitching_base, - num_steps=num_steps, delta_time=delta_time, - custom_function=self.custom_pitch_function, ) + pitching_list = oscillating_customspace( + amplitude=self.pitching_amplitude, + period=self.pitching_period, + base_value=self.pitching_base, + num_steps=num_steps, + delta_time=delta_time, + custom_function=self.custom_pitch_function, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -941,28 +1114,41 @@ def generate_wing_cross_sections(self, num_steps=10, delta_time=0.1, if self.heaving_spacing == "sine": # Create an array of points with a sinusoidal spacing. - heaving_list = oscillating_sinspace(amplitude=self.heaving_amplitude, - period=self.heaving_period, base_value=cross_section_heave, - num_steps=num_steps, delta_time=delta_time, ) + heaving_list = oscillating_sinspace( + amplitude=self.heaving_amplitude, + period=self.heaving_period, + base_value=cross_section_heave, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.heaving_spacing == "uniform": # Create an array of points with a uniform spacing. - heaving_list = oscillating_linspace(amplitude=self.heaving_amplitude, - period=self.heaving_period, base_value=cross_section_heave, - num_steps=num_steps, delta_time=delta_time, ) + heaving_list = oscillating_linspace( + amplitude=self.heaving_amplitude, + period=self.heaving_period, + base_value=cross_section_heave, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.heaving_spacing == "custom": # Raise an exception if the user did not declare a custom heave function. if self.custom_heave_function is None: raise Exception( "You can't declare custom heave spacing without providing a " - "custom heave function.") + "custom heave function." + ) # Create an array of points with custom spacing. - heaving_list = oscillating_customspace(amplitude=self.heaving_amplitude, - period=self.heaving_period, base_value=cross_section_heave, - num_steps=num_steps, delta_time=delta_time, - custom_function=self.custom_heave_function, ) + heaving_list = oscillating_customspace( + amplitude=self.heaving_amplitude, + period=self.heaving_period, + base_value=cross_section_heave, + num_steps=num_steps, + delta_time=delta_time, + custom_function=self.custom_heave_function, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -981,11 +1167,14 @@ def generate_wing_cross_sections(self, num_steps=10, delta_time=0.1, # convert the lists of sweep, pitch, and heave values to radians before # passing them into numpy's trigonometry functions. x_le_list = last_x_les + cross_section_span * np.cos( - sweeping_list * np.pi / 180) * np.sin(heaving_list * np.pi / 180) + sweeping_list * np.pi / 180 + ) * np.sin(heaving_list * np.pi / 180) y_le_list = last_y_les + cross_section_span * np.cos( - sweeping_list * np.pi / 180) * np.cos(heaving_list * np.pi / 180) + sweeping_list * np.pi / 180 + ) * np.cos(heaving_list * np.pi / 180) z_le_list = last_z_les + cross_section_span * np.sin( - sweeping_list * np.pi / 180) + sweeping_list * np.pi / 180 + ) twist_list = pitching_list # Create an empty list of wing cross sections. @@ -997,7 +1186,8 @@ def generate_wing_cross_sections(self, num_steps=10, delta_time=0.1, control_surface_deflection = self.control_surface_deflection_base control_surface_type = self.base_wing_cross_section.control_surface_type control_surface_hinge_point = ( - self.base_wing_cross_section.control_surface_hinge_point) + self.base_wing_cross_section.control_surface_hinge_point + ) num_spanwise_panels = self.base_wing_cross_section.num_spanwise_panels spanwise_spacing = self.base_wing_cross_section.spanwise_spacing @@ -1010,13 +1200,19 @@ def generate_wing_cross_sections(self, num_steps=10, delta_time=0.1, twist = twist_list[step] # Make a new wing cross section object for this time step. - this_wing_cross_section = geometry.WingCrossSection(x_le=x_le, y_le=y_le, - z_le=z_le, chord=chord, twist=twist, airfoil=airfoil, + this_wing_cross_section = geometry.WingCrossSection( + x_le=x_le, + y_le=y_le, + z_le=z_le, + chord=chord, + twist=twist, + airfoil=airfoil, control_surface_type=control_surface_type, control_surface_hinge_point=control_surface_hinge_point, control_surface_deflection=control_surface_deflection, num_spanwise_panels=num_spanwise_panels, - spanwise_spacing=spanwise_spacing, ) + spanwise_spacing=spanwise_spacing, + ) # Add this new object to the list of wing cross sections. wing_cross_sections.append(this_wing_cross_section) @@ -1031,8 +1227,9 @@ def get_max_period(self): The longest period in seconds. """ - max_period = max(self.sweeping_period, self.pitching_period, - self.heaving_period) + max_period = max( + self.sweeping_period, self.pitching_period, self.heaving_period + ) return max_period @@ -1055,8 +1252,13 @@ class OperatingPointMovement: This class is not meant to be subclassed. """ - def __init__(self, base_operating_point, velocity_amplitude=0.0, - velocity_period=0.0, velocity_spacing="sine", ): + def __init__( + self, + base_operating_point, + velocity_amplitude=0.0, + velocity_period=0.0, + velocity_spacing="sine", + ): """This is the initialization method. :param base_operating_point: OperatingPoint @@ -1098,15 +1300,23 @@ def generate_operating_points(self, num_steps=10, delta_time=0.1): if self.velocity_spacing == "sine": # Create an array of points with a sinusoidal spacing. - velocity_list = oscillating_sinspace(amplitude=self.velocity_amplitude, - period=self.velocity_period, base_value=self.velocity_base, - num_steps=num_steps, delta_time=delta_time, ) + velocity_list = oscillating_sinspace( + amplitude=self.velocity_amplitude, + period=self.velocity_period, + base_value=self.velocity_base, + num_steps=num_steps, + delta_time=delta_time, + ) elif self.velocity_spacing == "uniform": # Create an array of points with a uniform spacing. - velocity_list = oscillating_linspace(amplitude=self.velocity_amplitude, - period=self.velocity_period, base_value=self.velocity_base, - num_steps=num_steps, delta_time=delta_time, ) + velocity_list = oscillating_linspace( + amplitude=self.velocity_amplitude, + period=self.velocity_period, + base_value=self.velocity_base, + num_steps=num_steps, + delta_time=delta_time, + ) else: # Throw an exception if the spacing value is not "sine" or "uniform". @@ -1126,8 +1336,9 @@ def generate_operating_points(self, num_steps=10, delta_time=0.1): velocity = velocity_list[step] # Make a new operating point object for this time step. - this_operating_point = operating_point.OperatingPoint(density=density, - velocity=velocity, alpha=alpha, beta=beta) + this_operating_point = operating_point.OperatingPoint( + density=density, velocity=velocity, alpha=alpha, beta=beta + ) # Add this new object to the list of operating points. operating_points.append(this_operating_point) @@ -1223,8 +1434,9 @@ def oscillating_linspace(amplitude, period, base_value, num_steps, delta_time): return a * signal.sawtooth((b * times + h), 0.5) + k -def oscillating_customspace(amplitude, period, base_value, num_steps, delta_time, - custom_function): +def oscillating_customspace( + amplitude, period, base_value, num_steps, delta_time, custom_function +): """This function returns a 1D array of values that are calculated by inputting a vector of linearly spaced time steps into a custom function. diff --git a/pterasoftware/operating_point.py b/pterasoftware/operating_point.py index 9c8da28a..ab588815 100644 --- a/pterasoftware/operating_point.py +++ b/pterasoftware/operating_point.py @@ -43,8 +43,15 @@ class OperatingPoint: This class is not meant to be subclassed. """ - def __init__(self, density=1.225, velocity=10.0, alpha=5.0, beta=0.0, - external_thrust=0.0, nu=15.06e-6, ): + def __init__( + self, + density=1.225, + velocity=10.0, + alpha=5.0, + beta=0.0, + external_thrust=0.0, + nu=15.06e-6, + ): """This is the initialization method. :param density: float, optional @@ -88,7 +95,7 @@ def calculate_dynamic_pressure(self): """ # Calculate and return the freestream dynamic pressure - dynamic_pressure = 0.5 * self.density * self.velocity ** 2 + dynamic_pressure = 0.5 * self.density * self.velocity**2 return dynamic_pressure def calculate_rotation_matrix_wind_to_geometry(self): @@ -106,19 +113,28 @@ def calculate_rotation_matrix_wind_to_geometry(self): eye = np.eye(3) alpha_rotation = np.array( - [[cos_alpha, 0, -sin_alpha], [0, 1, 0], [sin_alpha, 0, cos_alpha]]) + [[cos_alpha, 0, -sin_alpha], [0, 1, 0], [sin_alpha, 0, cos_alpha]] + ) beta_rotation = np.array( - [[cos_beta, -sin_beta, 0], [sin_beta, cos_beta, 0], [0, 0, 1]]) + [[cos_beta, -sin_beta, 0], [sin_beta, cos_beta, 0], [0, 0, 1]] + ) # Flip the axes because in geometry axes x is downstream by convention, # while in wind axes x is upstream by convention. Same with z being up/down # respectively. - axes_flip = np.array([[-1, 0, 0], [0, 1, 0], [0, 0, -1], ]) + axes_flip = np.array( + [ + [-1, 0, 0], + [0, 1, 0], + [0, 0, -1], + ] + ) # Calculate and return the rotation matrix to convert wind axes to geometry # axes. rotation_matrix_wind_axes_to_geometry_axes = ( - axes_flip @ alpha_rotation @ beta_rotation @ eye) + axes_flip @ alpha_rotation @ beta_rotation @ eye + ) return rotation_matrix_wind_axes_to_geometry_axes def calculate_freestream_direction_geometry_axes(self): @@ -131,8 +147,9 @@ def calculate_freestream_direction_geometry_axes(self): velocity_direction_wind_axes = np.array([-1, 0, 0]) velocity_direction_geometry_axes = ( - self.calculate_rotation_matrix_wind_to_geometry() @ - velocity_direction_wind_axes) + self.calculate_rotation_matrix_wind_to_geometry() + @ velocity_direction_wind_axes + ) return velocity_direction_geometry_axes def calculate_freestream_velocity_geometry_axes(self): @@ -144,5 +161,6 @@ def calculate_freestream_velocity_geometry_axes(self): """ freestream_velocity_geometry_axes = ( - self.calculate_freestream_direction_geometry_axes() * self.velocity) + self.calculate_freestream_direction_geometry_axes() * self.velocity + ) return freestream_velocity_geometry_axes diff --git a/pterasoftware/output.py b/pterasoftware/output.py index c140e431..0ad42110 100644 --- a/pterasoftware/output.py +++ b/pterasoftware/output.py @@ -58,9 +58,28 @@ # For the figure lines, use the "Prism" qualitative color map from # carto.com/carto-colors. -prism = ["#5F4690", "#1D6996", "#38A6A5", "#0F8554", "#73AF48", "#EDAD08", "#E17C05", - "#CC503E", "#94346E", "#6F4070", "#994E95", "#666666", ] -[drag_color, side_color, lift_color, roll_color, pitch_color, yaw_color, ] = prism[3:9] +prism = [ + "#5F4690", + "#1D6996", + "#38A6A5", + "#0F8554", + "#73AF48", + "#EDAD08", + "#E17C05", + "#CC503E", + "#94346E", + "#6F4070", + "#994E95", + "#666666", +] +[ + drag_color, + side_color, + lift_color, + roll_color, + pitch_color, + yaw_color, +] = prism[3:9] # Set constants for the color maps, scalar bars, and text boxes. color_map_num_sig = 3 @@ -83,8 +102,13 @@ marker_spacing = 1.0 / num_markers -def draw(solver, scalar_type=None, show_streamlines=False, show_wake_vortices=False, - save=False, ): +def draw( + solver, + scalar_type=None, + show_streamlines=False, + show_wake_vortices=False, + save=False, +): """Draw the geometry of the airplanes in a solver object. Citation: @@ -117,9 +141,10 @@ def draw(solver, scalar_type=None, show_streamlines=False, show_wake_vortices=Fa plotter = pv.Plotter(window_size=window_size, lighting=None) # Get the solver's geometry. - if isinstance(solver, - unsteady_ring_vortex_lattice_method - .UnsteadyRingVortexLatticeMethodSolver, ): + if isinstance( + solver, + unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver, + ): draw_step = solver.num_steps - 1 airplanes = solver.steady_problems[draw_step].airplanes @@ -127,15 +152,22 @@ def draw(solver, scalar_type=None, show_streamlines=False, show_wake_vortices=Fa # plot them. if show_wake_vortices: wake_ring_vortex_surfaces = get_wake_ring_vortex_surfaces(solver, draw_step) - plotter.add_mesh(wake_ring_vortex_surfaces, show_edges=True, - smooth_shading=False, color=wake_vortex_color, ) + plotter.add_mesh( + wake_ring_vortex_surfaces, + show_edges=True, + smooth_shading=False, + color=wake_vortex_color, + ) else: airplanes = solver.airplanes # Check if the user wants to show scalars on the wing panels. show_scalars = False if ( - scalar_type == "induced drag" or scalar_type == "side force" or scalar_type == "lift"): + scalar_type == "induced drag" + or scalar_type == "side force" + or scalar_type == "lift" + ): show_scalars = True # Get the panel surfaces. @@ -156,20 +188,35 @@ def draw(solver, scalar_type=None, show_streamlines=False, show_wake_vortices=Fa color_map = sequential_color_map c_min = max( np.mean(these_scalars) - color_map_num_sig * np.std(these_scalars), - np.min(these_scalars), ) + np.min(these_scalars), + ) c_max = min( np.mean(these_scalars) + color_map_num_sig * np.std(these_scalars), - np.max(these_scalars), ) + np.max(these_scalars), + ) else: color_map = diverging_color_map c_min = -color_map_num_sig * np.std(these_scalars) c_max = color_map_num_sig * np.std(these_scalars) - plot_scalars(plotter, these_scalars, scalar_type, min_scalar, max_scalar, - color_map, c_min, c_max, panel_surfaces, ) + plot_scalars( + plotter, + these_scalars, + scalar_type, + min_scalar, + max_scalar, + color_map, + c_min, + c_max, + panel_surfaces, + ) else: - plotter.add_mesh(panel_surfaces, show_edges=True, color=panel_color, - smooth_shading=False, ) + plotter.add_mesh( + panel_surfaces, + show_edges=True, + color=panel_color, + smooth_shading=False, + ) # Check if the user wants to plot streamlines. if show_streamlines: @@ -191,30 +238,52 @@ def draw(solver, scalar_type=None, show_streamlines=False, show_wake_vortices=Fa last_point = streamline_point_column[point_index - 1, :] # Add a line to make this segment of the streamline. - plotter.add_mesh(pv.Line(last_point, point, ), show_edges=True, - color=streamline_color, line_width=2, smooth_shading=False, ) + plotter.add_mesh( + pv.Line( + last_point, + point, + ), + show_edges=True, + color=streamline_color, + line_width=2, + smooth_shading=False, + ) # Set the plotter's background color and camera position. Then show the plotter # so the user can adjust the camera position and window. When the user closes the # window, the plotter object won't be closed so that it can be saved as an image # if the user wants. plotter.set_background(color=plotter_background_color) - plotter.show(cpos=(-1, -1, 1), full_screen=False, auto_close=False, ) + plotter.show( + cpos=(-1, -1, 1), + full_screen=False, + auto_close=False, + ) # If the user wants to save the image, take a screenshot, convert it into an # image object, and save it as a WebP. if save: image = webp.Image.fromarray( - plotter.screenshot(filename=None, transparent_background=True, - return_img=True, )) - webp.save_image(img=image, file_path="Draw.webp", lossless=False, - quality=quality) + plotter.screenshot( + filename=None, + transparent_background=True, + return_img=True, + ) + ) + webp.save_image( + img=image, file_path="Draw.webp", lossless=False, quality=quality + ) # Close all the plotters. pv.close_all() -def animate(unsteady_solver, scalar_type=None, show_wake_vortices=False, save=False, ): +def animate( + unsteady_solver, + scalar_type=None, + show_wake_vortices=False, + save=False, +): """Create an animation of a solver's geometries. :param unsteady_solver: UnsteadyRingVortexLatticeMethodSolver @@ -262,14 +331,21 @@ def animate(unsteady_solver, scalar_type=None, show_wake_vortices=False, save=Fa if save: # Add text to the animation that display's its speed relative to the true # speed. - plotter.add_text(text="Speed: " + str(round(100 * speed)) + "%", - position=text_speed_position, font_size=text_font_size, viewport=True, - color=text_color, ) + plotter.add_text( + text="Speed: " + str(round(100 * speed)) + "%", + position=text_speed_position, + font_size=text_font_size, + viewport=True, + color=text_color, + ) # Check if the user wants to show scalars on the wing panels. show_scalars = False if ( - scalar_type == "induced drag" or scalar_type == "side force" or scalar_type == "lift"): + scalar_type == "induced drag" + or scalar_type == "side force" + or scalar_type == "lift" + ): show_scalars = True # Initialize variables to hold the problems' scalars and their attributes. @@ -291,10 +367,14 @@ def animate(unsteady_solver, scalar_type=None, show_wake_vortices=False, save=Fa # have different signs (diverging color map). if np.sign(np.min(all_scalars)) == np.sign(np.max(all_scalars)): color_map = sequential_color_map - c_min = max(np.mean(all_scalars) - color_map_num_sig * np.std(all_scalars), - np.min(all_scalars), ) - c_max = min(np.mean(all_scalars) + color_map_num_sig * np.std(all_scalars), - np.max(all_scalars), ) + c_min = max( + np.mean(all_scalars) - color_map_num_sig * np.std(all_scalars), + np.min(all_scalars), + ) + c_max = min( + np.mean(all_scalars) + color_map_num_sig * np.std(all_scalars), + np.max(all_scalars), + ) else: color_map = diverging_color_map c_min = -color_map_num_sig * np.std(all_scalars) @@ -311,27 +391,51 @@ def animate(unsteady_solver, scalar_type=None, show_wake_vortices=False, save=Fa if show_scalars and first_results_step == 0: these_scalars = get_scalars(step_airplanes[0], scalar_type) - plot_scalars(plotter, these_scalars, scalar_type, min_scalar, max_scalar, - color_map, c_min, c_max, panel_surfaces, ) + plot_scalars( + plotter, + these_scalars, + scalar_type, + min_scalar, + max_scalar, + color_map, + c_min, + c_max, + panel_surfaces, + ) else: - plotter.add_mesh(panel_surfaces, show_edges=True, color=panel_color, - smooth_shading=False, ) + plotter.add_mesh( + panel_surfaces, + show_edges=True, + color=panel_color, + smooth_shading=False, + ) # Set the plotter background color and show the plotter. plotter.set_background(color=plotter_background_color) # Print a message to the console on how to set up the window. print( - 'Orient the view, then press "q" to close the window and produce the animation.') + 'Orient the view, then press "q" to close the window and produce the animation.' + ) # Show the plotter so the user can set up the camera. Then, they will close the # window, but the plotter object will stay open off-screen. - plotter.show(title="Rendering speed not to scale.", cpos=(-1, -1, 1), - full_screen=False, auto_close=False, ) + plotter.show( + title="Rendering speed not to scale.", + cpos=(-1, -1, 1), + full_screen=False, + auto_close=False, + ) # Start a list which will hold a WebP image of each frame. - images = [webp.Image.fromarray( - plotter.screenshot(transparent_background=True, return_img=True, ))] + images = [ + webp.Image.fromarray( + plotter.screenshot( + transparent_background=True, + return_img=True, + ) + ) + ] # Initialize a variable to keep track of which step we are on. current_step = 1 @@ -346,17 +450,26 @@ def animate(unsteady_solver, scalar_type=None, show_wake_vortices=False, save=Fa panel_surfaces = get_panel_surfaces(airplanes) if save: - plotter.add_text(text="Speed: " + str(round(100 * speed)) + "%", - position=text_speed_position, font_size=text_font_size, viewport=True, - color=text_color, ) + plotter.add_text( + text="Speed: " + str(round(100 * speed)) + "%", + position=text_speed_position, + font_size=text_font_size, + viewport=True, + color=text_color, + ) # If the user wants to show the wake ring vortices, then get their surfaces # and plot them. if show_wake_vortices: - wake_ring_vortex_surfaces = get_wake_ring_vortex_surfaces(unsteady_solver, - current_step) - plotter.add_mesh(wake_ring_vortex_surfaces, show_edges=True, - smooth_shading=False, color=wake_vortex_color, ) + wake_ring_vortex_surfaces = get_wake_ring_vortex_surfaces( + unsteady_solver, current_step + ) + plotter.add_mesh( + wake_ring_vortex_surfaces, + show_edges=True, + smooth_shading=False, + color=wake_vortex_color, + ) # Check if the user wants to plot scalars and this step is equal to or # greater than the first step with calculated results. If so, add the panel @@ -365,18 +478,37 @@ def animate(unsteady_solver, scalar_type=None, show_wake_vortices=False, save=Fa these_scalars = get_scalars(airplanes, scalar_type) - plot_scalars(plotter, these_scalars, scalar_type, min_scalar, max_scalar, - color_map, c_min, c_max, panel_surfaces, ) + plot_scalars( + plotter, + these_scalars, + scalar_type, + min_scalar, + max_scalar, + color_map, + c_min, + c_max, + panel_surfaces, + ) else: - plotter.add_mesh(panel_surfaces, show_edges=True, color=panel_color, - smooth_shading=False, ) + plotter.add_mesh( + panel_surfaces, + show_edges=True, + color=panel_color, + smooth_shading=False, + ) # Append a WebP image of this frame to the list of frame images if the user # wants to save an animation. if save: - images.append(webp.Image.fromarray( - plotter.screenshot(filename=None, transparent_background=True, - return_img=True, ))) + images.append( + webp.Image.fromarray( + plotter.screenshot( + filename=None, + transparent_background=True, + return_img=True, + ) + ) + ) # Increment the step number tracker. current_step += 1 @@ -384,8 +516,9 @@ def animate(unsteady_solver, scalar_type=None, show_wake_vortices=False, save=Fa # If the user wants to save the file, save the list of images as an animated WebP. if save: # Convert the list of WebP images to a WebP animation. - webp.save_images(images, "Animate.webp", fps=actual_fps, lossless=False, - quality=quality) + webp.save_images( + images, "Animate.webp", fps=actual_fps, lossless=False, quality=quality + ) # Close all the plotters. pv.close_all() @@ -421,19 +554,27 @@ def plot_results_versus_time(unsteady_solver, show=True, save=False): # Create a 1D array with the time at each time step where results have been # calculated. - times = np.linspace(first_results_time_step_time, final_time_step_time, - num_steps_to_average, endpoint=True, ) + times = np.linspace( + first_results_time_step_time, + final_time_step_time, + num_steps_to_average, + endpoint=True, + ) # Initialize matrices to hold the forces, moments, and coefficients at each time # step which has results. total_near_field_force_wind_axes = np.zeros( - (num_airplanes, 3, num_steps_to_average)) + (num_airplanes, 3, num_steps_to_average) + ) total_near_field_force_coefficients_wind_axes = np.zeros( - (num_airplanes, 3, num_steps_to_average)) + (num_airplanes, 3, num_steps_to_average) + ) total_near_field_moment_wind_axes = np.zeros( - (num_airplanes, 3, num_steps_to_average)) + (num_airplanes, 3, num_steps_to_average) + ) total_near_field_moment_coefficients_wind_axes = np.zeros( - (num_airplanes, 3, num_steps_to_average)) + (num_airplanes, 3, num_steps_to_average) + ) # Initialize a variable to track position in the results arrays. results_step = 0 @@ -447,13 +588,17 @@ def plot_results_versus_time(unsteady_solver, show=True, save=False): # Iterate through this step's airplanes. for airplane_id, airplane in enumerate(airplanes): total_near_field_force_wind_axes[airplane_id, :, results_step] = ( - airplane.total_near_field_force_wind_axes) - total_near_field_force_coefficients_wind_axes[airplane_id, :, - results_step] = airplane.total_near_field_force_coefficients_wind_axes + airplane.total_near_field_force_wind_axes + ) + total_near_field_force_coefficients_wind_axes[ + airplane_id, :, results_step + ] = airplane.total_near_field_force_coefficients_wind_axes total_near_field_moment_wind_axes[airplane_id, :, results_step] = ( - airplane.total_near_field_moment_wind_axes) - total_near_field_moment_coefficients_wind_axes[airplane_id, :, - results_step] = airplane.total_near_field_moment_coefficients_wind_axes + airplane.total_near_field_moment_wind_axes + ) + total_near_field_moment_coefficients_wind_axes[ + airplane_id, :, results_step + ] = airplane.total_near_field_moment_coefficients_wind_axes results_step += 1 @@ -515,60 +660,114 @@ def plot_results_versus_time(unsteady_solver, show=True, save=False): moment_coefficients_axes.set_facecolor(figure_background_color) # Populate the plots. - force_axes.plot(times, total_near_field_force_wind_axes[airplane_id, 0], - label="Induced Drag", color=drag_color, marker=".", + force_axes.plot( + times, + total_near_field_force_wind_axes[airplane_id, 0], + label="Induced Drag", + color=drag_color, + marker=".", markevery=(marker_spacing * 0 / 3, marker_spacing), - markersize=marker_size, ) - force_axes.plot(times, total_near_field_force_wind_axes[airplane_id, 1], - label="Side Force", color=side_color, marker=".", + markersize=marker_size, + ) + force_axes.plot( + times, + total_near_field_force_wind_axes[airplane_id, 1], + label="Side Force", + color=side_color, + marker=".", markevery=(marker_spacing * 1 / 3, marker_spacing), - markersize=marker_size, ) - force_axes.plot(times, total_near_field_force_wind_axes[airplane_id, 2], - label="Lift", color=lift_color, marker=".", + markersize=marker_size, + ) + force_axes.plot( + times, + total_near_field_force_wind_axes[airplane_id, 2], + label="Lift", + color=lift_color, + marker=".", markevery=(marker_spacing * 2 / 3, marker_spacing), - markersize=marker_size, ) - force_coefficients_axes.plot(times, + markersize=marker_size, + ) + force_coefficients_axes.plot( + times, total_near_field_force_coefficients_wind_axes[airplane_id, 0], - label="Induced Drag", color=drag_color, marker=".", + label="Induced Drag", + color=drag_color, + marker=".", markevery=(marker_spacing * 0 / 3, marker_spacing), - markersize=marker_size, ) - force_coefficients_axes.plot(times, + markersize=marker_size, + ) + force_coefficients_axes.plot( + times, total_near_field_force_coefficients_wind_axes[airplane_id, 1], - label="Side Force", color=side_color, marker=".", + label="Side Force", + color=side_color, + marker=".", markevery=(marker_spacing * 1 / 3, marker_spacing), - markersize=marker_size, ) - force_coefficients_axes.plot(times, - total_near_field_force_coefficients_wind_axes[airplane_id, 2], label="Lift", - color=lift_color, marker=".", + markersize=marker_size, + ) + force_coefficients_axes.plot( + times, + total_near_field_force_coefficients_wind_axes[airplane_id, 2], + label="Lift", + color=lift_color, + marker=".", markevery=(marker_spacing * 2 / 3, marker_spacing), - markersize=marker_size, ) - moment_axes.plot(times, total_near_field_moment_wind_axes[airplane_id, 0], - label="Roll", color=roll_color, marker=".", + markersize=marker_size, + ) + moment_axes.plot( + times, + total_near_field_moment_wind_axes[airplane_id, 0], + label="Roll", + color=roll_color, + marker=".", markevery=(marker_spacing * 0 / 3, marker_spacing), - markersize=marker_size, ) - moment_axes.plot(times, total_near_field_moment_wind_axes[airplane_id, 1], - label="Pitch", color=pitch_color, marker=".", + markersize=marker_size, + ) + moment_axes.plot( + times, + total_near_field_moment_wind_axes[airplane_id, 1], + label="Pitch", + color=pitch_color, + marker=".", markevery=(marker_spacing * 1 / 3, marker_spacing), - markersize=marker_size, ) - moment_axes.plot(times, total_near_field_moment_wind_axes[airplane_id, 2], - label="Yaw", color=yaw_color, marker=".", + markersize=marker_size, + ) + moment_axes.plot( + times, + total_near_field_moment_wind_axes[airplane_id, 2], + label="Yaw", + color=yaw_color, + marker=".", markevery=(marker_spacing * 2 / 3, marker_spacing), - markersize=marker_size, ) - moment_coefficients_axes.plot(times, + markersize=marker_size, + ) + moment_coefficients_axes.plot( + times, total_near_field_moment_coefficients_wind_axes[airplane_id, 0], - label="Roll", color=roll_color, marker=".", + label="Roll", + color=roll_color, + marker=".", markevery=(marker_spacing * 0 / 3, marker_spacing), - markersize=marker_size, ) - moment_coefficients_axes.plot(times, + markersize=marker_size, + ) + moment_coefficients_axes.plot( + times, total_near_field_moment_coefficients_wind_axes[airplane_id, 1], - label="Pitch", color=pitch_color, marker=".", + label="Pitch", + color=pitch_color, + marker=".", markevery=(marker_spacing * 1 / 3, marker_spacing), - markersize=marker_size, ) - moment_coefficients_axes.plot(times, - total_near_field_moment_coefficients_wind_axes[airplane_id, 2], label="Yaw", - color=yaw_color, marker=".", + markersize=marker_size, + ) + moment_coefficients_axes.plot( + times, + total_near_field_moment_coefficients_wind_axes[airplane_id, 2], + label="Yaw", + color=yaw_color, + marker=".", markevery=(marker_spacing * 2 / 3, marker_spacing), - markersize=marker_size, ) + markersize=marker_size, + ) # Find and format this airplane's name for use in the plot titles. airplane_name = unsteady_solver.steady_problems[0].airplanes[airplane_id].name @@ -592,23 +791,45 @@ def plot_results_versus_time(unsteady_solver, show=True, save=False): moment_coefficients_axes.set_title(moment_coefficient_title, color=text_color) # Format the plots' legends. - force_axes.legend(facecolor=figure_background_color, - edgecolor=figure_background_color, labelcolor=text_color, ) - force_coefficients_axes.legend(facecolor=figure_background_color, - edgecolor=figure_background_color, labelcolor=text_color, ) - moment_axes.legend(facecolor=figure_background_color, - edgecolor=figure_background_color, labelcolor=text_color, ) - moment_coefficients_axes.legend(facecolor=figure_background_color, - edgecolor=figure_background_color, labelcolor=text_color, ) + force_axes.legend( + facecolor=figure_background_color, + edgecolor=figure_background_color, + labelcolor=text_color, + ) + force_coefficients_axes.legend( + facecolor=figure_background_color, + edgecolor=figure_background_color, + labelcolor=text_color, + ) + moment_axes.legend( + facecolor=figure_background_color, + edgecolor=figure_background_color, + labelcolor=text_color, + ) + moment_coefficients_axes.legend( + facecolor=figure_background_color, + edgecolor=figure_background_color, + labelcolor=text_color, + ) # Save the figures as PNGs if the user wants to do so. if save: - force_figure.savefig(airplane_name + " Forces.png", dpi=300, ) - force_coefficients_figure.savefig(airplane_name + " Force Coefficients.png", - dpi=300, ) - moment_figure.savefig(airplane_name + " Moments.png", dpi=300, ) + force_figure.savefig( + airplane_name + " Forces.png", + dpi=300, + ) + force_coefficients_figure.savefig( + airplane_name + " Force Coefficients.png", + dpi=300, + ) + moment_figure.savefig( + airplane_name + " Moments.png", + dpi=300, + ) moment_coefficients_figure.savefig( - airplane_name + " Moment Coefficients.png", dpi=300, ) + airplane_name + " Moment Coefficients.png", + dpi=300, + ) # If the user wants to show the plots, do so. if show: @@ -635,45 +856,81 @@ def print_steady_results(steady_solver): # Print out this airplane's forces. print("\tForces in Wind Axes:") - print("\t\tInduced Drag:\t\t\t", - np.round(airplane.total_near_field_force_wind_axes[0], 3), " N", sep="", ) - print("\t\tSide Force:\t\t\t\t", - np.round(airplane.total_near_field_force_wind_axes[1], 3), " N", sep="", ) - print("\t\tLift:\t\t\t\t\t", - np.round(airplane.total_near_field_force_wind_axes[2], 3), " N", sep="", ) + print( + "\t\tInduced Drag:\t\t\t", + np.round(airplane.total_near_field_force_wind_axes[0], 3), + " N", + sep="", + ) + print( + "\t\tSide Force:\t\t\t\t", + np.round(airplane.total_near_field_force_wind_axes[1], 3), + " N", + sep="", + ) + print( + "\t\tLift:\t\t\t\t\t", + np.round(airplane.total_near_field_force_wind_axes[2], 3), + " N", + sep="", + ) # Print out this airplane's moments. print("\n\tMoments in Wind Axes:") - print("\t\tRolling Moment:\t\t\t", - np.round(airplane.total_near_field_moment_wind_axes[0], 3), " Nm", sep="", ) - print("\t\tPitching Moment:\t\t", - np.round(airplane.total_near_field_moment_wind_axes[1], 3), " Nm", sep="", ) - print("\t\tYawing Moment:\t\t\t", - np.round(airplane.total_near_field_moment_wind_axes[2], 3), " Nm", sep="", ) + print( + "\t\tRolling Moment:\t\t\t", + np.round(airplane.total_near_field_moment_wind_axes[0], 3), + " Nm", + sep="", + ) + print( + "\t\tPitching Moment:\t\t", + np.round(airplane.total_near_field_moment_wind_axes[1], 3), + " Nm", + sep="", + ) + print( + "\t\tYawing Moment:\t\t\t", + np.round(airplane.total_near_field_moment_wind_axes[2], 3), + " Nm", + sep="", + ) # Print out this airplane's force coefficients. print("\n\tForce Coefficients in Wind Axes:") - print("\t\tCDi:\t\t\t\t\t", + print( + "\t\tCDi:\t\t\t\t\t", np.round(airplane.total_near_field_force_coefficients_wind_axes[0], 3), - sep="", ) - print("\t\tCY:\t\t\t\t\t\t", + sep="", + ) + print( + "\t\tCY:\t\t\t\t\t\t", np.round(airplane.total_near_field_force_coefficients_wind_axes[1], 3), - sep="", ) - print("\t\tCL:\t\t\t\t\t\t", + sep="", + ) + print( + "\t\tCL:\t\t\t\t\t\t", np.round(airplane.total_near_field_force_coefficients_wind_axes[2], 3), - sep="", ) + sep="", + ) # Print out this airplane's moment coefficients. print("\n\tMoment Coefficients in Wind Axes:") - print("\t\tCl:\t\t\t\t\t\t", + print( + "\t\tCl:\t\t\t\t\t\t", np.round(airplane.total_near_field_moment_coefficients_wind_axes[0], 3), - sep="", ) - print("\t\tCm:\t\t\t\t\t\t", + sep="", + ) + print( + "\t\tCm:\t\t\t\t\t\t", np.round(airplane.total_near_field_moment_coefficients_wind_axes[1], 3), - sep="", ) - print("\t\tCn:\t\t\t\t\t\t", + sep="", + ) + print( + "\t\tCn:\t\t\t\t\t\t", np.round(airplane.total_near_field_moment_coefficients_wind_axes[2], 3), - sep="", ) + sep="", + ) # If the results from more airplanes are going to be printed, print new line # to separate them. @@ -692,14 +949,17 @@ def print_unsteady_results(unsteady_solver): forces = unsteady_solver.unsteady_problem.final_mean_near_field_forces_wind_axes moments = unsteady_solver.unsteady_problem.final_mean_near_field_moments_wind_axes force_coefficients = ( - unsteady_solver.unsteady_problem.final_mean_near_field_force_coefficients_wind_axes) + unsteady_solver.unsteady_problem.final_mean_near_field_force_coefficients_wind_axes + ) moment_coefficients = ( - unsteady_solver.unsteady_problem.final_mean_near_field_moment_coefficients_wind_axes) + unsteady_solver.unsteady_problem.final_mean_near_field_moment_coefficients_wind_axes + ) # For each airplane object, calculate and print the average force, moment, # force coefficient, and moment coefficient values. for airplane_id, airplane in enumerate( - unsteady_solver.steady_problems[0].airplanes): + unsteady_solver.steady_problems[0].airplanes + ): these_forces = forces[airplane_id] these_moments = moments[airplane_id] these_force_coefficients = force_coefficients[airplane_id] @@ -723,34 +983,81 @@ def print_unsteady_results(unsteady_solver): # Print out this airplane's average forces. print("\tFinal Forces in Wind Axes:") - print("\t\tInduced Drag:\t\t\t", np.round(this_induced_drag, 3), " N", sep="", ) - print("\t\tSide Force:\t\t\t\t", np.round(this_side_force, 3), " N", sep="", ) - print("\t\tLift:\t\t\t\t\t", np.round(this_lift, 3), " N", sep="", ) + print( + "\t\tInduced Drag:\t\t\t", + np.round(this_induced_drag, 3), + " N", + sep="", + ) + print( + "\t\tSide Force:\t\t\t\t", + np.round(this_side_force, 3), + " N", + sep="", + ) + print( + "\t\tLift:\t\t\t\t\t", + np.round(this_lift, 3), + " N", + sep="", + ) # Print out this airplane's average moments. print("\n\tFinal Moments in Wind Axes:") - print("\t\tRolling Moment:\t\t\t", np.round(this_rolling_moment, 3), " Nm", - sep="", ) - print("\t\tPitching Moment:\t\t", np.round(this_pitching_moment, 3), " Nm", - sep="", ) - print("\t\tYawing Moment:\t\t\t", np.round(this_yawing_moment, 3), " Nm", - sep="", ) + print( + "\t\tRolling Moment:\t\t\t", + np.round(this_rolling_moment, 3), + " Nm", + sep="", + ) + print( + "\t\tPitching Moment:\t\t", + np.round(this_pitching_moment, 3), + " Nm", + sep="", + ) + print( + "\t\tYawing Moment:\t\t\t", + np.round(this_yawing_moment, 3), + " Nm", + sep="", + ) # Print out this airplane's average force coefficients. print("\n\tFinal Force Coefficients in Wind Axes:") - print("\t\tCDi:\t\t\t\t\t", np.round(this_induced_drag_coefficient, 3), - sep="", ) - print("\t\tCY:\t\t\t\t\t\t", np.round(this_side_force_coefficient, 3), sep="", ) - print("\t\tCL:\t\t\t\t\t\t", np.round(this_lift_coefficient, 3), sep="", ) + print( + "\t\tCDi:\t\t\t\t\t", + np.round(this_induced_drag_coefficient, 3), + sep="", + ) + print( + "\t\tCY:\t\t\t\t\t\t", + np.round(this_side_force_coefficient, 3), + sep="", + ) + print( + "\t\tCL:\t\t\t\t\t\t", + np.round(this_lift_coefficient, 3), + sep="", + ) # Print out this airplane's average moment coefficients. print("\n\tFinal Moment Coefficients in Wind Axes:") - print("\t\tCl:\t\t\t\t\t\t", np.round(this_rolling_moment_coefficient, 3), - sep="", ) - print("\t\tCm:\t\t\t\t\t\t", np.round(this_pitching_moment_coefficient, 3), - sep="", ) - print("\t\tCn:\t\t\t\t\t\t", np.round(this_yawing_moment_coefficient, 3), - sep="", ) + print( + "\t\tCl:\t\t\t\t\t\t", + np.round(this_rolling_moment_coefficient, 3), + sep="", + ) + print( + "\t\tCm:\t\t\t\t\t\t", + np.round(this_pitching_moment_coefficient, 3), + sep="", + ) + print( + "\t\tCn:\t\t\t\t\t\t", + np.round(this_yawing_moment_coefficient, 3), + sep="", + ) # If the results from more airplanes are going to be printed, print new line # to separate them. @@ -758,7 +1065,9 @@ def print_unsteady_results(unsteady_solver): print("") -def get_panel_surfaces(airplanes, ): +def get_panel_surfaces( + airplanes, +): """This function returns a PolyData representation of the wing panel surfaces associated with all the airplanes in a given list. @@ -784,12 +1093,23 @@ def get_panel_surfaces(airplanes, ): for panel in panels: # Stack this panel's vertices and faces. Look through the PolyData # documentation for more details. - panel_vertices_to_add = np.vstack(( - panel.front_left_vertex, panel.front_right_vertex, - panel.back_right_vertex, panel.back_left_vertex,)) + panel_vertices_to_add = np.vstack( + ( + panel.front_left_vertex, + panel.front_right_vertex, + panel.back_right_vertex, + panel.back_left_vertex, + ) + ) panel_face_to_add = np.array( - [4, (panel_num * 4), (panel_num * 4) + 1, (panel_num * 4) + 2, - (panel_num * 4) + 3, ]) + [ + 4, + (panel_num * 4), + (panel_num * 4) + 1, + (panel_num * 4) + 2, + (panel_num * 4) + 3, + ] + ) # Stack this panel's vertices and faces with the array of all the # vertices and faces. @@ -817,13 +1137,17 @@ def get_wake_ring_vortex_surfaces(solver, step): """ num_wake_ring_vortices = solver.num_wake_ring_vortices_list[step] wake_ring_vortex_front_right_vertices = ( - solver.wake_ring_vortex_front_right_vertices_list[step]) + solver.wake_ring_vortex_front_right_vertices_list[step] + ) wake_ring_vortex_front_left_vertices = ( - solver.wake_ring_vortex_front_left_vertices_list[step]) + solver.wake_ring_vortex_front_left_vertices_list[step] + ) wake_ring_vortex_back_left_vertices = ( - solver.wake_ring_vortex_back_left_vertices_list[step]) + solver.wake_ring_vortex_back_left_vertices_list[step] + ) wake_ring_vortex_back_right_vertices = ( - solver.wake_ring_vortex_back_right_vertices_list[step]) + solver.wake_ring_vortex_back_right_vertices_list[step] + ) # Initialize empty arrays to hold each wake ring vortex's vertices and its face. wake_ring_vortex_vertices = np.zeros((0, 3), dtype=int) @@ -831,27 +1155,43 @@ def get_wake_ring_vortex_surfaces(solver, step): for wake_ring_vortex_num in range(num_wake_ring_vortices): this_front_right_vertex = wake_ring_vortex_front_right_vertices[ - wake_ring_vortex_num] + wake_ring_vortex_num + ] this_front_left_vertex = wake_ring_vortex_front_left_vertices[ - wake_ring_vortex_num] + wake_ring_vortex_num + ] this_back_left_vertex = wake_ring_vortex_back_left_vertices[ - wake_ring_vortex_num] + wake_ring_vortex_num + ] this_back_right_vertex = wake_ring_vortex_back_right_vertices[ - wake_ring_vortex_num] - - wake_ring_vortex_vertices_to_add = np.vstack(( - this_front_left_vertex, this_front_right_vertex, this_back_right_vertex, - this_back_left_vertex,)) + wake_ring_vortex_num + ] + + wake_ring_vortex_vertices_to_add = np.vstack( + ( + this_front_left_vertex, + this_front_right_vertex, + this_back_right_vertex, + this_back_left_vertex, + ) + ) wake_ring_vortex_face_to_add = np.array( - [4, (wake_ring_vortex_num * 4), (wake_ring_vortex_num * 4) + 1, - (wake_ring_vortex_num * 4) + 2, - (wake_ring_vortex_num * 4) + 3, ]) + [ + 4, + (wake_ring_vortex_num * 4), + (wake_ring_vortex_num * 4) + 1, + (wake_ring_vortex_num * 4) + 2, + (wake_ring_vortex_num * 4) + 3, + ] + ) # Stack this wake ring vortex's vertices and faces. wake_ring_vortex_vertices = np.vstack( - (wake_ring_vortex_vertices, wake_ring_vortex_vertices_to_add)) + (wake_ring_vortex_vertices, wake_ring_vortex_vertices_to_add) + ) wake_ring_vortex_faces = np.hstack( - (wake_ring_vortex_faces, wake_ring_vortex_face_to_add)) + (wake_ring_vortex_faces, wake_ring_vortex_face_to_add) + ) # Increment the wake ring vortex counter. wake_ring_vortex_num += 1 @@ -860,7 +1200,10 @@ def get_wake_ring_vortex_surfaces(solver, step): return pv.PolyData(wake_ring_vortex_vertices, wake_ring_vortex_faces) -def get_scalars(airplanes, scalar_type, ): +def get_scalars( + airplanes, + scalar_type, +): """This function gets the coefficient values from a problem's airplane objects, and puts them into a 1D array to be used as scalars for display by other output methods. @@ -898,8 +1241,17 @@ def get_scalars(airplanes, scalar_type, ): return scalars -def plot_scalars(plotter, these_scalars, scalar_type, min_scalar, max_scalar, color_map, - c_min, c_max, panel_surfaces, ): +def plot_scalars( + plotter, + these_scalars, + scalar_type, + min_scalar, + max_scalar, + color_map, + c_min, + c_max, + panel_surfaces, +): """This function plots a scalar bars, the mesh panels with a particular set of scalars, and labels for the minimum and maximum scalar values. @@ -914,15 +1266,38 @@ def plot_scalars(plotter, these_scalars, scalar_type, min_scalar, max_scalar, co :param panel_surfaces: :return: """ - scalar_bar_args = dict(title=scalar_type.title() + " Coefficient", - title_font_size=bar_title_font_size, label_font_size=bar_label_font_size, - width=bar_width, position_x=bar_position_x, position_y=bar_position_y, - n_labels=bar_n_labels, fmt="%.2f", color=text_color, ) - plotter.add_mesh(panel_surfaces, show_edges=True, cmap=color_map, - clim=[c_min, c_max], scalars=these_scalars, smooth_shading=False, - scalar_bar_args=scalar_bar_args, ) - plotter.add_text(text="Max: " + str(max_scalar), position=text_max_position, - font_size=text_font_size, viewport=True, color=text_color, ) - plotter.add_text(text="Min: " + str(min_scalar), position=text_min_position, - font_size=text_font_size, viewport=True, color=text_color, ) + scalar_bar_args = dict( + title=scalar_type.title() + " Coefficient", + title_font_size=bar_title_font_size, + label_font_size=bar_label_font_size, + width=bar_width, + position_x=bar_position_x, + position_y=bar_position_y, + n_labels=bar_n_labels, + fmt="%.2f", + color=text_color, + ) + plotter.add_mesh( + panel_surfaces, + show_edges=True, + cmap=color_map, + clim=[c_min, c_max], + scalars=these_scalars, + smooth_shading=False, + scalar_bar_args=scalar_bar_args, + ) + plotter.add_text( + text="Max: " + str(max_scalar), + position=text_max_position, + font_size=text_font_size, + viewport=True, + color=text_color, + ) + plotter.add_text( + text="Min: " + str(min_scalar), + position=text_min_position, + font_size=text_font_size, + viewport=True, + color=text_color, + ) plotter.update_scalars(these_scalars) diff --git a/pterasoftware/panel.py b/pterasoftware/panel.py index b3ebf232..59e9dc0f 100644 --- a/pterasoftware/panel.py +++ b/pterasoftware/panel.py @@ -39,8 +39,15 @@ class Panel: This class is not meant to be subclassed. """ - def __init__(self, front_right_vertex, front_left_vertex, back_left_vertex, - back_right_vertex, is_leading_edge, is_trailing_edge, ): + def __init__( + self, + front_right_vertex, + front_left_vertex, + back_left_vertex, + back_right_vertex, + is_leading_edge, + is_trailing_edge, + ): """This is the initialization method. :param front_right_vertex: 1D array with three elements @@ -134,7 +141,8 @@ def collocation_point(self): # Find the vector between the points three quarters of the way down the left # and right legs of the panel. three_quarter_chord_vector = ( - left_three_quarter_chord_mark - right_three_quarter_chord_mark) + left_three_quarter_chord_mark - right_three_quarter_chord_mark + ) # Find the collocation point, which is halfway between the points three # quarters of the way down the left and right legs of the panel. Then @@ -214,11 +222,12 @@ def calculate_normalized_induced_velocity(self, point): if self.ring_vortex is not None: normalized_induced_velocity += ( - self.ring_vortex.calculate_normalized_induced_velocity(point=point)) + self.ring_vortex.calculate_normalized_induced_velocity(point=point) + ) if self.horseshoe_vortex is not None: normalized_induced_velocity += ( - self.horseshoe_vortex.calculate_normalized_induced_velocity( - point=point)) + self.horseshoe_vortex.calculate_normalized_induced_velocity(point=point) + ) return normalized_induced_velocity @@ -242,7 +251,8 @@ def calculate_induced_velocity(self, point): induced_velocity += self.ring_vortex.calculate_induced_velocity(point=point) if self.horseshoe_vortex is not None: induced_velocity += self.horseshoe_vortex.calculate_induced_velocity( - point=point) + point=point + ) return induced_velocity diff --git a/pterasoftware/problems.py b/pterasoftware/problems.py index 779eb426..e22758bb 100644 --- a/pterasoftware/problems.py +++ b/pterasoftware/problems.py @@ -82,8 +82,9 @@ def __init__(self, movement, only_final_results=False): if self.max_period == 0: self.first_averaging_step = self.num_steps - 1 else: - self.first_averaging_step = max(0, - math.floor(self.num_steps - (self.max_period / self.delta_time))) + self.first_averaging_step = max( + 0, math.floor(self.num_steps - (self.max_period / self.delta_time)) + ) # If the user only wants to calculate forces and moments for the final cycle # (for cyclic motion) or for the final time step (for static movement) set @@ -134,8 +135,9 @@ def __init__(self, movement, only_final_results=False): this_operating_point = movement.operating_points[step_id] # Initialize the steady problem object at this time step. - this_steady_problem = SteadyProblem(airplanes=these_airplanes, - operating_point=this_operating_point) + this_steady_problem = SteadyProblem( + airplanes=these_airplanes, operating_point=this_operating_point + ) # Append this steady problem to the list of steady problems. self.steady_problems.append(this_steady_problem) diff --git a/pterasoftware/steady_horseshoe_vortex_lattice_method.py b/pterasoftware/steady_horseshoe_vortex_lattice_method.py index 0df4ebf4..597273b0 100644 --- a/pterasoftware/steady_horseshoe_vortex_lattice_method.py +++ b/pterasoftware/steady_horseshoe_vortex_lattice_method.py @@ -78,7 +78,8 @@ def __init__(self, steady_problem): # Initialize attributes to hold aerodynamic data that pertains to this problem. self.wing_wing_influences = np.zeros((self.num_panels, self.num_panels)) self.freestream_velocity = ( - self.operating_point.calculate_freestream_velocity_geometry_axes()) + self.operating_point.calculate_freestream_velocity_geometry_axes() + ) self.freestream_wing_influences = np.zeros(self.num_panels) self.vortex_strengths = np.zeros(self.num_panels) self.panel_normal_directions = np.zeros((self.num_panels, 3)) @@ -110,7 +111,8 @@ def run(self, logging_level="Warning"): """ # Configure the problem's logger. logging_level_value = functions.convert_logging_level_name_to_value( - logging_level) + logging_level + ) logging.basicConfig(level=logging_level_value) # Initialize this problem's panels to have vortices congruent with this @@ -156,7 +158,8 @@ def initialize_panel_vortices(self): """ # Find the freestream direction in geometry axes. freestream_direction = ( - self.operating_point.calculate_freestream_direction_geometry_axes()) + self.operating_point.calculate_freestream_direction_geometry_axes() + ) # Iterate through each airplane's wings. for airplane in self.airplanes: @@ -182,8 +185,10 @@ def initialize_panel_vortices(self): panel.horseshoe_vortex = aerodynamics.HorseshoeVortex( finite_leg_origin=front_right_vortex_vertex, finite_leg_termination=front_left_vortex_vertex, - strength=None, infinite_leg_direction=freestream_direction, - infinite_leg_length=infinite_leg_length, ) + strength=None, + infinite_leg_direction=freestream_direction, + infinite_leg_length=infinite_leg_length, + ) def collapse_geometry(self): """This method converts attributes of the problem's geometry into 1D arrays. @@ -209,32 +214,46 @@ def collapse_geometry(self): # attributes. self.panels[global_panel_position] = panel self.panel_normal_directions[global_panel_position, :] = ( - panel.unit_normal) + panel.unit_normal + ) self.panel_areas[global_panel_position] = panel.area self.panel_collocation_points[global_panel_position, :] = ( - panel.collocation_point) + panel.collocation_point + ) self.panel_back_right_vortex_vertices[global_panel_position, :] = ( - panel.horseshoe_vortex.right_leg.origin) + panel.horseshoe_vortex.right_leg.origin + ) self.panel_front_right_vortex_vertices[global_panel_position, :] = ( - panel.horseshoe_vortex.right_leg.termination) + panel.horseshoe_vortex.right_leg.termination + ) self.panel_front_left_vortex_vertices[global_panel_position, :] = ( - panel.horseshoe_vortex.left_leg.origin) + panel.horseshoe_vortex.left_leg.origin + ) self.panel_back_left_vortex_vertices[global_panel_position, :] = ( - panel.horseshoe_vortex.left_leg.termination) + panel.horseshoe_vortex.left_leg.termination + ) self.panel_bound_vortex_centers[global_panel_position, :] = ( - panel.horseshoe_vortex.finite_leg.center) + panel.horseshoe_vortex.finite_leg.center + ) self.panel_bound_vortex_vectors[global_panel_position, :] = ( - panel.horseshoe_vortex.finite_leg.vector) + panel.horseshoe_vortex.finite_leg.vector + ) self.panel_moment_references[global_panel_position, :] = ( - airplane.xyz_ref) + airplane.xyz_ref + ) # Check if this panel is on the trailing edge. if panel.is_trailing_edge: # If it is, calculate it's streamline seed point and add it # to the solver's array of seed points. - self.seed_points = np.vstack((self.seed_points, - panel.back_left_vertex + 0.5 * ( - panel.back_right_vertex - panel.back_left_vertex),)) + self.seed_points = np.vstack( + ( + self.seed_points, + panel.back_left_vertex + + 0.5 + * (panel.back_right_vertex - panel.back_left_vertex), + ) + ) # Increment the global panel position. global_panel_position += 1 @@ -253,13 +272,17 @@ def calculate_wing_wing_influences(self): front_right_vortex_vertices=self.panel_front_right_vortex_vertices, front_left_vortex_vertices=self.panel_front_left_vortex_vertices, back_left_vortex_vertices=self.panel_back_left_vortex_vertices, - strengths=np.ones(self.num_panels), ) + strengths=np.ones(self.num_panels), + ) # Take the batch dot product of the normalized velocities with each panel's # normal direction. This is now the problem's matrix of wing-wing influence # coefficients. - self.wing_wing_influences = np.einsum("...k,...k->...", induced_velocities, - np.expand_dims(self.panel_normal_directions, axis=1), ) + self.wing_wing_influences = np.einsum( + "...k,...k->...", + induced_velocities, + np.expand_dims(self.panel_normal_directions, axis=1), + ) def calculate_vortex_strengths(self): """Solve for each panel's vortex strengths. @@ -267,8 +290,9 @@ def calculate_vortex_strengths(self): :return: None """ # Solve for the strength of each panel's vortex. - self.vortex_strengths = np.linalg.solve(self.wing_wing_influences, - -self.freestream_wing_influences) + self.vortex_strengths = np.linalg.solve( + self.wing_wing_influences, -self.freestream_wing_influences + ) # Iterate through the panels and update their vortex strengths. for panel_num, panel in enumerate(self.panels): @@ -303,7 +327,8 @@ def calculate_solution_velocity(self, points): front_right_vortex_vertices=self.panel_front_right_vortex_vertices, front_left_vortex_vertices=self.panel_front_left_vortex_vertices, back_left_vortex_vertices=self.panel_back_left_vortex_vertices, - strengths=self.vortex_strengths, ) + strengths=self.vortex_strengths, + ) return induced_velocities + self.freestream_velocity @@ -318,21 +343,27 @@ def calculate_near_field_forces_and_moments(self): """ # Calculate the total velocity at every panel's bound vortex center. total_velocities = self.calculate_solution_velocity( - points=self.panel_bound_vortex_centers) + points=self.panel_bound_vortex_centers + ) # Calculate the near field force, in geometry axes, on each panel's bound # vortex. near_field_forces_geometry_axes = ( - self.operating_point.density * np.expand_dims(self.vortex_strengths, - axis=1) * np.cross( - total_velocities, self.panel_bound_vortex_vectors, axis=-1)) + self.operating_point.density + * np.expand_dims(self.vortex_strengths, axis=1) + * np.cross(total_velocities, self.panel_bound_vortex_vectors, axis=-1) + ) # Calculate the near field moments, in geometry axes, on each panel's bound # vortex. near_field_moments_geometry_axes = np.cross( self.panel_bound_vortex_centers - self.panel_moment_references, - near_field_forces_geometry_axes, axis=-1, ) + near_field_forces_geometry_axes, + axis=-1, + ) - functions.process_steady_solver_forces(steady_solver=self, + functions.process_steady_solver_forces( + steady_solver=self, near_field_forces_geometry_axes=near_field_forces_geometry_axes, - near_field_moments_geometry_axes=near_field_moments_geometry_axes, ) + near_field_moments_geometry_axes=near_field_moments_geometry_axes, + ) diff --git a/pterasoftware/steady_ring_vortex_lattice_method.py b/pterasoftware/steady_ring_vortex_lattice_method.py index ea03cf30..cd7cf0e4 100644 --- a/pterasoftware/steady_ring_vortex_lattice_method.py +++ b/pterasoftware/steady_ring_vortex_lattice_method.py @@ -79,7 +79,8 @@ def __init__(self, steady_problem): # Initialize attributes to hold aerodynamic data that pertains to this problem. self.wing_wing_influences = np.zeros((self.num_panels, self.num_panels)) self.freestream_velocity = ( - self.operating_point.calculate_freestream_velocity_geometry_axes()) + self.operating_point.calculate_freestream_velocity_geometry_axes() + ) self.freestream_wing_influences = np.zeros(self.num_panels) self.vortex_strengths = np.ones(self.num_panels) self.panel_normal_directions = np.zeros((self.num_panels, 3)) @@ -132,7 +133,8 @@ def run(self, logging_level="Warning"): """ # Configure the problem's logger. logging_level_value = functions.convert_logging_level_name_to_value( - logging_level) + logging_level + ) logging.basicConfig(level=logging_level_value) # Initialize this problem's panels to have vortices congruent with this @@ -189,7 +191,8 @@ def initialize_panel_vortices(self): """ # Find the freestream direction in geometry axes. freestream_direction = ( - self.operating_point.calculate_freestream_direction_geometry_axes()) + self.operating_point.calculate_freestream_direction_geometry_axes() + ) # Iterate through each airplane's wings. for airplane in self.airplanes: @@ -216,16 +219,21 @@ def initialize_panel_vortices(self): # whether the panel is along the trailing edge or not. if not panel.is_trailing_edge: next_chordwise_panel = wing.panels[ - chordwise_position + 1, spanwise_position] + chordwise_position + 1, spanwise_position + ] back_left_vortex_vertex = ( - next_chordwise_panel.front_left_vortex_vertex) + next_chordwise_panel.front_left_vortex_vertex + ) back_right_vortex_vertex = ( - next_chordwise_panel.front_right_vortex_vertex) + next_chordwise_panel.front_right_vortex_vertex + ) else: back_left_vortex_vertex = front_left_vortex_vertex + ( - panel.back_left_vertex - panel.front_left_vertex) + panel.back_left_vertex - panel.front_left_vertex + ) back_right_vortex_vertex = front_right_vortex_vertex + ( - panel.back_right_vertex - panel.front_right_vertex) + panel.back_right_vertex - panel.front_right_vertex + ) # If the panel is along the trailing edge, initialize its # horseshoe vortex. @@ -234,14 +242,17 @@ def initialize_panel_vortices(self): finite_leg_termination=back_left_vortex_vertex, strength=None, infinite_leg_direction=freestream_direction, - infinite_leg_length=infinite_leg_length, ) + infinite_leg_length=infinite_leg_length, + ) # Initialize the panel's ring vortex. panel.ring_vortex = aerodynamics.RingVortex( front_right_vertex=front_right_vortex_vertex, front_left_vertex=front_left_vortex_vertex, back_left_vertex=back_left_vortex_vertex, - back_right_vertex=back_right_vortex_vertex, strength=None, ) + back_right_vertex=back_right_vortex_vertex, + strength=None, + ) def collapse_geometry(self): """This method converts attributes of the problem's geometry into 1D @@ -265,23 +276,29 @@ def collapse_geometry(self): # Update the solver's list of attributes with this panel's # attributes. - functions.update_ring_vortex_solvers_panel_attributes(solver=self, - global_panel_position=global_panel_position, panel=panel, - airplane=airplane, ) + functions.update_ring_vortex_solvers_panel_attributes( + solver=self, + global_panel_position=global_panel_position, + panel=panel, + airplane=airplane, + ) if panel.is_trailing_edge: # Also, update the attribute lists horseshoe vortex # attributes at this position with this panel's horseshoe # vortex attributes self.horseshoe_vortex_back_right_vertex[ - global_panel_position] = ( - panel.horseshoe_vortex.right_leg.origin) + global_panel_position + ] = panel.horseshoe_vortex.right_leg.origin self.horseshoe_vortex_front_right_vertex[ - global_panel_position] = panel.horseshoe_vortex.right_leg.termination + global_panel_position + ] = panel.horseshoe_vortex.right_leg.termination self.horseshoe_vortex_front_left_vertex[ - global_panel_position] = panel.horseshoe_vortex.left_leg.origin + global_panel_position + ] = panel.horseshoe_vortex.left_leg.origin self.horseshoe_vortex_back_left_vertex[ - global_panel_position] = panel.horseshoe_vortex.left_leg.termination + global_panel_position + ] = panel.horseshoe_vortex.left_leg.termination # Set the horseshoe vortex strength at this position to 1.0. # This will be updated after the correct vortex strengths are @@ -307,7 +324,8 @@ def calculate_wing_wing_influences(self): front_right_vortex_vertices=self.panel_front_right_vortex_vertices, front_left_vortex_vertices=self.panel_front_left_vortex_vertices, back_left_vortex_vertices=self.panel_back_left_vortex_vertices, - strengths=self.vortex_strengths, ) + strengths=self.vortex_strengths, + ) # Find the matrix of normalized velocities induced at every panel's # collocation point by every panel's horseshoe vortex. The answer is @@ -323,7 +341,9 @@ def calculate_wing_wing_influences(self): front_right_vortex_vertices=self.horseshoe_vortex_front_right_vertex, front_left_vortex_vertices=self.horseshoe_vortex_front_left_vertex, back_left_vortex_vertices=self.horseshoe_vortex_back_left_vertex, - strengths=self.horseshoe_vortex_strengths, )) + strengths=self.horseshoe_vortex_strengths, + ) + ) # Calculate the total normalized influences, which is the sum of the # influences by the ring vortices and by the horseshoe vortices. @@ -332,8 +352,11 @@ def calculate_wing_wing_influences(self): # Take the batch dot product of the normalized velocities with each panel's # normal direction. This is now the problem's matrix of wing-wing influence # coefficients. - self.wing_wing_influences = np.einsum("...k,...k->...", total_influences, - np.expand_dims(self.panel_normal_directions, axis=1), ) + self.wing_wing_influences = np.einsum( + "...k,...k->...", + total_influences, + np.expand_dims(self.panel_normal_directions, axis=1), + ) def calculate_vortex_strengths(self): """Solve for each panel's vortex strengths. @@ -341,8 +364,9 @@ def calculate_vortex_strengths(self): :return: None """ # Solve for the strength of each panel's vortex. - self.vortex_strengths = np.linalg.solve(self.wing_wing_influences, - -self.freestream_wing_influences) + self.vortex_strengths = np.linalg.solve( + self.wing_wing_influences, -self.freestream_wing_influences + ) # Iterate through the panels and update their vortex strengths. for panel_num in range(self.panels.size): @@ -358,7 +382,8 @@ def calculate_vortex_strengths(self): # Update the panel's horseshoe vortex strength. panel.horseshoe_vortex.update_strength(self.vortex_strengths[panel_num]) self.horseshoe_vortex_strengths[panel_num] = self.vortex_strengths[ - panel_num] + panel_num + ] def calculate_solution_velocity(self, points): """This function takes in a group of points. At every point, it finds the @@ -390,7 +415,8 @@ def calculate_solution_velocity(self, points): front_right_vortex_vertices=self.panel_front_right_vortex_vertices, front_left_vortex_vertices=self.panel_front_left_vortex_vertices, back_left_vortex_vertices=self.panel_back_left_vortex_vertices, - strengths=self.vortex_strengths, ) + strengths=self.vortex_strengths, + ) # Find the matrix of velocities induced at every panel's collocation point by # every panel's horseshoe vortex. This can be called in a batch fashion, @@ -399,12 +425,15 @@ def calculate_solution_velocity(self, points): # zero, which eliminates their effects. The effect of every horseshoe vortex # on each point will be summed. horseshoe_vortex_influences = ( - aerodynamics.collapsed_velocities_from_horseshoe_vortices(points=points, + aerodynamics.collapsed_velocities_from_horseshoe_vortices( + points=points, back_right_vortex_vertices=self.horseshoe_vortex_back_right_vertex, front_right_vortex_vertices=self.horseshoe_vortex_front_right_vertex, front_left_vortex_vertices=self.horseshoe_vortex_front_left_vertex, back_left_vortex_vertices=self.horseshoe_vortex_back_left_vertex, - strengths=self.horseshoe_vortex_strengths, )) + strengths=self.horseshoe_vortex_strengths, + ) + ) # Find the total influence of the vortices, which is the sum of the influence # due to the ring vortices and the horseshoe vortices. @@ -453,21 +482,25 @@ def calculate_near_field_forces_and_moments(self): # Change the effective right vortex line strength from zero # to this panel's ring vortex's strength. effective_right_vortex_line_strengths[global_panel_position] = ( - self.vortex_strengths[global_panel_position]) + self.vortex_strengths[global_panel_position] + ) else: # Get the panel directly to the right of this panel. panel_to_right = wing.panels[ - panel.local_chordwise_position, panel.local_spanwise_position + 1,] + panel.local_chordwise_position, + panel.local_spanwise_position + 1, + ] # Change the effective right vortex line strength from zero # to the difference between this panel's ring vortex's # strength, and the ring vortex strength of the panel to the # right of it. effective_right_vortex_line_strengths[global_panel_position] = ( - self.vortex_strengths[ - global_panel_position] - panel_to_right.ring_vortex.strength) + self.vortex_strengths[global_panel_position] + - panel_to_right.ring_vortex.strength + ) # Check if this panel is on its wing's leading edge. if panel.is_leading_edge: @@ -475,20 +508,24 @@ def calculate_near_field_forces_and_moments(self): # Change the effective front vortex line strength from zero # to this panel's ring vortex's strength. effective_front_vortex_line_strengths[global_panel_position] = ( - self.vortex_strengths[global_panel_position]) + self.vortex_strengths[global_panel_position] + ) else: # Get the panel directly in front of this panel. panel_to_front = wing.panels[ - panel.local_chordwise_position - 1, panel.local_spanwise_position,] + panel.local_chordwise_position - 1, + panel.local_spanwise_position, + ] # Change the effective front vortex line strength from zero # to the difference between this panel's ring vortex's # strength, and the ring vortex strength of the panel in # front of it. effective_front_vortex_line_strengths[global_panel_position] = ( - self.vortex_strengths[ - global_panel_position] - panel_to_front.ring_vortex.strength) + self.vortex_strengths[global_panel_position] + - panel_to_front.ring_vortex.strength + ) # Check if this panel is on its wing's left edge. if panel.is_left_edge: @@ -496,19 +533,23 @@ def calculate_near_field_forces_and_moments(self): # Change the effective left vortex line strength from zero to # this panel's ring vortex's strength. effective_left_vortex_line_strengths[global_panel_position] = ( - self.vortex_strengths[global_panel_position]) + self.vortex_strengths[global_panel_position] + ) else: # Get the panel directly to the left of this panel. panel_to_left = wing.panels[ - panel.local_chordwise_position, panel.local_spanwise_position - 1,] + panel.local_chordwise_position, + panel.local_spanwise_position - 1, + ] # Change the effective left vortex line strength from zero to # the difference between this panel's ring vortex's strength, # and the ring vortex strength of the panel to the left of it. effective_left_vortex_line_strengths[global_panel_position] = ( - self.vortex_strengths[ - global_panel_position] - panel_to_left.ring_vortex.strength) + self.vortex_strengths[global_panel_position] + - panel_to_left.ring_vortex.strength + ) # Increment the global panel position. global_panel_position += 1 @@ -516,53 +557,82 @@ def calculate_near_field_forces_and_moments(self): # Calculate the solution velocities at the centers of the panel's front leg, # left leg, and right leg. velocities_at_ring_vortex_front_leg_centers = self.calculate_solution_velocity( - points=self.panel_front_vortex_centers) + points=self.panel_front_vortex_centers + ) velocities_at_ring_vortex_left_leg_centers = self.calculate_solution_velocity( - points=self.panel_left_vortex_centers) + points=self.panel_left_vortex_centers + ) velocities_at_ring_vortex_right_leg_centers = self.calculate_solution_velocity( - points=self.panel_right_vortex_centers) + points=self.panel_right_vortex_centers + ) # Using the effective line vortex strengths, and the Kutta-Joukowski theorem # to find the near field force in geometry axes on the front leg, left leg, # and right leg. near_field_forces_on_ring_vortex_right_legs_geometry_axes = ( - self.operating_point.density * np.expand_dims( - effective_right_vortex_line_strengths, axis=1) * np.cross( - velocities_at_ring_vortex_right_leg_centers, - self.panel_right_vortex_vectors, axis=-1, )) + self.operating_point.density + * np.expand_dims(effective_right_vortex_line_strengths, axis=1) + * np.cross( + velocities_at_ring_vortex_right_leg_centers, + self.panel_right_vortex_vectors, + axis=-1, + ) + ) near_field_forces_on_ring_vortex_front_legs_geometry_axes = ( - self.operating_point.density * np.expand_dims( - effective_front_vortex_line_strengths, axis=1) * np.cross( - velocities_at_ring_vortex_front_leg_centers, - self.panel_front_vortex_vectors, axis=-1, )) + self.operating_point.density + * np.expand_dims(effective_front_vortex_line_strengths, axis=1) + * np.cross( + velocities_at_ring_vortex_front_leg_centers, + self.panel_front_vortex_vectors, + axis=-1, + ) + ) near_field_forces_on_ring_vortex_left_legs_geometry_axes = ( - self.operating_point.density * np.expand_dims( - effective_left_vortex_line_strengths, axis=1) * np.cross( - velocities_at_ring_vortex_left_leg_centers, self.panel_left_vortex_vectors, - axis=-1, )) + self.operating_point.density + * np.expand_dims(effective_left_vortex_line_strengths, axis=1) + * np.cross( + velocities_at_ring_vortex_left_leg_centers, + self.panel_left_vortex_vectors, + axis=-1, + ) + ) # Sum the forces on the legs to calculate the total near field force, # in geometry axes, on each panel. near_field_forces_geometry_axes = ( - near_field_forces_on_ring_vortex_front_legs_geometry_axes + near_field_forces_on_ring_vortex_left_legs_geometry_axes + near_field_forces_on_ring_vortex_right_legs_geometry_axes) + near_field_forces_on_ring_vortex_front_legs_geometry_axes + + near_field_forces_on_ring_vortex_left_legs_geometry_axes + + near_field_forces_on_ring_vortex_right_legs_geometry_axes + ) # Find the near field moment in geometry axes on the front leg, left leg, # and right leg. near_field_moments_on_ring_vortex_front_legs_geometry_axes = np.cross( self.panel_front_vortex_centers - self.panel_moment_references, - near_field_forces_on_ring_vortex_front_legs_geometry_axes, axis=-1, ) + near_field_forces_on_ring_vortex_front_legs_geometry_axes, + axis=-1, + ) near_field_moments_on_ring_vortex_left_legs_geometry_axes = np.cross( self.panel_left_vortex_centers - self.panel_moment_references, - near_field_forces_on_ring_vortex_left_legs_geometry_axes, axis=-1, ) + near_field_forces_on_ring_vortex_left_legs_geometry_axes, + axis=-1, + ) near_field_moments_on_ring_vortex_right_legs_geometry_axes = np.cross( self.panel_right_vortex_centers - self.panel_moment_references, - near_field_forces_on_ring_vortex_right_legs_geometry_axes, axis=-1, ) + near_field_forces_on_ring_vortex_right_legs_geometry_axes, + axis=-1, + ) # Sum the moments on the legs to calculate the total near field moment, # in geometry axes, on each panel. near_field_moments_geometry_axes = ( - near_field_moments_on_ring_vortex_front_legs_geometry_axes + near_field_moments_on_ring_vortex_left_legs_geometry_axes + near_field_moments_on_ring_vortex_right_legs_geometry_axes) + near_field_moments_on_ring_vortex_front_legs_geometry_axes + + near_field_moments_on_ring_vortex_left_legs_geometry_axes + + near_field_moments_on_ring_vortex_right_legs_geometry_axes + ) - functions.process_steady_solver_forces(steady_solver=self, + functions.process_steady_solver_forces( + steady_solver=self, near_field_forces_geometry_axes=near_field_forces_geometry_axes, - near_field_moments_geometry_axes=near_field_moments_geometry_axes, ) + near_field_moments_geometry_axes=near_field_moments_geometry_axes, + ) diff --git a/pterasoftware/trim.py b/pterasoftware/trim.py index 9d558f88..160b4681 100644 --- a/pterasoftware/trim.py +++ b/pterasoftware/trim.py @@ -36,8 +36,15 @@ # ToDo: Document this function. -def analyze_steady_trim(problem, velocity_bounds, alpha_bounds, beta_bounds, - external_thrust_bounds, objective_cut_off=0.01, num_calls=100, ): +def analyze_steady_trim( + problem, + velocity_bounds, + alpha_bounds, + beta_bounds, + external_thrust_bounds, + objective_cut_off=0.01, + num_calls=100, +): """This function attempts to calculate a trim condition of a steady solver by varying the operating point's velocity, angle of attack, angle of sideslip, and external thrust until the net force and net moment on the aircraft are @@ -48,7 +55,8 @@ def analyze_steady_trim(problem, velocity_bounds, alpha_bounds, beta_bounds, """ if len(problem.airplanes) != 1: trim_logger.error( - "The problem objects for trim analyses must have only one airplane.") + "The problem objects for trim analyses must have only one airplane." + ) weight = problem.airplanes[0].weight base_velocity = problem.operating_point.velocity @@ -59,18 +67,24 @@ def analyze_steady_trim(problem, velocity_bounds, alpha_bounds, beta_bounds, if base_velocity < velocity_bounds[0] or base_velocity > velocity_bounds[1]: raise Exception( "The operating point's velocity must be within the specified velocity " - "bounds.") + "bounds." + ) if base_alpha < alpha_bounds[0] or base_alpha > alpha_bounds[1]: raise Exception( - "The operating point's alpha must be within the specified alpha bounds.") + "The operating point's alpha must be within the specified alpha bounds." + ) if base_beta < beta_bounds[0] or base_beta > beta_bounds[1]: raise Exception( - "The operating point's beta must be within the specified beta bounds.") - if (base_external_thrust < external_thrust_bounds[0] or base_external_thrust > - external_thrust_bounds[1]): + "The operating point's beta must be within the specified beta bounds." + ) + if ( + base_external_thrust < external_thrust_bounds[0] + or base_external_thrust > external_thrust_bounds[1] + ): raise Exception( "The operating point's external thrust must be within the specified " - "external thrust bounds.") + "external thrust bounds." + ) current_arguments = [np.nan, np.nan, np.nan, np.nan] @@ -96,18 +110,21 @@ def objective_function(arguments): external_forces = np.array([external_thrust, 0, weight]) external_force_coefficients = external_forces / dynamic_pressure / s_ref - solver = (steady_horseshoe_vortex_lattice_method - .SteadyHorseshoeVortexLatticeMethodSolver( - steady_problem=problem)) + solver = steady_horseshoe_vortex_lattice_method.SteadyHorseshoeVortexLatticeMethodSolver( + steady_problem=problem + ) solver.run() airplane = solver.airplanes[0] net_force_coefficient = np.linalg.norm( - airplane.total_near_field_force_coefficients_wind_axes - external_force_coefficients) + airplane.total_near_field_force_coefficients_wind_axes + - external_force_coefficients + ) net_moment_coefficient = np.linalg.norm( - airplane.total_near_field_moment_coefficients_wind_axes) + airplane.total_near_field_moment_coefficients_wind_axes + ) objective = (abs(net_force_coefficient) + abs(net_moment_coefficient)) / 2 @@ -118,7 +135,15 @@ def objective_function(arguments): o_str = str(round(objective, 3)) state_msg = ( - "State: velocity=" + v_str + ", alpha=" + a_str + ", beta=" + b_str + ", external thrust=" + t_str) + "State: velocity=" + + v_str + + ", alpha=" + + a_str + + ", beta=" + + b_str + + ", external thrust=" + + t_str + ) obj_msg = "Objective: " + o_str trim_logger.info(state_msg) @@ -133,36 +158,58 @@ def objective_function(arguments): return objective initial_guess = np.array( - [base_velocity, base_alpha, base_beta, base_external_thrust]) + [base_velocity, base_alpha, base_beta, base_external_thrust] + ) bounds = (velocity_bounds, alpha_bounds, beta_bounds, external_thrust_bounds) trim_logger.info("Starting local search.") try: - scipy.optimize.minimize(fun=objective_function, x0=initial_guess, bounds=bounds, - method="L-BFGS-B", options={"maxfun": num_calls, "eps": 0.01}, ) + scipy.optimize.minimize( + fun=objective_function, + x0=initial_guess, + bounds=bounds, + method="L-BFGS-B", + options={"maxfun": num_calls, "eps": 0.01}, + ) except StopIteration: trim_logger.info("Acceptable value reached with local search.") return current_arguments trim_logger.warning( - "No acceptable value reached with local search. Starting global search.") + "No acceptable value reached with local search. Starting global search." + ) try: - scipy.optimize.dual_annealing(func=objective_function, bounds=bounds, - x0=initial_guess, maxfun=num_calls, minimizer_kwargs={"method": "L-BFGS-B", - "options": {"maxfun": num_calls, "eps": 0.01}, }, ) + scipy.optimize.dual_annealing( + func=objective_function, + bounds=bounds, + x0=initial_guess, + maxfun=num_calls, + minimizer_kwargs={ + "method": "L-BFGS-B", + "options": {"maxfun": num_calls, "eps": 0.01}, + }, + ) except StopIteration: trim_logger.info("Acceptable global minima found.") return current_arguments trim_logger.critical( "No trim condition found. Try increasing the bounds and the maximum number of " - "iterations.") + "iterations." + ) return [np.nan, np.nan, np.nan, np.nan] # ToDo: Document this function. -def analyze_unsteady_trim(airplane_movement, operating_point, velocity_bounds, - alpha_bounds, beta_bounds, objective_cut_off=0.01, num_calls=100, ): +def analyze_unsteady_trim( + airplane_movement, + operating_point, + velocity_bounds, + alpha_bounds, + beta_bounds, + objective_cut_off=0.01, + num_calls=100, +): """This function attempts to calculate a trim condition of an unsteady solver by varying the operating point's velocity, angle of attack, angle of sideslip, and external thrust until the net cycle-averaged force and net cycle-averaged @@ -179,13 +226,16 @@ def analyze_unsteady_trim(airplane_movement, operating_point, velocity_bounds, if base_velocity < velocity_bounds[0] or base_velocity > velocity_bounds[1]: trim_logger.error( "The operating point's velocity must be within the specified velocity " - "bounds.") + "bounds." + ) if base_alpha < alpha_bounds[0] or base_alpha > alpha_bounds[1]: trim_logger.error( - "The operating point's alpha must be within the specified alpha bounds.") + "The operating point's alpha must be within the specified alpha bounds." + ) if base_beta < beta_bounds[0] or base_beta > beta_bounds[1]: trim_logger.error( - "The operating point's beta must be within the specified beta bounds.") + "The operating point's beta must be within the specified beta bounds." + ) current_arguments = [np.nan, np.nan, np.nan] @@ -212,27 +262,36 @@ def objective_function(arguments): external_force_coefficients = external_forces / dynamic_pressure / s_ref operating_point_movement = movement.OperatingPointMovement( - base_operating_point=operating_point) + base_operating_point=operating_point + ) - this_movement = movement.Movement(airplane_movements=[airplane_movement], - operating_point_movement=operating_point_movement, ) + this_movement = movement.Movement( + airplane_movements=[airplane_movement], + operating_point_movement=operating_point_movement, + ) - this_problem = problems.UnsteadyProblem(movement=this_movement, - only_final_results=True) + this_problem = problems.UnsteadyProblem( + movement=this_movement, only_final_results=True + ) this_solver = ( unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver( - unsteady_problem=this_problem)) + unsteady_problem=this_problem + ) + ) this_solver.run(logging_level="Critical") force_coefficients = ( - this_solver.unsteady_problem.final_total_near_field_force_coefficients_wind_axes) + this_solver.unsteady_problem.final_total_near_field_force_coefficients_wind_axes + ) moment_coefficients = ( - this_solver.unsteady_problem.final_total_near_field_moment_coefficients_wind_axes) + this_solver.unsteady_problem.final_total_near_field_moment_coefficients_wind_axes + ) net_force_coefficients = np.linalg.norm( - force_coefficients - external_force_coefficients) + force_coefficients - external_force_coefficients + ) net_moment_coefficients = np.linalg.norm(moment_coefficients) objective = (abs(net_force_coefficients) + abs(net_moment_coefficients)) / 2 @@ -261,23 +320,37 @@ def objective_function(arguments): trim_logger.info("Starting local search.") try: - scipy.optimize.minimize(fun=objective_function, x0=initial_guess, bounds=bounds, - method="L-BFGS-B", options={"maxfun": num_calls, "eps": 0.01}, ) + scipy.optimize.minimize( + fun=objective_function, + x0=initial_guess, + bounds=bounds, + method="L-BFGS-B", + options={"maxfun": num_calls, "eps": 0.01}, + ) except StopIteration: trim_logger.info("Acceptable value reached with local search.") return current_arguments trim_logger.warning( - "No acceptable value reached with local search. Starting global search.") + "No acceptable value reached with local search. Starting global search." + ) try: - scipy.optimize.dual_annealing(func=objective_function, bounds=bounds, - x0=initial_guess, maxfun=num_calls, minimizer_kwargs={"method": "L-BFGS-B", - "options": {"maxfun": num_calls, "eps": 0.01}, }, ) + scipy.optimize.dual_annealing( + func=objective_function, + bounds=bounds, + x0=initial_guess, + maxfun=num_calls, + minimizer_kwargs={ + "method": "L-BFGS-B", + "options": {"maxfun": num_calls, "eps": 0.01}, + }, + ) except StopIteration: trim_logger.info("Acceptable global minima found.") return current_arguments trim_logger.critical( "No trim condition found. Try increasing the bounds and the maximum number of " - "iterations.") + "iterations." + ) return [np.nan, np.nan, np.nan] diff --git a/pterasoftware/ui_resources/textdialog.py b/pterasoftware/ui_resources/textdialog.py index b6c7315c..32985163 100644 --- a/pterasoftware/ui_resources/textdialog.py +++ b/pterasoftware/ui_resources/textdialog.py @@ -5,8 +5,10 @@ class Ui_TextAboutDialog(object): def setupUi(self, TextAboutDialog): TextAboutDialog.setObjectName("TextAboutDialog") TextAboutDialog.resize(1000, 900) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding, ) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(TextAboutDialog.sizePolicy().hasHeightForWidth()) @@ -17,8 +19,10 @@ def setupUi(self, TextAboutDialog): self.textEdit = QtWidgets.QTextEdit(TextAboutDialog) self.textEdit.setEnabled(True) self.textEdit.setGeometry(QtCore.QRect(0, 0, 1000, 900)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding, ) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.textEdit.sizePolicy().hasHeightForWidth()) @@ -34,4 +38,5 @@ def setupUi(self, TextAboutDialog): def retranslateUi(self, TextAboutDialog): TextAboutDialog.setWindowTitle( - QtWidgets.QApplication.translate("TextAboutDialog", "Dialog", None, -1)) + QtWidgets.QApplication.translate("TextAboutDialog", "Dialog", None, -1) + ) diff --git a/pterasoftware/unsteady_ring_vortex_lattice_method.py b/pterasoftware/unsteady_ring_vortex_lattice_method.py index 5ae9fe27..5f487927 100644 --- a/pterasoftware/unsteady_ring_vortex_lattice_method.py +++ b/pterasoftware/unsteady_ring_vortex_lattice_method.py @@ -177,8 +177,12 @@ def __init__(self, unsteady_problem): self.current_wake_ring_vortex_ages = None self.num_panels = None - def run(self, logging_level="Warning", prescribed_wake=True, - calculate_streamlines=True, ): + def run( + self, + logging_level="Warning", + prescribed_wake=True, + calculate_streamlines=True, + ): """This method runs the solver on the unsteady problem. :param logging_level: str, optional @@ -197,7 +201,8 @@ def run(self, logging_level="Warning", prescribed_wake=True, """ # Configure the problem's logger. logging_level_value = functions.convert_logging_level_name_to_value( - logging_level) + logging_level + ) logging.basicConfig(level=logging_level_value) # The following loop iterates through the steps to populate currently empty @@ -228,26 +233,34 @@ def run(self, logging_level="Warning", prescribed_wake=True, this_wake_ring_vortex_strengths = np.zeros(this_num_wake_ring_vortices) this_wake_ring_vortex_ages = np.zeros(this_num_wake_ring_vortices) this_wake_ring_vortex_front_right_vertices = np.zeros( - (this_num_wake_ring_vortices, 3)) + (this_num_wake_ring_vortices, 3) + ) this_wake_ring_vortex_front_left_vertices = np.zeros( - (this_num_wake_ring_vortices, 3)) + (this_num_wake_ring_vortices, 3) + ) this_wake_ring_vortex_back_left_vertices = np.zeros( - (this_num_wake_ring_vortices, 3)) + (this_num_wake_ring_vortices, 3) + ) this_wake_ring_vortex_back_right_vertices = np.zeros( - (this_num_wake_ring_vortices, 3)) + (this_num_wake_ring_vortices, 3) + ) # Append this step's arrays to the list of arrays. self.num_wake_ring_vortices_list.append(this_num_wake_ring_vortices) self.wake_ring_vortex_strengths_list.append(this_wake_ring_vortex_strengths) self.wake_ring_vortex_ages_list.append(this_wake_ring_vortex_ages) self.wake_ring_vortex_front_right_vertices_list.append( - this_wake_ring_vortex_front_right_vertices) + this_wake_ring_vortex_front_right_vertices + ) self.wake_ring_vortex_front_left_vertices_list.append( - this_wake_ring_vortex_front_left_vertices) + this_wake_ring_vortex_front_left_vertices + ) self.wake_ring_vortex_back_left_vertices_list.append( - this_wake_ring_vortex_back_left_vertices) + this_wake_ring_vortex_back_left_vertices + ) self.wake_ring_vortex_back_right_vertices_list.append( - this_wake_ring_vortex_back_right_vertices) + this_wake_ring_vortex_back_right_vertices + ) # The following loop attempts to predict how much time each step will take, # relative to the other steps. This data will be used to generate estimates @@ -288,10 +301,16 @@ def run(self, logging_level="Warning", prescribed_wake=True, # Unless the logging level is at or above Warning, run the simulation with a # progress bar. - with tqdm(total=approx_total_time, unit="", unit_scale=True, ncols=100, - desc="Simulating", disable=logging_level_value != logging.WARNING, - bar_format="{desc}:{percentage:3.0f}% |{bar}| Elapsed: {elapsed}, " - "Remaining: {remaining}", ) as bar: + with tqdm( + total=approx_total_time, + unit="", + unit_scale=True, + ncols=100, + desc="Simulating", + disable=logging_level_value != logging.WARNING, + bar_format="{desc}:{percentage:3.0f}% |{bar}| Elapsed: {elapsed}, " + "Remaining: {remaining}", + ) as bar: # Initialize all the airplanes' panels' vortices. logging.info("Initializing all airplanes' panel vortices.") @@ -308,14 +327,21 @@ def run(self, logging_level="Warning", prescribed_wake=True, # point. self.current_step = step self.current_airplanes = self.steady_problems[ - self.current_step].airplanes + self.current_step + ].airplanes self.current_operating_point = self.steady_problems[ - self.current_step].operating_point + self.current_step + ].operating_point self.current_freestream_velocity_geometry_axes = ( - self.current_operating_point.calculate_freestream_velocity_geometry_axes()) + self.current_operating_point.calculate_freestream_velocity_geometry_axes() + ) logging.info( - "Beginning time step " + str(self.current_step) + " out of " + str( - self.num_steps - 1) + ".") + "Beginning time step " + + str(self.current_step) + + " out of " + + str(self.num_steps - 1) + + "." + ) # Calculate the number of panels for this time step. self.num_panels = 0 @@ -325,9 +351,11 @@ def run(self, logging_level="Warning", prescribed_wake=True, # Initialize attributes to hold aerodynamic data that pertains to this # problem. self.current_wing_wing_influences = np.zeros( - (self.num_panels, self.num_panels)) + (self.num_panels, self.num_panels) + ) self.current_freestream_velocity_geometry_axes = ( - self.current_operating_point.calculate_freestream_velocity_geometry_axes()) + self.current_operating_point.calculate_freestream_velocity_geometry_axes() + ) self.current_freestream_wing_influences = np.zeros(self.num_panels) self.current_wake_wing_influences = np.zeros(self.num_panels) self.current_vortex_strengths = np.ones(self.num_panels) @@ -365,13 +393,17 @@ def run(self, logging_level="Warning", prescribed_wake=True, self.last_panel_collocation_points = np.zeros((self.num_panels, 3)) self.last_panel_vortex_strengths = np.zeros(self.num_panels) self.last_panel_back_right_vortex_vertices = np.zeros( - (self.num_panels, 3)) + (self.num_panels, 3) + ) self.last_panel_front_right_vortex_vertices = np.zeros( - (self.num_panels, 3)) + (self.num_panels, 3) + ) self.last_panel_front_left_vortex_vertices = np.zeros( - (self.num_panels, 3)) + (self.num_panels, 3) + ) self.last_panel_back_left_vortex_vertices = np.zeros( - (self.num_panels, 3)) + (self.num_panels, 3) + ) self.last_panel_right_vortex_centers = np.zeros((self.num_panels, 3)) self.last_panel_front_vortex_centers = np.zeros((self.num_panels, 3)) self.last_panel_left_vortex_centers = np.zeros((self.num_panels, 3)) @@ -380,17 +412,23 @@ def run(self, logging_level="Warning", prescribed_wake=True, # Get the pre-allocated (but still all zero) arrays of wake # information that are associated with this time step. self.current_wake_ring_vortex_strengths = ( - self.wake_ring_vortex_strengths_list[step]) + self.wake_ring_vortex_strengths_list[step] + ) self.current_wake_ring_vortex_ages = self.wake_ring_vortex_ages_list[ - step] + step + ] self.current_wake_ring_vortex_front_right_vertices = ( - self.wake_ring_vortex_front_right_vertices_list[step]) + self.wake_ring_vortex_front_right_vertices_list[step] + ) self.current_wake_ring_vortex_front_left_vertices = ( - self.wake_ring_vortex_front_left_vertices_list[step]) + self.wake_ring_vortex_front_left_vertices_list[step] + ) self.current_wake_ring_vortex_back_left_vertices = ( - self.wake_ring_vortex_back_left_vertices_list[step]) + self.wake_ring_vortex_back_left_vertices_list[step] + ) self.current_wake_ring_vortex_back_right_vertices = ( - self.wake_ring_vortex_back_right_vertices_list[step]) + self.wake_ring_vortex_back_right_vertices_list[step] + ) # Collapse this problem's geometry matrices into 1D arrays of # attributes. @@ -456,8 +494,8 @@ def initialize_panel_vortices(self): # Get the freestream velocity at this time step's problem. this_freestream_velocity_geometry_axes = ( - steady_problem.operating_point - .calculate_freestream_velocity_geometry_axes()) + steady_problem.operating_point.calculate_freestream_velocity_geometry_axes() + ) # Iterate through this problem's airplanes' wings. for airplane in steady_problem.airplanes: @@ -479,11 +517,14 @@ def initialize_panel_vortices(self): # whether the panel is along the trailing edge or not. if not panel.is_trailing_edge: next_chordwise_panel = wing.panels[ - chordwise_position + 1, spanwise_position] + chordwise_position + 1, spanwise_position + ] back_left_vortex_vertex = ( - next_chordwise_panel.front_left_vortex_vertex) + next_chordwise_panel.front_left_vortex_vertex + ) back_right_vortex_vertex = ( - next_chordwise_panel.front_right_vortex_vertex) + next_chordwise_panel.front_right_vortex_vertex + ) else: # As these vertices are directly behind the trailing # edge, they are spaced back from their panel's @@ -493,11 +534,23 @@ def initialize_panel_vortices(self): # of "Modeling of aerodynamic forces in flapping # flight with the Unsteady Vortex Lattice Method" by # Thomas Lambert. - back_left_vortex_vertex = (front_left_vortex_vertex + ( - panel.back_left_vertex - panel.front_left_vertex) + this_freestream_velocity_geometry_axes * self.delta_time * 0.25) + back_left_vortex_vertex = ( + front_left_vortex_vertex + + (panel.back_left_vertex - panel.front_left_vertex) + + this_freestream_velocity_geometry_axes + * self.delta_time + * 0.25 + ) back_right_vortex_vertex = ( - front_right_vortex_vertex + ( - panel.back_right_vertex - panel.front_right_vertex) + this_freestream_velocity_geometry_axes * self.delta_time * 0.25) + front_right_vortex_vertex + + ( + panel.back_right_vertex + - panel.front_right_vertex + ) + + this_freestream_velocity_geometry_axes + * self.delta_time + * 0.25 + ) # Initialize the panel's ring vortex. panel.ring_vortex = aerodynamics.RingVortex( @@ -505,7 +558,8 @@ def initialize_panel_vortices(self): front_left_vertex=front_left_vortex_vertex, back_left_vertex=back_left_vortex_vertex, back_right_vertex=back_right_vortex_vertex, - strength=None, ) + strength=None, + ) def collapse_geometry(self): """This method converts attributes of the problem's geometry into 1D @@ -530,30 +584,35 @@ def collapse_geometry(self): for panel in panels: # Update the solver's list of attributes with this panel's # attributes. - functions.update_ring_vortex_solvers_panel_attributes(solver=self, - global_panel_position=global_panel_position, panel=panel, - airplane=airplane, ) + functions.update_ring_vortex_solvers_panel_attributes( + solver=self, + global_panel_position=global_panel_position, + panel=panel, + airplane=airplane, + ) # Increment the global panel position. global_panel_position += 1 for wake_ring_vortex in wake_ring_vortices: self.current_wake_ring_vortex_strengths[ - global_wake_ring_vortex_position] = wake_ring_vortex.strength + global_wake_ring_vortex_position + ] = wake_ring_vortex.strength self.current_wake_ring_vortex_ages[ - global_wake_ring_vortex_position] = wake_ring_vortex.age + global_wake_ring_vortex_position + ] = wake_ring_vortex.age self.current_wake_ring_vortex_front_right_vertices[ - global_wake_ring_vortex_position, - :] = wake_ring_vortex.front_right_vertex + global_wake_ring_vortex_position, : + ] = wake_ring_vortex.front_right_vertex self.current_wake_ring_vortex_front_left_vertices[ - global_wake_ring_vortex_position, - :] = wake_ring_vortex.front_left_vertex + global_wake_ring_vortex_position, : + ] = wake_ring_vortex.front_left_vertex self.current_wake_ring_vortex_back_left_vertices[ - global_wake_ring_vortex_position, - :] = wake_ring_vortex.back_left_vertex + global_wake_ring_vortex_position, : + ] = wake_ring_vortex.back_left_vertex self.current_wake_ring_vortex_back_right_vertices[ - global_wake_ring_vortex_position, - :] = wake_ring_vortex.back_right_vertex + global_wake_ring_vortex_position, : + ] = wake_ring_vortex.back_right_vertex global_wake_ring_vortex_position += 1 @@ -577,26 +636,35 @@ def collapse_geometry(self): # Update the solver's list of attributes with this panel's # attributes. self.last_panel_collocation_points[global_panel_position, :] = ( - panel.collocation_point) + panel.collocation_point + ) self.last_panel_vortex_strengths[global_panel_position] = ( - panel.ring_vortex.strength) + panel.ring_vortex.strength + ) self.last_panel_back_right_vortex_vertices[ - global_panel_position, :] = panel.ring_vortex.right_leg.origin + global_panel_position, : + ] = panel.ring_vortex.right_leg.origin self.last_panel_front_right_vortex_vertices[ - global_panel_position, - :] = panel.ring_vortex.right_leg.termination + global_panel_position, : + ] = panel.ring_vortex.right_leg.termination self.last_panel_front_left_vortex_vertices[ - global_panel_position, :] = panel.ring_vortex.left_leg.origin - self.last_panel_back_left_vortex_vertices[global_panel_position, - :] = panel.ring_vortex.left_leg.termination - self.last_panel_right_vortex_centers[global_panel_position, - :] = panel.ring_vortex.right_leg.center - self.last_panel_front_vortex_centers[global_panel_position, - :] = panel.ring_vortex.front_leg.center - self.last_panel_left_vortex_centers[global_panel_position, - :] = panel.ring_vortex.left_leg.center - self.last_panel_back_vortex_centers[global_panel_position, - :] = panel.ring_vortex.back_leg.center + global_panel_position, : + ] = panel.ring_vortex.left_leg.origin + self.last_panel_back_left_vortex_vertices[ + global_panel_position, : + ] = panel.ring_vortex.left_leg.termination + self.last_panel_right_vortex_centers[ + global_panel_position, : + ] = panel.ring_vortex.right_leg.center + self.last_panel_front_vortex_centers[ + global_panel_position, : + ] = panel.ring_vortex.front_leg.center + self.last_panel_left_vortex_centers[ + global_panel_position, : + ] = panel.ring_vortex.left_leg.center + self.last_panel_back_vortex_centers[ + global_panel_position, : + ] = panel.ring_vortex.back_leg.center # Increment the global panel position. global_panel_position += 1 @@ -617,14 +685,19 @@ def calculate_wing_wing_influences(self): front_right_vortex_vertices=self.panel_front_right_vortex_vertices, front_left_vortex_vertices=self.panel_front_left_vortex_vertices, back_left_vortex_vertices=self.panel_back_left_vortex_vertices, - strengths=self.current_vortex_strengths, ages=None, - nu=self.current_operating_point.nu, ) + strengths=self.current_vortex_strengths, + ages=None, + nu=self.current_operating_point.nu, + ) # Take the batch dot product of the normalized velocities with each panel's # normal direction. This is now the problem's matrix of wing-wing influence # coefficients. - self.current_wing_wing_influences = np.einsum("...k,...k->...", - total_influences, np.expand_dims(self.panel_normal_directions, axis=1), ) + self.current_wing_wing_influences = np.einsum( + "...k,...k->...", + total_influences, + np.expand_dims(self.panel_normal_directions, axis=1), + ) def calculate_freestream_wing_influences(self): """This method finds the vector of freestream-wing influence coefficients @@ -637,22 +710,30 @@ def calculate_freestream_wing_influences(self): """ # Find the normal components of the freestream velocity on every panel by # taking a batch dot product. - freestream_influences = np.einsum("ij,j->i", self.panel_normal_directions, - self.current_freestream_velocity_geometry_axes, ) + freestream_influences = np.einsum( + "ij,j->i", + self.panel_normal_directions, + self.current_freestream_velocity_geometry_axes, + ) # Get the current flapping velocities at every collocation point. current_flapping_velocities_at_collocation_points = ( - self.calculate_current_flapping_velocities_at_collocation_points()) + self.calculate_current_flapping_velocities_at_collocation_points() + ) # Find the normal components of every panel's flapping velocities at their # collocation points by taking a batch dot product. - flapping_influences = np.einsum("ij,ij->i", self.panel_normal_directions, - current_flapping_velocities_at_collocation_points, ) + flapping_influences = np.einsum( + "ij,ij->i", + self.panel_normal_directions, + current_flapping_velocities_at_collocation_points, + ) # Calculate the total current freestream-wing influences by summing the # freestream influences and the flapping influences. self.current_freestream_wing_influences = ( - freestream_influences + flapping_influences) + freestream_influences + flapping_influences + ) def calculate_wake_wing_influences(self): """This method finds the vector of the wake-wing influences associated with @@ -678,12 +759,14 @@ def calculate_wake_wing_influences(self): back_left_vortex_vertices=self.current_wake_ring_vortex_back_left_vertices, strengths=self.current_wake_ring_vortex_strengths, ages=self.current_wake_ring_vortex_ages, - nu=self.current_operating_point.nu, ) + nu=self.current_operating_point.nu, + ) # Set the current wake-wing influences to the normal component of the # wake induced velocities at each panel. - self.current_wake_wing_influences = np.einsum("ij,ij->i", - velocities_from_wake, self.panel_normal_directions) + self.current_wake_wing_influences = np.einsum( + "ij,ij->i", velocities_from_wake, self.panel_normal_directions + ) else: @@ -699,7 +782,9 @@ def calculate_vortex_strengths(self): # Solve for the strength of each panel's vortex. self.current_vortex_strengths = np.linalg.solve( self.current_wing_wing_influences, - -self.current_wake_wing_influences - self.current_freestream_wing_influences, ) + -self.current_wake_wing_influences + - self.current_freestream_wing_influences, + ) # Iterate through the panels and update their vortex strengths. for panel_num in range(self.panels.size): @@ -741,8 +826,10 @@ def calculate_solution_velocity(self, points): front_right_vortex_vertices=self.panel_front_right_vortex_vertices, front_left_vortex_vertices=self.panel_front_left_vortex_vertices, back_left_vortex_vertices=self.panel_back_left_vortex_vertices, - strengths=self.current_vortex_strengths, ages=None, - nu=self.current_operating_point.nu, ) + strengths=self.current_vortex_strengths, + ages=None, + nu=self.current_operating_point.nu, + ) # Find the vector of velocities induced at every point by every wake ring # vortex. The effect of every wake ring vortex on each point will be summed. @@ -754,7 +841,8 @@ def calculate_solution_velocity(self, points): back_left_vortex_vertices=self.current_wake_ring_vortex_back_left_vertices, strengths=self.current_wake_ring_vortex_strengths, ages=self.current_wake_ring_vortex_ages, - nu=self.current_operating_point.nu, ) + nu=self.current_operating_point.nu, + ) # Find the total influence of the vortices, which is the sum of the influence # due to the bound ring vortices and the wake ring vortices. @@ -803,21 +891,25 @@ def calculate_near_field_forces_and_moments(self): # Change the effective right vortex line strength from zero # to this panel's ring vortex's strength. effective_right_vortex_line_strengths[global_panel_position] = ( - self.current_vortex_strengths[global_panel_position]) + self.current_vortex_strengths[global_panel_position] + ) else: # Get the panel directly to the right of this panel. panel_to_right = wing.panels[ - panel.local_chordwise_position, panel.local_spanwise_position + 1,] + panel.local_chordwise_position, + panel.local_spanwise_position + 1, + ] # Change the effective right vortex line strength from zero # to the difference between this panel's ring vortex's # strength, and the ring vortex strength of the panel to the # right of it. effective_right_vortex_line_strengths[global_panel_position] = ( - self.current_vortex_strengths[ - global_panel_position] - panel_to_right.ring_vortex.strength) + self.current_vortex_strengths[global_panel_position] + - panel_to_right.ring_vortex.strength + ) # Check if this panel is on its wing's leading edge. if panel.is_leading_edge: @@ -825,20 +917,24 @@ def calculate_near_field_forces_and_moments(self): # Change the effective front vortex line strength from zero # to this panel's ring vortex's strength. effective_front_vortex_line_strengths[global_panel_position] = ( - self.current_vortex_strengths[global_panel_position]) + self.current_vortex_strengths[global_panel_position] + ) else: # Get the panel directly in front of this panel. panel_to_front = wing.panels[ - panel.local_chordwise_position - 1, panel.local_spanwise_position,] + panel.local_chordwise_position - 1, + panel.local_spanwise_position, + ] # Change the effective front vortex line strength from zero # to the difference between this panel's ring vortex's # strength, and the ring vortex strength of the panel in # front of it. effective_front_vortex_line_strengths[global_panel_position] = ( - self.current_vortex_strengths[ - global_panel_position] - panel_to_front.ring_vortex.strength) + self.current_vortex_strengths[global_panel_position] + - panel_to_front.ring_vortex.strength + ) # Check if this panel is on its wing's left edge. if panel.is_left_edge: @@ -846,91 +942,129 @@ def calculate_near_field_forces_and_moments(self): # Change the effective left vortex line strength from zero to # this panel's ring vortex's strength. effective_left_vortex_line_strengths[global_panel_position] = ( - self.current_vortex_strengths[global_panel_position]) + self.current_vortex_strengths[global_panel_position] + ) else: # Get the panel directly to the left of this panel. panel_to_left = wing.panels[ - panel.local_chordwise_position, panel.local_spanwise_position - 1,] + panel.local_chordwise_position, + panel.local_spanwise_position - 1, + ] # Change the effective left vortex line strength from zero to # the difference between this panel's ring vortex's strength, # and the ring vortex strength of the panel to the left of it. effective_left_vortex_line_strengths[global_panel_position] = ( - self.current_vortex_strengths[ - global_panel_position] - panel_to_left.ring_vortex.strength) + self.current_vortex_strengths[global_panel_position] + - panel_to_left.ring_vortex.strength + ) # Increment the global panel position. global_panel_position += 1 # Calculate the solution velocities at the centers of the panel's front leg, # left leg, and right leg. - velocities_at_ring_vortex_front_leg_centers = (self.calculate_solution_velocity( - points=self.panel_front_vortex_centers) + self.calculate_current_flapping_velocities_at_front_leg_centers()) - velocities_at_ring_vortex_left_leg_centers = (self.calculate_solution_velocity( - points=self.panel_left_vortex_centers) + self.calculate_current_flapping_velocities_at_left_leg_centers()) - velocities_at_ring_vortex_right_leg_centers = (self.calculate_solution_velocity( - points=self.panel_right_vortex_centers) + self.calculate_current_flapping_velocities_at_right_leg_centers()) + velocities_at_ring_vortex_front_leg_centers = ( + self.calculate_solution_velocity(points=self.panel_front_vortex_centers) + + self.calculate_current_flapping_velocities_at_front_leg_centers() + ) + velocities_at_ring_vortex_left_leg_centers = ( + self.calculate_solution_velocity(points=self.panel_left_vortex_centers) + + self.calculate_current_flapping_velocities_at_left_leg_centers() + ) + velocities_at_ring_vortex_right_leg_centers = ( + self.calculate_solution_velocity(points=self.panel_right_vortex_centers) + + self.calculate_current_flapping_velocities_at_right_leg_centers() + ) # Using the effective line vortex strengths, and the Kutta-Joukowski theorem # to find the near field force in geometry axes on the front leg, left leg, # and right leg. Also calculate the unsteady component of the force on each # panel, which is derived from the unsteady Bernoulli equation. near_field_forces_on_ring_vortex_right_legs_geometry_axes = ( - self.current_operating_point.density * np.expand_dims( - effective_right_vortex_line_strengths, - axis=1) * functions.numba_1d_explicit_cross( - velocities_at_ring_vortex_right_leg_centers, - self.panel_right_vortex_vectors, )) + self.current_operating_point.density + * np.expand_dims(effective_right_vortex_line_strengths, axis=1) + * functions.numba_1d_explicit_cross( + velocities_at_ring_vortex_right_leg_centers, + self.panel_right_vortex_vectors, + ) + ) near_field_forces_on_ring_vortex_front_legs_geometry_axes = ( - self.current_operating_point.density * np.expand_dims( - effective_front_vortex_line_strengths, - axis=1) * functions.numba_1d_explicit_cross( - velocities_at_ring_vortex_front_leg_centers, - self.panel_front_vortex_vectors, )) + self.current_operating_point.density + * np.expand_dims(effective_front_vortex_line_strengths, axis=1) + * functions.numba_1d_explicit_cross( + velocities_at_ring_vortex_front_leg_centers, + self.panel_front_vortex_vectors, + ) + ) near_field_forces_on_ring_vortex_left_legs_geometry_axes = ( - self.current_operating_point.density * np.expand_dims( - effective_left_vortex_line_strengths, - axis=1) * functions.numba_1d_explicit_cross( - velocities_at_ring_vortex_left_leg_centers, - self.panel_left_vortex_vectors, )) + self.current_operating_point.density + * np.expand_dims(effective_left_vortex_line_strengths, axis=1) + * functions.numba_1d_explicit_cross( + velocities_at_ring_vortex_left_leg_centers, + self.panel_left_vortex_vectors, + ) + ) unsteady_near_field_forces_geometry_axes = ( - self.current_operating_point.density * np.expand_dims( - (self.current_vortex_strengths - self.last_panel_vortex_strengths), - axis=1, ) * np.expand_dims(self.panel_areas, - axis=1) * self.panel_normal_directions / self.delta_time) + self.current_operating_point.density + * np.expand_dims( + (self.current_vortex_strengths - self.last_panel_vortex_strengths), + axis=1, + ) + * np.expand_dims(self.panel_areas, axis=1) + * self.panel_normal_directions + / self.delta_time + ) # Sum the forces on the legs, and the unsteady force, to calculate the total # near field force, in geometry axes, on each panel. near_field_forces_geometry_axes = ( - near_field_forces_on_ring_vortex_front_legs_geometry_axes + near_field_forces_on_ring_vortex_left_legs_geometry_axes + near_field_forces_on_ring_vortex_right_legs_geometry_axes + unsteady_near_field_forces_geometry_axes) + near_field_forces_on_ring_vortex_front_legs_geometry_axes + + near_field_forces_on_ring_vortex_left_legs_geometry_axes + + near_field_forces_on_ring_vortex_right_legs_geometry_axes + + unsteady_near_field_forces_geometry_axes + ) # Find the near field moment in geometry axes on the front leg, left leg, # and right leg. Also find the moment on each panel due to the unsteady force. near_field_moments_on_ring_vortex_front_legs_geometry_axes = ( functions.numba_1d_explicit_cross( self.panel_front_vortex_centers - self.panel_moment_references, - near_field_forces_on_ring_vortex_front_legs_geometry_axes, )) + near_field_forces_on_ring_vortex_front_legs_geometry_axes, + ) + ) near_field_moments_on_ring_vortex_left_legs_geometry_axes = ( functions.numba_1d_explicit_cross( self.panel_left_vortex_centers - self.panel_moment_references, - near_field_forces_on_ring_vortex_left_legs_geometry_axes, )) + near_field_forces_on_ring_vortex_left_legs_geometry_axes, + ) + ) near_field_moments_on_ring_vortex_right_legs_geometry_axes = ( functions.numba_1d_explicit_cross( self.panel_right_vortex_centers - self.panel_moment_references, - near_field_forces_on_ring_vortex_right_legs_geometry_axes, )) + near_field_forces_on_ring_vortex_right_legs_geometry_axes, + ) + ) unsteady_near_field_moments_geometry_axes = functions.numba_1d_explicit_cross( self.panel_collocation_points - self.panel_moment_references, - unsteady_near_field_forces_geometry_axes, ) + unsteady_near_field_forces_geometry_axes, + ) # Sum the moments on the legs, and the unsteady moment, to calculate the # total near field moment, in geometry axes, on each panel. near_field_moments_geometry_axes = ( - near_field_moments_on_ring_vortex_front_legs_geometry_axes + near_field_moments_on_ring_vortex_left_legs_geometry_axes + near_field_moments_on_ring_vortex_right_legs_geometry_axes + unsteady_near_field_moments_geometry_axes) - - functions.process_unsteady_solver_forces(unsteady_solver=self, + near_field_moments_on_ring_vortex_front_legs_geometry_axes + + near_field_moments_on_ring_vortex_left_legs_geometry_axes + + near_field_moments_on_ring_vortex_right_legs_geometry_axes + + unsteady_near_field_moments_geometry_axes + ) + + functions.process_unsteady_solver_forces( + unsteady_solver=self, near_field_forces_geometry_axes=near_field_forces_geometry_axes, - near_field_moments_geometry_axes=near_field_moments_geometry_axes, ) + near_field_moments_geometry_axes=near_field_moments_geometry_axes, + ) def populate_next_airplanes_wake(self, prescribed_wake=True): """This method updates the next time step's airplanes' wakes. @@ -943,7 +1077,8 @@ def populate_next_airplanes_wake(self, prescribed_wake=True): """ # Populate the locations of the next step's airplanes' wake vortex vertices: self.populate_next_airplanes_wake_vortex_vertices( - prescribed_wake=prescribed_wake) + prescribed_wake=prescribed_wake + ) # Populate the locations of the next step's airplanes' wake vortices. self.populate_next_airplanes_wake_vortices() @@ -995,24 +1130,28 @@ def populate_next_airplanes_wake_vortex_vertices(self, prescribed_wake=True): # Initialize a matrix to hold the vertices of the new row of # wake ring vortices. first_row_of_wake_ring_vortex_vertices = np.zeros( - (1, num_spanwise_panels + 1, 3)) + (1, num_spanwise_panels + 1, 3) + ) # Iterate through the spanwise panel positions. for spanwise_position in range(num_spanwise_panels): # Get the next wing's panel object at this location. next_panel = next_wing.panels[ - chordwise_position, spanwise_position] + chordwise_position, spanwise_position + ] # The position of the next front left wake ring vortex # vertex is the next panel's ring vortex's back left # vertex. next_front_left_vertex = ( - next_panel.ring_vortex.back_left_vertex) + next_panel.ring_vortex.back_left_vertex + ) # Add this to the new row of wake ring vortex vertices. first_row_of_wake_ring_vortex_vertices[ - 0, spanwise_position] = next_front_left_vertex + 0, spanwise_position + ] = next_front_left_vertex # Check if this panel is on the right edge of the wing. if spanwise_position == (num_spanwise_panels - 1): @@ -1020,17 +1159,20 @@ def populate_next_airplanes_wake_vortex_vertices(self, prescribed_wake=True): # vortex vertex is the next panel's ring vortex's # back right vertex. next_front_right_vertex = ( - next_panel.ring_vortex.back_right_vertex) + next_panel.ring_vortex.back_right_vertex + ) # Add this to the new row of wake ring vortex vertices. first_row_of_wake_ring_vortex_vertices[ - 0, spanwise_position + 1] = next_front_right_vertex + 0, spanwise_position + 1 + ] = next_front_right_vertex # Set the next wing's matrix of wake ring vortex vertices to # a copy of the row of new wake ring vortex vertices. This is # correct because this is the first time step. next_wing.wake_ring_vortex_vertices = np.copy( - first_row_of_wake_ring_vortex_vertices) + first_row_of_wake_ring_vortex_vertices + ) # Initialize variables to hold the number of spanwise vertices. num_spanwise_vertices = num_spanwise_panels + 1 @@ -1038,7 +1180,8 @@ def populate_next_airplanes_wake_vortex_vertices(self, prescribed_wake=True): # Initialize a new matrix to hold the second row of wake ring # vortex vertices. second_row_of_wake_ring_vortex_vertices = np.zeros( - (1, num_spanwise_panels + 1, 3)) + (1, num_spanwise_panels + 1, 3) + ) # Iterate through the spanwise vertex positions. for spanwise_vertex_position in range(num_spanwise_vertices): @@ -1046,34 +1189,45 @@ def populate_next_airplanes_wake_vortex_vertices(self, prescribed_wake=True): # Get the corresponding vertex from the first row. wake_ring_vortex_vertex = ( next_wing.wake_ring_vortex_vertices[ - 0, spanwise_vertex_position]) + 0, spanwise_vertex_position + ] + ) if prescribed_wake: # If the wake is prescribed, set the velocity at this # vertex to the freestream velocity. velocity_at_first_row_wake_ring_vortex_vertex = ( - self.current_freestream_velocity_geometry_axes) + self.current_freestream_velocity_geometry_axes + ) else: # If the wake is not prescribed, set the velocity at # this vertex to the solution velocity at this point. velocity_at_first_row_wake_ring_vortex_vertex = ( self.calculate_solution_velocity( - np.expand_dims(wake_ring_vortex_vertex, - axis=0))) + np.expand_dims(wake_ring_vortex_vertex, axis=0) + ) + ) # Update the second row with the interpolated position of # the first vertex. second_row_of_wake_ring_vortex_vertices[ - 0, spanwise_vertex_position] = ( - wake_ring_vortex_vertex + velocity_at_first_row_wake_ring_vortex_vertex * self.delta_time) + 0, spanwise_vertex_position + ] = ( + wake_ring_vortex_vertex + + velocity_at_first_row_wake_ring_vortex_vertex + * self.delta_time + ) # Update the wing's wake ring vortex vertex matrix by # vertically stacking the second row below it. - next_wing.wake_ring_vortex_vertices = np.vstack(( - next_wing.wake_ring_vortex_vertices, - second_row_of_wake_ring_vortex_vertices,)) + next_wing.wake_ring_vortex_vertices = np.vstack( + ( + next_wing.wake_ring_vortex_vertices, + second_row_of_wake_ring_vortex_vertices, + ) + ) # If this isn't the first step, then do this. else: @@ -1081,45 +1235,61 @@ def populate_next_airplanes_wake_vortex_vertices(self, prescribed_wake=True): # Set the next wing's wake ring vortex vertex matrix to a # copy of this wing's wake ring vortex vertex matrix. next_wing.wake_ring_vortex_vertices = np.copy( - this_wing.wake_ring_vortex_vertices) + this_wing.wake_ring_vortex_vertices + ) # Get the number of chordwise and spanwise vertices. num_chordwise_vertices = ( - next_wing.wake_ring_vortex_vertices.shape[0]) + next_wing.wake_ring_vortex_vertices.shape[0] + ) num_spanwise_vertices = ( - next_wing.wake_ring_vortex_vertices.shape[1]) + next_wing.wake_ring_vortex_vertices.shape[1] + ) # Iterate through the chordwise and spanwise vertex positions. for chordwise_vertex_position in range(num_chordwise_vertices): for spanwise_vertex_position in range( - num_spanwise_vertices): + num_spanwise_vertices + ): # Get the wake ring vortex vertex at this position. wake_ring_vortex_vertex = ( next_wing.wake_ring_vortex_vertices[ - chordwise_vertex_position, spanwise_vertex_position,]) + chordwise_vertex_position, + spanwise_vertex_position, + ] + ) if prescribed_wake: # If the wake is prescribed, set the velocity at # this vertex to the freestream velocity. velocity_at_first_row_wake_vortex_vertex = ( - self.current_freestream_velocity_geometry_axes) + self.current_freestream_velocity_geometry_axes + ) else: # If the wake is not prescribed, set the velocity # at this vertex to the solution velocity at this # point. velocity_at_first_row_wake_vortex_vertex = ( - np.squeeze(self.calculate_solution_velocity( - np.expand_dims(wake_ring_vortex_vertex, - axis=0)))) + np.squeeze( + self.calculate_solution_velocity( + np.expand_dims( + wake_ring_vortex_vertex, axis=0 + ) + ) + ) + ) # Update the vertex at this point with its # interpolated position. next_wing.wake_ring_vortex_vertices[ - chordwise_vertex_position, spanwise_vertex_position] += ( - velocity_at_first_row_wake_vortex_vertex * self.delta_time) + chordwise_vertex_position, spanwise_vertex_position + ] += ( + velocity_at_first_row_wake_vortex_vertex + * self.delta_time + ) # Set the chordwise position to the trailing edge. chordwise_position = this_wing.num_chordwise_panels - 1 @@ -1127,7 +1297,8 @@ def populate_next_airplanes_wake_vortex_vertices(self, prescribed_wake=True): # Initialize a new matrix to hold the new first row of wake # ring vortex vertices. first_row_of_wake_ring_vortex_vertices = np.zeros( - (1, this_wing.num_spanwise_panels + 1, 3)) + (1, this_wing.num_spanwise_panels + 1, 3) + ) # Iterate spanwise through the trailing edge panels. for spanwise_position in range(this_wing.num_spanwise_panels): @@ -1135,25 +1306,31 @@ def populate_next_airplanes_wake_vortex_vertices(self, prescribed_wake=True): # Get the panel object at this location on the next # airplane's wing object. next_panel = next_wing.panels[ - chordwise_position, spanwise_position] + chordwise_position, spanwise_position + ] # Add the panel object's back left ring vortex vertex to # the matrix of new wake ring vortex vertices. first_row_of_wake_ring_vortex_vertices[ - 0, spanwise_position] = next_panel.ring_vortex.back_left_vertex + 0, spanwise_position + ] = next_panel.ring_vortex.back_left_vertex if spanwise_position == (this_wing.num_spanwise_panels - 1): # If the panel object is at the right edge of the # wing, add its back right ring vortex vertex to the # matrix of new wake ring vortex vertices. first_row_of_wake_ring_vortex_vertices[ - 0, spanwise_position + 1] = next_panel.ring_vortex.back_right_vertex + 0, spanwise_position + 1 + ] = next_panel.ring_vortex.back_right_vertex # Stack the new first row of wake ring vortex vertices above # the wing's matrix of wake ring vortex vertices. - next_wing.wake_ring_vortex_vertices = np.vstack(( - first_row_of_wake_ring_vortex_vertices, - next_wing.wake_ring_vortex_vertices,)) + next_wing.wake_ring_vortex_vertices = np.vstack( + ( + first_row_of_wake_ring_vortex_vertices, + next_wing.wake_ring_vortex_vertices, + ) + ) def populate_next_airplanes_wake_vortices(self): """This method populates the locations of the next airplanes' wake vortices. @@ -1174,32 +1351,39 @@ def populate_next_airplanes_wake_vortices(self): # Iterate through the copy of the current airplane's wing positions. for wing_id, this_wing in enumerate( - self.current_airplanes[airplane_id].wings): + self.current_airplanes[airplane_id].wings + ): next_wing = next_airplane.wings[wing_id] # Get the next wing's matrix of wake ring vortex vertices. next_wing_wake_ring_vortex_vertices = ( - next_wing.wake_ring_vortex_vertices) + next_wing.wake_ring_vortex_vertices + ) this_wing_wake_ring_vortices = ( - self.current_airplanes[airplane_id].wings[ - wing_id].wake_ring_vortices) + self.current_airplanes[airplane_id] + .wings[wing_id] + .wake_ring_vortices + ) # Find the number of chordwise and spanwise vertices in the next # wing's matrix of wake ring vortex vertices. num_chordwise_vertices = next_wing_wake_ring_vortex_vertices.shape[ - 0] + 0 + ] num_spanwise_vertices = next_wing_wake_ring_vortex_vertices.shape[1] # Initialize a new matrix to hold the new row of wake ring # vortices. new_row_of_wake_ring_vortices = np.empty( - (1, num_spanwise_vertices - 1), dtype=object) + (1, num_spanwise_vertices - 1), dtype=object + ) # Stack the new matrix on top of the copy of this wing's matrix # and assign it to the next wing. next_wing.wake_ring_vortices = np.vstack( - (new_row_of_wake_ring_vortices, this_wing_wake_ring_vortices)) + (new_row_of_wake_ring_vortices, this_wing_wake_ring_vortices) + ) # Iterate through the vertex positions. for chordwise_vertex_position in range(num_chordwise_vertices): @@ -1208,9 +1392,11 @@ def populate_next_airplanes_wake_vortices(self): # Set booleans to determine if this vertex is on the # right and/or trailing edge of the wake. has_right_vertex = ( - spanwise_vertex_position + 1) < num_spanwise_vertices + spanwise_vertex_position + 1 + ) < num_spanwise_vertices has_back_vertex = ( - chordwise_vertex_position + 1) < num_chordwise_vertices + chordwise_vertex_position + 1 + ) < num_chordwise_vertices if has_right_vertex and has_back_vertex: @@ -1219,51 +1405,71 @@ def populate_next_airplanes_wake_vortices(self): # be associated with the corresponding ring vortex at # this position. front_left_vertex = next_wing_wake_ring_vortex_vertices[ - chordwise_vertex_position, spanwise_vertex_position] + chordwise_vertex_position, spanwise_vertex_position + ] front_right_vertex = ( next_wing_wake_ring_vortex_vertices[ - chordwise_vertex_position, spanwise_vertex_position + 1,]) + chordwise_vertex_position, + spanwise_vertex_position + 1, + ] + ) back_left_vertex = next_wing_wake_ring_vortex_vertices[ - chordwise_vertex_position + 1, spanwise_vertex_position,] + chordwise_vertex_position + 1, + spanwise_vertex_position, + ] back_right_vertex = next_wing_wake_ring_vortex_vertices[ - chordwise_vertex_position + 1, spanwise_vertex_position + 1,] + chordwise_vertex_position + 1, + spanwise_vertex_position + 1, + ] if chordwise_vertex_position > 0: # If this isn't the front of the wake, update the # position of the ring vortex at this location. next_wing.wake_ring_vortices[ - chordwise_vertex_position, spanwise_vertex_position,].update_position( + chordwise_vertex_position, + spanwise_vertex_position, + ].update_position( front_left_vertex=front_left_vertex, front_right_vertex=front_right_vertex, back_left_vertex=back_left_vertex, - back_right_vertex=back_right_vertex, ) + back_right_vertex=back_right_vertex, + ) # Also, update the age of this ring vortex. if self.current_step == 0: next_wing.wake_ring_vortices[ - chordwise_vertex_position, spanwise_vertex_position,].age = self.delta_time + chordwise_vertex_position, + spanwise_vertex_position, + ].age = self.delta_time else: next_wing.wake_ring_vortices[ - chordwise_vertex_position, spanwise_vertex_position,].age += self.delta_time + chordwise_vertex_position, + spanwise_vertex_position, + ].age += self.delta_time if chordwise_vertex_position == 0: # If this is the front of the wake, get the # vortex strength from the wing panel's ring # vortex direction in front of it. this_strength_copy = this_wing.panels[ - this_wing.num_chordwise_panels - 1, spanwise_vertex_position,].ring_vortex.strength + this_wing.num_chordwise_panels - 1, + spanwise_vertex_position, + ].ring_vortex.strength # Then, make a new ring vortex at this location, # with the panel's ring vortex's strength, # and add it to the matrix of ring vortices. next_wing.wake_ring_vortices[ - chordwise_vertex_position, spanwise_vertex_position,] = aerodynamics.RingVortex( + chordwise_vertex_position, + spanwise_vertex_position, + ] = aerodynamics.RingVortex( front_left_vertex=front_left_vertex, front_right_vertex=front_right_vertex, back_left_vertex=back_left_vertex, back_right_vertex=back_right_vertex, - strength=this_strength_copy, ) + strength=this_strength_copy, + ) def calculate_current_flapping_velocities_at_collocation_points(self): """This method finds the apparent flow velocity due to flapping at the @@ -1395,13 +1601,17 @@ def finalize_near_field_forces_and_moments(self): # Initialize matrices to hold the forces, moments, and coefficients at each of # the time steps that has results. total_near_field_forces_wind_axes = np.zeros( - (self.num_airplanes, 3, num_steps_to_average)) + (self.num_airplanes, 3, num_steps_to_average) + ) total_near_field_force_coefficients_wind_axes = np.zeros( - (self.num_airplanes, 3, num_steps_to_average)) + (self.num_airplanes, 3, num_steps_to_average) + ) total_near_field_moments_wind_axes = np.zeros( - (self.num_airplanes, 3, num_steps_to_average)) + (self.num_airplanes, 3, num_steps_to_average) + ) total_near_field_moment_coefficients_wind_axes = np.zeros( - (self.num_airplanes, 3, num_steps_to_average)) + (self.num_airplanes, 3, num_steps_to_average) + ) # Initialize a variable to track position in the results arrays. results_step = 0 @@ -1416,13 +1626,17 @@ def finalize_near_field_forces_and_moments(self): # Iterate through this step's airplanes. for airplane_id, airplane in enumerate(these_airplanes): total_near_field_forces_wind_axes[airplane_id, :, results_step] = ( - airplane.total_near_field_force_wind_axes) - total_near_field_force_coefficients_wind_axes[airplane_id, :, - results_step] = airplane.total_near_field_force_coefficients_wind_axes + airplane.total_near_field_force_wind_axes + ) + total_near_field_force_coefficients_wind_axes[ + airplane_id, :, results_step + ] = airplane.total_near_field_force_coefficients_wind_axes total_near_field_moments_wind_axes[airplane_id, :, results_step] = ( - airplane.total_near_field_moment_wind_axes) - total_near_field_moment_coefficients_wind_axes[airplane_id, :, - results_step] = airplane.total_near_field_moment_coefficients_wind_axes + airplane.total_near_field_moment_wind_axes + ) + total_near_field_moment_coefficients_wind_axes[ + airplane_id, :, results_step + ] = airplane.total_near_field_moment_coefficients_wind_axes results_step += 1 @@ -1432,51 +1646,82 @@ def finalize_near_field_forces_and_moments(self): if is_static: self.unsteady_problem.final_near_field_forces_wind_axes.append( - total_near_field_forces_wind_axes[airplane_id, :, -1]) + total_near_field_forces_wind_axes[airplane_id, :, -1] + ) self.unsteady_problem.final_near_field_force_coefficients_wind_axes.append( - total_near_field_force_coefficients_wind_axes[airplane_id, :, -1]) + total_near_field_force_coefficients_wind_axes[airplane_id, :, -1] + ) self.unsteady_problem.final_near_field_moments_wind_axes.append( - total_near_field_moments_wind_axes[airplane_id, :, -1]) + total_near_field_moments_wind_axes[airplane_id, :, -1] + ) self.unsteady_problem.final_near_field_moment_coefficients_wind_axes.append( - total_near_field_moment_coefficients_wind_axes[airplane_id, :, -1]) + total_near_field_moment_coefficients_wind_axes[airplane_id, :, -1] + ) else: - mean_forces = np.mean(total_near_field_forces_wind_axes[airplane_id], - axis=-1) + mean_forces = np.mean( + total_near_field_forces_wind_axes[airplane_id], axis=-1 + ) mean_force_coefficients = np.mean( - total_near_field_force_coefficients_wind_axes[airplane_id], axis=-1) - mean_moments = np.mean(total_near_field_moments_wind_axes[airplane_id], - axis=-1) + total_near_field_force_coefficients_wind_axes[airplane_id], axis=-1 + ) + mean_moments = np.mean( + total_near_field_moments_wind_axes[airplane_id], axis=-1 + ) mean_moment_coefficients = np.mean( - total_near_field_moment_coefficients_wind_axes[airplane_id], - axis=-1) + total_near_field_moment_coefficients_wind_axes[airplane_id], axis=-1 + ) rms_forces = np.sqrt( - np.mean(np.square(total_near_field_forces_wind_axes[airplane_id]), - axis=-1, )) - rms_force_coefficients = np.sqrt(np.mean(np.square( - total_near_field_force_coefficients_wind_axes[airplane_id]), - axis=-1, )) + np.mean( + np.square(total_near_field_forces_wind_axes[airplane_id]), + axis=-1, + ) + ) + rms_force_coefficients = np.sqrt( + np.mean( + np.square( + total_near_field_force_coefficients_wind_axes[airplane_id] + ), + axis=-1, + ) + ) rms_moments = np.sqrt( - np.mean(np.square(total_near_field_moments_wind_axes[airplane_id]), - axis=-1, )) - rms_moment_coefficients = np.sqrt(np.mean(np.square( - total_near_field_moment_coefficients_wind_axes[airplane_id]), - axis=-1, )) + np.mean( + np.square(total_near_field_moments_wind_axes[airplane_id]), + axis=-1, + ) + ) + rms_moment_coefficients = np.sqrt( + np.mean( + np.square( + total_near_field_moment_coefficients_wind_axes[airplane_id] + ), + axis=-1, + ) + ) self.unsteady_problem.final_mean_near_field_forces_wind_axes.append( - mean_forces) + mean_forces + ) self.unsteady_problem.final_mean_near_field_force_coefficients_wind_axes.append( - mean_force_coefficients) + mean_force_coefficients + ) self.unsteady_problem.final_mean_near_field_moments_wind_axes.append( - mean_moments) + mean_moments + ) self.unsteady_problem.final_mean_near_field_moment_coefficients_wind_axes.append( - mean_moment_coefficients) + mean_moment_coefficients + ) self.unsteady_problem.final_rms_near_field_forces_wind_axes.append( - rms_forces) + rms_forces + ) self.unsteady_problem.final_rms_near_field_force_coefficients_wind_axes.append( - rms_force_coefficients) + rms_force_coefficients + ) self.unsteady_problem.final_rms_near_field_moments_wind_axes.append( - rms_moments) + rms_moments + ) self.unsteady_problem.final_rms_near_field_moment_coefficients_wind_axes.append( - rms_moment_coefficients) + rms_moment_coefficients + ) diff --git a/tests/integration/fixtures/airplane_fixtures.py b/tests/integration/fixtures/airplane_fixtures.py index 7d966530..2cb5c0d6 100644 --- a/tests/integration/fixtures/airplane_fixtures.py +++ b/tests/integration/fixtures/airplane_fixtures.py @@ -35,12 +35,25 @@ def make_steady_validation_airplane(): This is the airplane fixture. """ # Create and return the airplane object. - steady_validation_airplane = ps.geometry.Airplane(wings=[ - ps.geometry.Wing(symmetric=True, wing_cross_sections=[ - ps.geometry.WingCrossSection( - airfoil=ps.geometry.Airfoil(name="naca2412"), ), - ps.geometry.WingCrossSection(x_le=1.0, y_le=5.0, twist=5.0, chord=0.75, - airfoil=ps.geometry.Airfoil(name="naca2412"), ), ], )], ) + steady_validation_airplane = ps.geometry.Airplane( + wings=[ + ps.geometry.Wing( + symmetric=True, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil(name="naca2412"), + ), + ps.geometry.WingCrossSection( + x_le=1.0, + y_le=5.0, + twist=5.0, + chord=0.75, + airfoil=ps.geometry.Airfoil(name="naca2412"), + ), + ], + ) + ], + ) return steady_validation_airplane @@ -52,41 +65,109 @@ def make_multiple_wing_steady_validation_airplane(): This is the airplane fixture. """ # Create and return the airplane object. - multiple_wing_steady_validation_airplane = ps.geometry.Airplane(x_ref=0.0, - y_ref=0.0, z_ref=0.0, weight=1 * 9.81, wings=[ - ps.geometry.Wing(x_le=0.0, y_le=0.0, z_le=0.0, wing_cross_sections=[ - ps.geometry.WingCrossSection(x_le=0.0, y_le=0.0, z_le=0.0, chord=1.0, - twist=0.0, - airfoil=ps.geometry.Airfoil(name="naca23012", coordinates=None, - repanel=True, n_points_per_side=400, ), - control_surface_type="symmetric", control_surface_hinge_point=0.75, - control_surface_deflection=0.0, num_spanwise_panels=8, - spanwise_spacing="uniform", ), - ps.geometry.WingCrossSection(x_le=1.0, y_le=5.0, z_le=0.0, chord=0.75, - twist=0.0, - airfoil=ps.geometry.Airfoil(name="naca23012", coordinates=None, - repanel=True, n_points_per_side=400, ), - control_surface_type="symmetric", control_surface_hinge_point=0.75, - control_surface_deflection=0.0, num_spanwise_panels=8, - spanwise_spacing="uniform", ), ], symmetric=True, - num_chordwise_panels=8, chordwise_spacing="uniform", ), - ps.geometry.Wing(x_le=5.0, y_le=0.0, z_le=0.0, wing_cross_sections=[ - ps.geometry.WingCrossSection(x_le=0.0, y_le=0.0, z_le=0.0, chord=1.00, - twist=-5.0, - airfoil=ps.geometry.Airfoil(name="naca0010", coordinates=None, - repanel=True, n_points_per_side=400, ), - control_surface_type="symmetric", control_surface_hinge_point=0.75, - control_surface_deflection=0.0, num_spanwise_panels=8, - spanwise_spacing="uniform", ), - ps.geometry.WingCrossSection(x_le=1.0, y_le=1.0, z_le=0.0, chord=0.75, - twist=-5.0, - airfoil=ps.geometry.Airfoil(name="naca0010", coordinates=None, - repanel=True, n_points_per_side=400, ), - control_surface_type="symmetric", control_surface_hinge_point=0.75, - control_surface_deflection=0.0, num_spanwise_panels=8, - spanwise_spacing="uniform", ), ], symmetric=True, - num_chordwise_panels=8, chordwise_spacing="uniform", ), ], s_ref=None, - c_ref=None, b_ref=None, ) + multiple_wing_steady_validation_airplane = ps.geometry.Airplane( + x_ref=0.0, + y_ref=0.0, + z_ref=0.0, + weight=1 * 9.81, + wings=[ + ps.geometry.Wing( + x_le=0.0, + y_le=0.0, + z_le=0.0, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=0.0, + z_le=0.0, + chord=1.0, + twist=0.0, + airfoil=ps.geometry.Airfoil( + name="naca23012", + coordinates=None, + repanel=True, + n_points_per_side=400, + ), + control_surface_type="symmetric", + control_surface_hinge_point=0.75, + control_surface_deflection=0.0, + num_spanwise_panels=8, + spanwise_spacing="uniform", + ), + ps.geometry.WingCrossSection( + x_le=1.0, + y_le=5.0, + z_le=0.0, + chord=0.75, + twist=0.0, + airfoil=ps.geometry.Airfoil( + name="naca23012", + coordinates=None, + repanel=True, + n_points_per_side=400, + ), + control_surface_type="symmetric", + control_surface_hinge_point=0.75, + control_surface_deflection=0.0, + num_spanwise_panels=8, + spanwise_spacing="uniform", + ), + ], + symmetric=True, + num_chordwise_panels=8, + chordwise_spacing="uniform", + ), + ps.geometry.Wing( + x_le=5.0, + y_le=0.0, + z_le=0.0, + wing_cross_sections=[ + ps.geometry.WingCrossSection( + x_le=0.0, + y_le=0.0, + z_le=0.0, + chord=1.00, + twist=-5.0, + airfoil=ps.geometry.Airfoil( + name="naca0010", + coordinates=None, + repanel=True, + n_points_per_side=400, + ), + control_surface_type="symmetric", + control_surface_hinge_point=0.75, + control_surface_deflection=0.0, + num_spanwise_panels=8, + spanwise_spacing="uniform", + ), + ps.geometry.WingCrossSection( + x_le=1.0, + y_le=1.0, + z_le=0.0, + chord=0.75, + twist=-5.0, + airfoil=ps.geometry.Airfoil( + name="naca0010", + coordinates=None, + repanel=True, + n_points_per_side=400, + ), + control_surface_type="symmetric", + control_surface_hinge_point=0.75, + control_surface_deflection=0.0, + num_spanwise_panels=8, + spanwise_spacing="uniform", + ), + ], + symmetric=True, + num_chordwise_panels=8, + chordwise_spacing="uniform", + ), + ], + s_ref=None, + c_ref=None, + b_ref=None, + ) return multiple_wing_steady_validation_airplane @@ -98,14 +179,30 @@ def make_asymmetric_unsteady_validation_airplane(): This is the airplane fixture. """ # Create and return the airplane object. - asymmetric_unsteady_validation_airplane = ps.geometry.Airplane(y_ref=5.0, wings=[ - ps.geometry.Wing(num_chordwise_panels=8, chordwise_spacing="uniform", - wing_cross_sections=[ps.geometry.WingCrossSection( - airfoil=ps.geometry.Airfoil(name="naca2412"), num_spanwise_panels=16, - spanwise_spacing="cosine", chord=1.0, ), - ps.geometry.WingCrossSection(y_le=10.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412"), - num_spanwise_panels=16, spanwise_spacing="cosine", ), ], )], ) + asymmetric_unsteady_validation_airplane = ps.geometry.Airplane( + y_ref=5.0, + wings=[ + ps.geometry.Wing( + num_chordwise_panels=8, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil(name="naca2412"), + num_spanwise_panels=16, + spanwise_spacing="cosine", + chord=1.0, + ), + ps.geometry.WingCrossSection( + y_le=10.0, + chord=1.0, + airfoil=ps.geometry.Airfoil(name="naca2412"), + num_spanwise_panels=16, + spanwise_spacing="cosine", + ), + ], + ) + ], + ) return asymmetric_unsteady_validation_airplane @@ -117,14 +214,27 @@ def make_symmetric_unsteady_validation_airplane(): This is the airplane fixture. """ # Create and return the airplane object. - symmetric_unsteady_validation_airplane = ps.geometry.Airplane(wings=[ - ps.geometry.Wing(symmetric=True, chordwise_spacing="uniform", - wing_cross_sections=[ps.geometry.WingCrossSection( - airfoil=ps.geometry.Airfoil(name="naca2412"), chord=2.0, - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(y_le=5.0, chord=2.0, - airfoil=ps.geometry.Airfoil(name="naca2412"), - spanwise_spacing="cosine", ), ], ), ], ) + symmetric_unsteady_validation_airplane = ps.geometry.Airplane( + wings=[ + ps.geometry.Wing( + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil(name="naca2412"), + chord=2.0, + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + y_le=5.0, + chord=2.0, + airfoil=ps.geometry.Airfoil(name="naca2412"), + spanwise_spacing="cosine", + ), + ], + ), + ], + ) return symmetric_unsteady_validation_airplane @@ -136,28 +246,69 @@ def make_symmetric_multiple_wing_unsteady_validation_airplane(): This is the airplane fixture. """ # Create and return the airplane object. - symmetric_multiple_wing_steady_validation_airplane = ps.geometry.Airplane(wings=[ - ps.geometry.Wing(symmetric=True, chordwise_spacing="uniform", - wing_cross_sections=[ps.geometry.WingCrossSection( - airfoil=ps.geometry.Airfoil(name="naca2412"), chord=1.5, - spanwise_spacing="cosine", ), - ps.geometry.WingCrossSection(x_le=0.5, y_le=5.0, z_le=0.0, chord=1.0, - airfoil=ps.geometry.Airfoil(name="naca2412"), - spanwise_spacing="cosine", ), ], ), - ps.geometry.Wing(symmetric=True, z_le=1.75, x_le=6.25, - chordwise_spacing="uniform", wing_cross_sections=[ - ps.geometry.WingCrossSection( - airfoil=ps.geometry.Airfoil(name="naca0010"), - spanwise_spacing="cosine", twist=-5.0, chord=1.00, ), - ps.geometry.WingCrossSection(y_le=1.5, twist=-5.0, chord=0.75, - x_le=0.25, airfoil=ps.geometry.Airfoil(name="naca0010"), - spanwise_spacing="cosine", ), ], ), - ps.geometry.Wing(symmetric=False, z_le=0.125, x_le=6.25, - chordwise_spacing="uniform", wing_cross_sections=[ - ps.geometry.WingCrossSection( - airfoil=ps.geometry.Airfoil(name="naca0010"), - spanwise_spacing="cosine", chord=1.0, ), - ps.geometry.WingCrossSection(z_le=1.5, chord=0.75, x_le=0.25, - airfoil=ps.geometry.Airfoil(name="naca0010"), - spanwise_spacing="cosine", ), ], ), ], ) + symmetric_multiple_wing_steady_validation_airplane = ps.geometry.Airplane( + wings=[ + ps.geometry.Wing( + symmetric=True, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil(name="naca2412"), + chord=1.5, + spanwise_spacing="cosine", + ), + ps.geometry.WingCrossSection( + x_le=0.5, + y_le=5.0, + z_le=0.0, + chord=1.0, + airfoil=ps.geometry.Airfoil(name="naca2412"), + spanwise_spacing="cosine", + ), + ], + ), + ps.geometry.Wing( + symmetric=True, + z_le=1.75, + x_le=6.25, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil(name="naca0010"), + spanwise_spacing="cosine", + twist=-5.0, + chord=1.00, + ), + ps.geometry.WingCrossSection( + y_le=1.5, + twist=-5.0, + chord=0.75, + x_le=0.25, + airfoil=ps.geometry.Airfoil(name="naca0010"), + spanwise_spacing="cosine", + ), + ], + ), + ps.geometry.Wing( + symmetric=False, + z_le=0.125, + x_le=6.25, + chordwise_spacing="uniform", + wing_cross_sections=[ + ps.geometry.WingCrossSection( + airfoil=ps.geometry.Airfoil(name="naca0010"), + spanwise_spacing="cosine", + chord=1.0, + ), + ps.geometry.WingCrossSection( + z_le=1.5, + chord=0.75, + x_le=0.25, + airfoil=ps.geometry.Airfoil(name="naca0010"), + spanwise_spacing="cosine", + ), + ], + ), + ], + ) return symmetric_multiple_wing_steady_validation_airplane diff --git a/tests/integration/fixtures/movement_fixtures.py b/tests/integration/fixtures/movement_fixtures.py index 4b733f35..d69598ab 100644 --- a/tests/integration/fixtures/movement_fixtures.py +++ b/tests/integration/fixtures/movement_fixtures.py @@ -34,27 +34,40 @@ def make_static_validation_movement(): # Construct an airplane object and an operating point object. unsteady_validation_airplane = ( - airplane_fixtures.make_asymmetric_unsteady_validation_airplane()) + airplane_fixtures.make_asymmetric_unsteady_validation_airplane() + ) unsteady_validation_operating_point = ( - operating_point_fixtures.make_validation_operating_point()) + operating_point_fixtures.make_validation_operating_point() + ) # Create a wing cross section movement object associated with this airplane's # root wing cross section. unsteady_validation_root_wing_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[0])) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[0] + ) + ) # Create a wing cross section movement object associated with this airplane's tip # wing cross section. unsteady_validation_tip_wing_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[1], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[1], + ) + ) # Create a wing movement object associated with this airplane's wing. unsteady_validation_wing_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[0], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[0], + wing_cross_sections_movements=[ unsteady_validation_root_wing_cross_section_movement, - unsteady_validation_tip_wing_cross_section_movement, ], ) + unsteady_validation_tip_wing_cross_section_movement, + ], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_root_wing_cross_section_movement @@ -63,7 +76,8 @@ def make_static_validation_movement(): # Create an airplane movement object associated with this airplane. unsteady_validation_airplane_movement = ps.movement.AirplaneMovement( base_airplane=unsteady_validation_airplane, - wing_movements=[unsteady_validation_wing_movement], ) + wing_movements=[unsteady_validation_wing_movement], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane @@ -71,7 +85,8 @@ def make_static_validation_movement(): # Create an operating point movement object associated with this operating point. unsteady_validation_operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=unsteady_validation_operating_point) + base_operating_point=unsteady_validation_operating_point + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_operating_point @@ -80,7 +95,9 @@ def make_static_validation_movement(): unsteady_validation_movement = ps.movement.Movement( airplane_movements=[unsteady_validation_airplane_movement], operating_point_movement=unsteady_validation_operating_point_movement, - num_steps=None, delta_time=None, ) + num_steps=None, + delta_time=None, + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane_movement @@ -99,30 +116,49 @@ def make_variable_validation_movement(): # Construct an airplane object and an operating point object. unsteady_validation_airplane = ( - airplane_fixtures.make_symmetric_unsteady_validation_airplane()) + airplane_fixtures.make_symmetric_unsteady_validation_airplane() + ) unsteady_validation_operating_point = ( - operating_point_fixtures.make_validation_operating_point()) + operating_point_fixtures.make_validation_operating_point() + ) # Create a wing cross section movement object associated with this airplane's # root wing cross section. unsteady_validation_root_wing_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[0], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[0], + ) + ) # Create a wing cross section movement object associated with this airplane's tip # wing cross section. unsteady_validation_tip_wing_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=30.0, sweeping_period=1.0, sweeping_spacing="sine", - pitching_amplitude=30.0, pitching_period=0.5, pitching_spacing="sine", - heaving_amplitude=30.0, heaving_period=0.5, heaving_spacing="sine", )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[1], + sweeping_amplitude=30.0, + sweeping_period=1.0, + sweeping_spacing="sine", + pitching_amplitude=30.0, + pitching_period=0.5, + pitching_spacing="sine", + heaving_amplitude=30.0, + heaving_period=0.5, + heaving_spacing="sine", + ) + ) # Create a wing movement object associated with this airplane's wing. unsteady_validation_wing_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[0], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[0], + wing_cross_sections_movements=[ unsteady_validation_root_wing_cross_section_movement, - unsteady_validation_tip_wing_cross_section_movement, ], ) + unsteady_validation_tip_wing_cross_section_movement, + ], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_root_wing_cross_section_movement @@ -131,7 +167,10 @@ def make_variable_validation_movement(): # Create an airplane movement object associated with this airplane. unsteady_validation_airplane_movement = ps.movement.AirplaneMovement( base_airplane=unsteady_validation_airplane, - wing_movements=[unsteady_validation_wing_movement, ], ) + wing_movements=[ + unsteady_validation_wing_movement, + ], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane @@ -139,7 +178,8 @@ def make_variable_validation_movement(): # Create an operating point movement object associated with this operating point. unsteady_validation_operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=unsteady_validation_operating_point, ) + base_operating_point=unsteady_validation_operating_point, + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_operating_point @@ -147,7 +187,8 @@ def make_variable_validation_movement(): # Create a movement object associated with this airplane and operating point. unsteady_validation_movement = ps.movement.Movement( airplane_movements=[unsteady_validation_airplane_movement], - operating_point_movement=unsteady_validation_operating_point_movement, ) + operating_point_movement=unsteady_validation_operating_point_movement, + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane_movement @@ -167,64 +208,99 @@ def make_multiple_wing_static_validation_movement(): # Construct an airplane object and an operating point object. unsteady_validation_airplane = ( - airplane_fixtures.make_symmetric_multiple_wing_unsteady_validation_airplane()) + airplane_fixtures.make_symmetric_multiple_wing_unsteady_validation_airplane() + ) unsteady_validation_operating_point = ( - operating_point_fixtures.make_validation_operating_point()) + operating_point_fixtures.make_validation_operating_point() + ) # Create a wing cross section movement object associated with this airplane's # main wing's root wing cross section. unsteady_validation_main_wing_root_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[0])) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[0] + ) + ) # Create a wing cross section movement object associated with this airplane's # main wing's tip wing cross section. unsteady_validation_main_wing_tip_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[1], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[1], + ) + ) # Create a wing cross section movement object associated with this airplane's # horizontal stabilizer's root wing cross section. unsteady_validation_hstab_root_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[1].wing_cross_sections[0])) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 1 + ].wing_cross_sections[0] + ) + ) # Create a wing cross section movement object associated with this airplane's # horizontal stabilizer's tip wing cross section. unsteady_validation_hstab_tip_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[1].wing_cross_sections[1], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 1 + ].wing_cross_sections[1], + ) + ) # Create a wing cross section movement object associated with this airplane's # vertical stabilizer's root wing cross section. unsteady_validation_vstab_root_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[2].wing_cross_sections[0])) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 2 + ].wing_cross_sections[0] + ) + ) # Create a wing cross section movement object associated with this airplane's # vertical stabilizer's tip wing cross section. unsteady_validation_vstab_tip_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[2].wing_cross_sections[1], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 2 + ].wing_cross_sections[1], + ) + ) # Create a wing movement object associated with this airplane's main wing. unsteady_validation_main_wing_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[0], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[0], + wing_cross_sections_movements=[ unsteady_validation_main_wing_root_cross_section_movement, - unsteady_validation_main_wing_tip_cross_section_movement, ], ) + unsteady_validation_main_wing_tip_cross_section_movement, + ], + ) # Create a wing movement object associated with this airplane's horizontal # stabilizer. unsteady_validation_hstab_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[1], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[1], + wing_cross_sections_movements=[ unsteady_validation_hstab_root_cross_section_movement, - unsteady_validation_hstab_tip_cross_section_movement, ], ) + unsteady_validation_hstab_tip_cross_section_movement, + ], + ) # Create a wing movement object associated with this airplane's vertical stabilizer. unsteady_validation_vstab_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[2], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[2], + wing_cross_sections_movements=[ unsteady_validation_vstab_root_cross_section_movement, - unsteady_validation_vstab_tip_cross_section_movement, ], ) + unsteady_validation_vstab_tip_cross_section_movement, + ], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_main_wing_root_cross_section_movement @@ -237,8 +313,12 @@ def make_multiple_wing_static_validation_movement(): # Create an airplane movement object associated with this airplane. unsteady_validation_airplane_movement = ps.movement.AirplaneMovement( base_airplane=unsteady_validation_airplane, - wing_movements=[unsteady_validation_main_wing_movement, - unsteady_validation_hstab_movement, unsteady_validation_vstab_movement, ], ) + wing_movements=[ + unsteady_validation_main_wing_movement, + unsteady_validation_hstab_movement, + unsteady_validation_vstab_movement, + ], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane @@ -248,7 +328,8 @@ def make_multiple_wing_static_validation_movement(): # Create an operating point movement object associated with this operating point. unsteady_validation_operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=unsteady_validation_operating_point) + base_operating_point=unsteady_validation_operating_point + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_operating_point @@ -257,7 +338,9 @@ def make_multiple_wing_static_validation_movement(): unsteady_validation_movement = ps.movement.Movement( airplane_movements=[unsteady_validation_airplane_movement], operating_point_movement=unsteady_validation_operating_point_movement, - num_steps=8, delta_time=1 / 8 / 10, ) + num_steps=8, + delta_time=1 / 8 / 10, + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane_movement @@ -277,67 +360,108 @@ def make_multiple_wing_variable_validation_movement(): # Construct an airplane object and an operating point object. unsteady_validation_airplane = ( - airplane_fixtures.make_symmetric_multiple_wing_unsteady_validation_airplane()) + airplane_fixtures.make_symmetric_multiple_wing_unsteady_validation_airplane() + ) unsteady_validation_operating_point = ( - operating_point_fixtures.make_validation_operating_point()) + operating_point_fixtures.make_validation_operating_point() + ) # Create a wing cross section movement object associated with this airplane's # main wing's root wing cross section. unsteady_validation_main_wing_root_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[0], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[0], + ) + ) # Create a wing cross section movement object associated with this airplane's # main wing's tip wing cross section. unsteady_validation_main_wing_tip_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[0].wing_cross_sections[1], - sweeping_amplitude=30.0, sweeping_period=1.0, sweeping_spacing="sine", - heaving_amplitude=15.0, heaving_period=1.0, heaving_spacing="sine", - pitching_amplitude=15.0, pitching_period=0.5, pitching_spacing="sine", )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 0 + ].wing_cross_sections[1], + sweeping_amplitude=30.0, + sweeping_period=1.0, + sweeping_spacing="sine", + heaving_amplitude=15.0, + heaving_period=1.0, + heaving_spacing="sine", + pitching_amplitude=15.0, + pitching_period=0.5, + pitching_spacing="sine", + ) + ) # Create a wing cross section movement object associated with this airplane's # horizontal stabilizer's root wing cross section. unsteady_validation_hstab_root_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[1].wing_cross_sections[0])) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 1 + ].wing_cross_sections[0] + ) + ) # Create a wing cross section movement object associated with this airplane's # horizontal stabilizer's tip wing cross section. unsteady_validation_hstab_tip_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[1].wing_cross_sections[1], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 1 + ].wing_cross_sections[1], + ) + ) # Create a wing cross section movement object associated with this airplane's # vertical stabilizer's root wing cross section. unsteady_validation_vstab_root_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[2].wing_cross_sections[0])) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 2 + ].wing_cross_sections[0] + ) + ) # Create a wing cross section movement object associated with this airplane's # vertical stabilizer's tip wing cross section. unsteady_validation_vstab_tip_cross_section_movement = ( - ps.movement.WingCrossSectionMovement(base_wing_cross_section= - unsteady_validation_airplane.wings[2].wing_cross_sections[1], )) + ps.movement.WingCrossSectionMovement( + base_wing_cross_section=unsteady_validation_airplane.wings[ + 2 + ].wing_cross_sections[1], + ) + ) # Create a wing movement object associated with this airplane's main wing. unsteady_validation_main_wing_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[0], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[0], + wing_cross_sections_movements=[ unsteady_validation_main_wing_root_cross_section_movement, - unsteady_validation_main_wing_tip_cross_section_movement, ], ) + unsteady_validation_main_wing_tip_cross_section_movement, + ], + ) # Create a wing movement object associated with this airplane's horizontal # stabilizer. unsteady_validation_hstab_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[1], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[1], + wing_cross_sections_movements=[ unsteady_validation_hstab_root_cross_section_movement, - unsteady_validation_hstab_tip_cross_section_movement, ], ) + unsteady_validation_hstab_tip_cross_section_movement, + ], + ) # Create a wing movement object associated with this airplane's vertical stabilizer. unsteady_validation_vstab_movement = ps.movement.WingMovement( - base_wing=unsteady_validation_airplane.wings[2], wing_cross_sections_movements=[ + base_wing=unsteady_validation_airplane.wings[2], + wing_cross_sections_movements=[ unsteady_validation_vstab_root_cross_section_movement, - unsteady_validation_vstab_tip_cross_section_movement, ], ) + unsteady_validation_vstab_tip_cross_section_movement, + ], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_main_wing_root_cross_section_movement @@ -350,8 +474,12 @@ def make_multiple_wing_variable_validation_movement(): # Create an airplane movement object associated with this airplane. unsteady_validation_airplane_movement = ps.movement.AirplaneMovement( base_airplane=unsteady_validation_airplane, - wing_movements=[unsteady_validation_main_wing_movement, - unsteady_validation_hstab_movement, unsteady_validation_vstab_movement, ], ) + wing_movements=[ + unsteady_validation_main_wing_movement, + unsteady_validation_hstab_movement, + unsteady_validation_vstab_movement, + ], + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane @@ -361,7 +489,8 @@ def make_multiple_wing_variable_validation_movement(): # Create an operating point movement object associated with this operating point. unsteady_validation_operating_point_movement = ps.movement.OperatingPointMovement( - base_operating_point=unsteady_validation_operating_point) + base_operating_point=unsteady_validation_operating_point + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_operating_point @@ -370,7 +499,9 @@ def make_multiple_wing_variable_validation_movement(): unsteady_validation_movement = ps.movement.Movement( airplane_movements=[unsteady_validation_airplane_movement], operating_point_movement=unsteady_validation_operating_point_movement, - num_steps=20, delta_time=1 / 8 / 10, ) + num_steps=20, + delta_time=1 / 8 / 10, + ) # Delete the now extraneous constructing fixtures. del unsteady_validation_airplane_movement diff --git a/tests/integration/fixtures/problem_fixtures.py b/tests/integration/fixtures/problem_fixtures.py index c8e7f4b1..acc1c361 100644 --- a/tests/integration/fixtures/problem_fixtures.py +++ b/tests/integration/fixtures/problem_fixtures.py @@ -44,12 +44,14 @@ def make_steady_validation_problem(): # Create the constructing fixtures. steady_validation_airplane = airplane_fixtures.make_steady_validation_airplane() steady_validation_operating_point = ( - operating_point_fixtures.make_validation_operating_point()) + operating_point_fixtures.make_validation_operating_point() + ) # Create the problem fixture. steady_validation_problem = ps.problems.SteadyProblem( airplanes=[steady_validation_airplane], - operating_point=steady_validation_operating_point, ) + operating_point=steady_validation_operating_point, + ) # Delete the constructing fixtures. del steady_validation_airplane @@ -69,14 +71,17 @@ def make_steady_multiple_wing_validation_problem(): # Create the constructing fixtures. steady_validation_airplane = ( - airplane_fixtures.make_multiple_wing_steady_validation_airplane()) + airplane_fixtures.make_multiple_wing_steady_validation_airplane() + ) steady_validation_operating_point = ( - operating_point_fixtures.make_validation_operating_point()) + operating_point_fixtures.make_validation_operating_point() + ) # Create the problem fixture. steady_validation_problem = ps.problems.SteadyProblem( airplanes=[steady_validation_airplane], - operating_point=steady_validation_operating_point, ) + operating_point=steady_validation_operating_point, + ) # Delete the constructing fixtures. del steady_validation_airplane @@ -99,7 +104,8 @@ def make_unsteady_validation_problem_with_static_geometry(): # Create the problem fixture. unsteady_validation_problem = ps.problems.UnsteadyProblem( - movement=unsteady_validation_movement) + movement=unsteady_validation_movement + ) # Delete the constructing fixture. del unsteady_validation_movement @@ -121,7 +127,8 @@ def make_unsteady_validation_problem_with_variable_geometry(): # Create the problem fixture. unsteady_validation_problem = ps.problems.UnsteadyProblem( - movement=unsteady_validation_movement) + movement=unsteady_validation_movement + ) # Delete the constructing fixture. del unsteady_validation_movement @@ -140,11 +147,13 @@ def make_unsteady_validation_problem_with_multiple_wing_static_geometry(): # Create the constructing fixture. unsteady_validation_movement = ( - movement_fixtures.make_multiple_wing_static_validation_movement()) + movement_fixtures.make_multiple_wing_static_validation_movement() + ) # Create the problem fixture. unsteady_validation_problem = ps.problems.UnsteadyProblem( - movement=unsteady_validation_movement) + movement=unsteady_validation_movement + ) # Delete the constructing fixture. del unsteady_validation_movement @@ -163,11 +172,13 @@ def make_unsteady_validation_problem_with_multiple_wing_variable_geometry(): # Create the constructing fixture. unsteady_validation_movement = ( - movement_fixtures.make_multiple_wing_variable_validation_movement()) + movement_fixtures.make_multiple_wing_variable_validation_movement() + ) # Create the problem fixture. unsteady_validation_problem = ps.problems.UnsteadyProblem( - movement=unsteady_validation_movement) + movement=unsteady_validation_movement + ) # Delete the constructing fixture. del unsteady_validation_movement diff --git a/tests/integration/test_output.py b/tests/integration/test_output.py index 91effbed..e6afd590 100644 --- a/tests/integration/test_output.py +++ b/tests/integration/test_output.py @@ -49,7 +49,8 @@ def setUp(self): # Set up the constructing fixtures. self.unsteady_solver = ( - solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_static_geometry()) + solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_static_geometry() + ) def tearDown(self): """This method is automatically called before each testing method to tear down the fixtures. @@ -68,8 +69,9 @@ def test_plot_results_versus_time_does_not_throw(self): # Call the plot_results_versus_time method on the solver fixture. The show flag is set to False, # so the figures will not be displayed. - ps.output.plot_results_versus_time(unsteady_solver=self.unsteady_solver, - show=False) + ps.output.plot_results_versus_time( + unsteady_solver=self.unsteady_solver, show=False + ) def test_animate_does_not_throw(self): """This method tests that the animate method does not throw any errors. @@ -78,8 +80,12 @@ def test_animate_does_not_throw(self): """ # Call the animate function on the unsteady solver fixture. - ps.output.animate(unsteady_solver=self.unsteady_solver, scalar_type=None, - show_wake_vortices=False, save=False, ) + ps.output.animate( + unsteady_solver=self.unsteady_solver, + scalar_type=None, + show_wake_vortices=False, + save=False, + ) def test_draw_does_not_throw(self): """This method tests that the draw method does not throw any errors. @@ -88,5 +94,9 @@ def test_draw_does_not_throw(self): """ # Call the draw function on the unsteady solver fixture. - ps.output.draw(solver=self.unsteady_solver, scalar_type=None, - show_wake_vortices=False, show_streamlines=False, ) + ps.output.draw( + solver=self.unsteady_solver, + scalar_type=None, + show_wake_vortices=False, + show_streamlines=False, + ) diff --git a/tests/integration/test_steady_convergence.py b/tests/integration/test_steady_convergence.py index d61a6533..36e52448 100644 --- a/tests/integration/test_steady_convergence.py +++ b/tests/integration/test_steady_convergence.py @@ -45,7 +45,8 @@ def setUp(self): # Create the steady problem. self.steady_validation_problem = ( - problem_fixtures.make_steady_validation_problem()) + problem_fixtures.make_steady_validation_problem() + ) def tearDown(self): """This method tears down the test. @@ -65,8 +66,10 @@ def test_steady_horseshoe_convergence(self): converged_parameters = ps.convergence.analyze_steady_convergence( ref_problem=self.steady_validation_problem, solver_type="steady horseshoe vortex lattice method", - panel_aspect_ratio_bounds=(4, 1), num_chordwise_panels_bounds=(3, 10), - convergence_criteria=1.0, ) + panel_aspect_ratio_bounds=(4, 1), + num_chordwise_panels_bounds=(3, 10), + convergence_criteria=1.0, + ) converged_panel_ar = converged_parameters[0] converged_num_chordwise = converged_parameters[1] @@ -87,8 +90,10 @@ def test_steady_ring_convergence(self): converged_parameters = ps.convergence.analyze_steady_convergence( ref_problem=self.steady_validation_problem, solver_type="steady ring vortex lattice method", - panel_aspect_ratio_bounds=(4, 1), num_chordwise_panels_bounds=(3, 10), - convergence_criteria=1.0, ) + panel_aspect_ratio_bounds=(4, 1), + num_chordwise_panels_bounds=(3, 10), + convergence_criteria=1.0, + ) converged_panel_ar = converged_parameters[0] converged_num_chordwise = converged_parameters[1] diff --git a/tests/integration/test_steady_horseshoe_vortex_lattice_method.py b/tests/integration/test_steady_horseshoe_vortex_lattice_method.py index 8059b203..2db35068 100644 --- a/tests/integration/test_steady_horseshoe_vortex_lattice_method.py +++ b/tests/integration/test_steady_horseshoe_vortex_lattice_method.py @@ -60,9 +60,11 @@ def setUp(self): # Create the steady method solvers. self.steady_horseshoe_vortex_lattice_method_validation_solver = ( - solver_fixtures.make_steady_horseshoe_vortex_lattice_method_validation_solver()) + solver_fixtures.make_steady_horseshoe_vortex_lattice_method_validation_solver() + ) self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver = ( - solver_fixtures.make_steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver()) + solver_fixtures.make_steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver() + ) def tearDown(self): """This method tears down the test. @@ -85,19 +87,25 @@ def test_method(self): c_di_expected = 0.019 c_di_calculated = ( self.steady_horseshoe_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_force_coefficients_wind_axes[0]) + 0 + ].total_near_field_force_coefficients_wind_axes[0] + ) c_di_error = abs(c_di_calculated - c_di_expected) / c_di_expected c_l_expected = 0.790 c_l_calculated = ( self.steady_horseshoe_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_force_coefficients_wind_axes[2]) + 0 + ].total_near_field_force_coefficients_wind_axes[2] + ) c_l_error = abs(c_l_calculated - c_l_expected) / c_l_expected c_m_expected = -0.690 c_m_calculated = ( self.steady_horseshoe_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_moment_coefficients_wind_axes[1]) + 0 + ].total_near_field_moment_coefficients_wind_axes[1] + ) c_m_error = abs(c_m_calculated - c_m_expected) / c_m_expected # Set the allowable percent error. @@ -105,7 +113,10 @@ def test_method(self): ps.output.draw( solver=self.steady_horseshoe_vortex_lattice_method_validation_solver, - show_wake_vortices=False, show_streamlines=True, scalar_type="side force", ) + show_wake_vortices=False, + show_streamlines=True, + scalar_type="side force", + ) # Assert that the percent errors are less than the allowable error. self.assertTrue(abs(c_di_error) < allowable_error) @@ -119,26 +130,33 @@ def test_method_multiple_wings(self): """ # Run the solver. - (self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver - .run()) + ( + self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.run() + ) # Calculate the percent errors of the output. c_di_expected = 0.007 - c_di_calculated = \ - self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_force_coefficients_wind_axes[0] + c_di_calculated = self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[ + 0 + ].total_near_field_force_coefficients_wind_axes[ + 0 + ] c_di_error = abs(c_di_calculated - c_di_expected) / c_di_expected c_l_expected = 0.524 - c_l_calculated = \ - self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_force_coefficients_wind_axes[2] + c_l_calculated = self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[ + 0 + ].total_near_field_force_coefficients_wind_axes[ + 2 + ] c_l_error = abs(c_l_calculated - c_l_expected) / c_l_expected c_m_expected = -0.350 - c_m_calculated = \ - self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_moment_coefficients_wind_axes[1] + c_m_calculated = self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[ + 0 + ].total_near_field_moment_coefficients_wind_axes[ + 1 + ] c_m_error = abs(c_m_calculated - c_m_expected) / c_m_expected # Set the allowable percent error. @@ -146,8 +164,10 @@ def test_method_multiple_wings(self): ps.output.draw( solver=self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver, - scalar_type="induced drag", show_streamlines=True, - show_wake_vortices=False, ) + scalar_type="induced drag", + show_streamlines=True, + show_wake_vortices=False, + ) # Assert that the percent errors are less than the allowable error. self.assertTrue(abs(c_di_error) < allowable_error) diff --git a/tests/integration/test_steady_ring_vortex_lattice_method.py b/tests/integration/test_steady_ring_vortex_lattice_method.py index 24b5b8a5..be7c5719 100644 --- a/tests/integration/test_steady_ring_vortex_lattice_method.py +++ b/tests/integration/test_steady_ring_vortex_lattice_method.py @@ -50,7 +50,8 @@ def setUp(self): # Create the steady method solver. self.steady_ring_vortex_lattice_method_validation_solver = ( - solver_fixtures.make_steady_ring_vortex_lattice_method_validation_solver()) + solver_fixtures.make_steady_ring_vortex_lattice_method_validation_solver() + ) def tearDown(self): """This method tears down the test. @@ -73,26 +74,36 @@ def test_method(self): c_di_expected = 0.019 c_di_calculated = ( self.steady_ring_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_force_coefficients_wind_axes[0]) + 0 + ].total_near_field_force_coefficients_wind_axes[0] + ) c_di_error = abs(c_di_calculated - c_di_expected) / c_di_expected c_l_expected = 0.788 c_l_calculated = ( self.steady_ring_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_force_coefficients_wind_axes[2]) + 0 + ].total_near_field_force_coefficients_wind_axes[2] + ) c_l_error = abs(c_l_calculated - c_l_expected) / c_l_expected c_m_expected = -0.687 c_m_calculated = ( self.steady_ring_vortex_lattice_method_validation_solver.airplanes[ - 0].total_near_field_moment_coefficients_wind_axes[1]) + 0 + ].total_near_field_moment_coefficients_wind_axes[1] + ) c_m_error = abs(c_m_calculated - c_m_expected) / c_m_expected # Set the allowable percent error. allowable_error = 0.10 - ps.output.draw(solver=self.steady_ring_vortex_lattice_method_validation_solver, - show_wake_vortices=False, show_streamlines=True, scalar_type="lift", ) + ps.output.draw( + solver=self.steady_ring_vortex_lattice_method_validation_solver, + show_wake_vortices=False, + show_streamlines=True, + scalar_type="lift", + ) # Assert that the percent errors are less than the allowable error. self.assertTrue(abs(c_di_error) < allowable_error) diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py index a5dc6051..2e4478a7 100644 --- a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py +++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py @@ -48,7 +48,8 @@ def setUp(self): # Create the unsteady method solver. self.unsteady_ring_vortex_lattice_method_validation_solver = ( - solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_multiple_wing_static_geometry()) + solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_multiple_wing_static_geometry() + ) def tearDown(self): """This method tears down the test. @@ -66,8 +67,12 @@ def test_method_does_not_throw(self): # Run the solver. self.unsteady_ring_vortex_lattice_method_validation_solver.run( - prescribed_wake=True, ) + prescribed_wake=True, + ) ps.output.animate( unsteady_solver=self.unsteady_ring_vortex_lattice_method_validation_solver, - show_wake_vortices=True, scalar_type="side force", save=False, ) + show_wake_vortices=True, + scalar_type="side force", + save=False, + ) diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py index 3d89f3e0..a592c599 100644 --- a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py +++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py @@ -23,7 +23,8 @@ class TestUnsteadyRingVortexLatticeMethodMultipleWingVariableGeometry( - unittest.TestCase): + unittest.TestCase +): """This is a class for testing the unsteady ring vortex lattice method solver on multi-wing, variable geometry. @@ -50,7 +51,8 @@ def setUp(self): # Create the unsteady method solver. self.unsteady_ring_vortex_lattice_method_validation_solver = ( - solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_multiple_wing_variable_geometry()) + solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_multiple_wing_variable_geometry() + ) def tearDown(self): """This method tears down the test. @@ -68,8 +70,12 @@ def test_method_does_not_throw(self): # Run the solver. self.unsteady_ring_vortex_lattice_method_validation_solver.run( - prescribed_wake=True, ) + prescribed_wake=True, + ) ps.output.animate( unsteady_solver=self.unsteady_ring_vortex_lattice_method_validation_solver, - scalar_type="induced drag", show_wake_vortices=True, save=False, ) + scalar_type="induced drag", + show_wake_vortices=True, + save=False, + ) diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py index 9267877f..bb60abfd 100644 --- a/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py +++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py @@ -54,7 +54,8 @@ def setUp(self): # Create the unsteady method solver. self.unsteady_ring_vortex_lattice_method_validation_solver = ( - solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_static_geometry()) + solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_static_geometry() + ) def tearDown(self): """This method tears down the test. @@ -72,7 +73,8 @@ def test_method(self): # Run the solver. self.unsteady_ring_vortex_lattice_method_validation_solver.run( - prescribed_wake=True) + prescribed_wake=True + ) this_solver = self.unsteady_ring_vortex_lattice_method_validation_solver this_airplane = this_solver.current_airplanes[0] @@ -95,11 +97,16 @@ def test_method(self): ps.output.animate( unsteady_solver=self.unsteady_ring_vortex_lattice_method_validation_solver, - show_wake_vortices=True, scalar_type="lift", save=False, ) + show_wake_vortices=True, + scalar_type="lift", + save=False, + ) ps.output.plot_results_versus_time( unsteady_solver=self.unsteady_ring_vortex_lattice_method_validation_solver, - show=False, save=False, ) + show=False, + save=False, + ) # Assert that the percent errors are less than the allowable error. self.assertTrue(abs(c_di_error) < allowable_error) diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py index 09ae41b7..45cf0886 100644 --- a/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py +++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py @@ -48,7 +48,8 @@ def setUp(self): # Create the unsteady method solver. self.unsteady_ring_vortex_lattice_method_validation_solver = ( - solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_variable_geometry()) + solver_fixtures.make_unsteady_ring_vortex_lattice_method_validation_solver_with_variable_geometry() + ) def tearDown(self): """This method tears down the test. @@ -65,12 +66,18 @@ def test_method_does_not_throw(self): """ # Run the unsteady solver. self.unsteady_ring_vortex_lattice_method_validation_solver.run( - prescribed_wake=True, ) + prescribed_wake=True, + ) ps.output.animate( unsteady_solver=self.unsteady_ring_vortex_lattice_method_validation_solver, - scalar_type="lift", show_wake_vortices=True, save=False, ) + scalar_type="lift", + show_wake_vortices=True, + save=False, + ) ps.output.plot_results_versus_time( unsteady_solver=self.unsteady_ring_vortex_lattice_method_validation_solver, - show=False, save=False, ) + show=False, + save=False, + ) diff --git a/tests/unit/fixtures/vortex_fixtures.py b/tests/unit/fixtures/vortex_fixtures.py index f37970ce..364fa625 100644 --- a/tests/unit/fixtures/vortex_fixtures.py +++ b/tests/unit/fixtures/vortex_fixtures.py @@ -90,8 +90,11 @@ def make_line_vortex_fixture(): strength_fixture = make_strength_fixture() # Create the line vortex object. - line_vortex_fixture = ps.aerodynamics.LineVortex(origin=origin_fixture, - termination=termination_fixture, strength=strength_fixture, ) + line_vortex_fixture = ps.aerodynamics.LineVortex( + origin=origin_fixture, + termination=termination_fixture, + strength=strength_fixture, + ) # Delete the constructing fixtures. del origin_fixture @@ -139,10 +142,12 @@ def make_horseshoe_vortex_fixture(): # Create the horseshoe vortex object. horseshoe_vortex_fixture = ps.aerodynamics.HorseshoeVortex( - finite_leg_origin=origin_fixture, finite_leg_termination=termination_fixture, + finite_leg_origin=origin_fixture, + finite_leg_termination=termination_fixture, strength=strength_fixture, infinite_leg_direction=infinite_leg_direction_fixture, - infinite_leg_length=infinite_leg_length_fixture, ) + infinite_leg_length=infinite_leg_length_fixture, + ) # Delete the constructing fixtures. del origin_fixture @@ -217,7 +222,9 @@ def make_ring_vortex_fixture(): front_left_vertex=front_left_vertex_fixture, front_right_vertex=front_right_vertex_fixture, back_left_vertex=back_left_vertex_fixture, - back_right_vertex=back_right_vertex_fixture, strength=strength_fixture, ) + back_right_vertex=back_right_vertex_fixture, + strength=strength_fixture, + ) # Delete the constructing fixtures. del front_left_vertex_fixture diff --git a/tests/unit/test_horseshoe_vortex.py b/tests/unit/test_horseshoe_vortex.py index 1feb747d..97404454 100644 --- a/tests/unit/test_horseshoe_vortex.py +++ b/tests/unit/test_horseshoe_vortex.py @@ -49,16 +49,21 @@ def setUp(self): # Get the constructing fixtures. self.horseshoe_vortex_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_horseshoe_vortex_fixture()) + tests.unit.fixtures.vortex_fixtures.make_horseshoe_vortex_fixture() + ) self.origin_fixture = tests.unit.fixtures.vortex_fixtures.make_origin_fixture() self.termination_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_termination_fixture()) + tests.unit.fixtures.vortex_fixtures.make_termination_fixture() + ) self.strength_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_strength_fixture()) + tests.unit.fixtures.vortex_fixtures.make_strength_fixture() + ) self.infinite_leg_direction_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_infinite_leg_direction_fixture()) + tests.unit.fixtures.vortex_fixtures.make_infinite_leg_direction_fixture() + ) self.infinite_leg_length_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_infinite_leg_length_fixture()) + tests.unit.fixtures.vortex_fixtures.make_infinite_leg_length_fixture() + ) def tearDown(self): """This method is automatically called before each testing method to tear @@ -82,38 +87,68 @@ def test_class(self): """ # Test that the objects are all the right type. - self.assertIsInstance(self.horseshoe_vortex_fixture, - ps.aerodynamics.HorseshoeVortex, ) - self.assertIsInstance(self.horseshoe_vortex_fixture.finite_leg, - ps.aerodynamics.LineVortex, ) - self.assertIsInstance(self.horseshoe_vortex_fixture.left_leg, - ps.aerodynamics.LineVortex, ) - self.assertIsInstance(self.horseshoe_vortex_fixture.right_leg, - ps.aerodynamics.LineVortex, ) + self.assertIsInstance( + self.horseshoe_vortex_fixture, + ps.aerodynamics.HorseshoeVortex, + ) + self.assertIsInstance( + self.horseshoe_vortex_fixture.finite_leg, + ps.aerodynamics.LineVortex, + ) + self.assertIsInstance( + self.horseshoe_vortex_fixture.left_leg, + ps.aerodynamics.LineVortex, + ) + self.assertIsInstance( + self.horseshoe_vortex_fixture.right_leg, + ps.aerodynamics.LineVortex, + ) # Test that the vortex objects' coordinates were correctly set. - self.assertTrue(np.allclose(self.horseshoe_vortex_fixture.finite_leg_origin, - self.origin_fixture)) self.assertTrue( - np.allclose(self.horseshoe_vortex_fixture.finite_leg_termination, - self.termination_fixture, )) + np.allclose( + self.horseshoe_vortex_fixture.finite_leg_origin, self.origin_fixture + ) + ) + self.assertTrue( + np.allclose( + self.horseshoe_vortex_fixture.finite_leg_termination, + self.termination_fixture, + ) + ) # Test that the horseshoe vortex object's strength was set correctly. self.assertEqual(self.horseshoe_vortex_fixture.strength, self.strength_fixture) # Test that other class attributes were correctly set. self.assertTrue( - np.allclose(self.horseshoe_vortex_fixture.infinite_leg_direction, - self.infinite_leg_direction_fixture, )) - self.assertEqual(self.horseshoe_vortex_fixture.infinite_leg_length, - self.infinite_leg_length_fixture, ) + np.allclose( + self.horseshoe_vortex_fixture.infinite_leg_direction, + self.infinite_leg_direction_fixture, + ) + ) + self.assertEqual( + self.horseshoe_vortex_fixture.infinite_leg_length, + self.infinite_leg_length_fixture, + ) # Test that the infinite legs' coordinates are correct. - self.assertTrue(np.allclose(self.horseshoe_vortex_fixture.right_leg_origin, - self.horseshoe_vortex_fixture.finite_leg_origin + - self.horseshoe_vortex_fixture.infinite_leg_direction * self.horseshoe_vortex_fixture.infinite_leg_length, )) - self.assertTrue(np.allclose(self.horseshoe_vortex_fixture.left_leg_termination, - self.horseshoe_vortex_fixture.finite_leg_termination + self.horseshoe_vortex_fixture.infinite_leg_direction * self.horseshoe_vortex_fixture.infinite_leg_length, )) + self.assertTrue( + np.allclose( + self.horseshoe_vortex_fixture.right_leg_origin, + self.horseshoe_vortex_fixture.finite_leg_origin + + self.horseshoe_vortex_fixture.infinite_leg_direction + * self.horseshoe_vortex_fixture.infinite_leg_length, + ) + ) + self.assertTrue( + np.allclose( + self.horseshoe_vortex_fixture.left_leg_termination, + self.horseshoe_vortex_fixture.finite_leg_termination + + self.horseshoe_vortex_fixture.infinite_leg_direction + * self.horseshoe_vortex_fixture.infinite_leg_length, + ) + ) def test_update_strength(self): """This method tests the update_strength method. @@ -130,12 +165,15 @@ def test_update_strength(self): # Test that all the strength's have been updated correctly. self.assertEqual(self.horseshoe_vortex_fixture.strength, new_strength_fixture) - self.assertEqual(self.horseshoe_vortex_fixture.right_leg.strength, - new_strength_fixture) - self.assertEqual(self.horseshoe_vortex_fixture.finite_leg.strength, - new_strength_fixture) - self.assertEqual(self.horseshoe_vortex_fixture.left_leg.strength, - new_strength_fixture) + self.assertEqual( + self.horseshoe_vortex_fixture.right_leg.strength, new_strength_fixture + ) + self.assertEqual( + self.horseshoe_vortex_fixture.finite_leg.strength, new_strength_fixture + ) + self.assertEqual( + self.horseshoe_vortex_fixture.left_leg.strength, new_strength_fixture + ) # Revert the change. self.horseshoe_vortex_fixture.update_strength(strength=old_strength_fixture) diff --git a/tests/unit/test_line_vortex.py b/tests/unit/test_line_vortex.py index 39883cb9..3e663e40 100644 --- a/tests/unit/test_line_vortex.py +++ b/tests/unit/test_line_vortex.py @@ -46,12 +46,15 @@ def setUp(self): # Create the constructing fixtures. self.line_vortex_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_line_vortex_fixture()) + tests.unit.fixtures.vortex_fixtures.make_line_vortex_fixture() + ) self.origin_fixture = tests.unit.fixtures.vortex_fixtures.make_origin_fixture() self.termination_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_termination_fixture()) + tests.unit.fixtures.vortex_fixtures.make_termination_fixture() + ) self.strength_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_strength_fixture()) + tests.unit.fixtures.vortex_fixtures.make_strength_fixture() + ) def tearDown(self): """This method is automatically called before each testing method to tear @@ -77,15 +80,19 @@ def test_class(self): # Test that the vortex's coordinates were correctly set. self.assertTrue( - np.allclose(self.line_vortex_fixture.origin, self.origin_fixture)) + np.allclose(self.line_vortex_fixture.origin, self.origin_fixture) + ) self.assertTrue( - np.allclose(self.line_vortex_fixture.termination, self.termination_fixture)) + np.allclose(self.line_vortex_fixture.termination, self.termination_fixture) + ) # Test that the vortex's strength was correctly set. self.assertEqual(self.line_vortex_fixture.strength, self.strength_fixture) # Test that the vortex's center and vector were correctly calculated. self.assertTrue( - np.allclose(self.line_vortex_fixture.center, np.array([0.5, 0.5, 0.5]))) + np.allclose(self.line_vortex_fixture.center, np.array([0.5, 0.5, 0.5])) + ) self.assertTrue( - np.allclose(self.line_vortex_fixture.vector, np.array([1, 1, 1]))) + np.allclose(self.line_vortex_fixture.vector, np.array([1, 1, 1])) + ) diff --git a/tests/unit/test_ring_vortex.py b/tests/unit/test_ring_vortex.py index 1296fb03..84a0f1ba 100644 --- a/tests/unit/test_ring_vortex.py +++ b/tests/unit/test_ring_vortex.py @@ -50,17 +50,23 @@ def setUp(self): # Set up the constructing fixtures. self.ring_vortex_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_ring_vortex_fixture()) + tests.unit.fixtures.vortex_fixtures.make_ring_vortex_fixture() + ) self.strength_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_strength_fixture()) + tests.unit.fixtures.vortex_fixtures.make_strength_fixture() + ) self.front_left_vertex_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_front_left_vertex_fixture()) + tests.unit.fixtures.vortex_fixtures.make_front_left_vertex_fixture() + ) self.front_right_vertex_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_front_right_vertex_fixture()) + tests.unit.fixtures.vortex_fixtures.make_front_right_vertex_fixture() + ) self.back_left_vertex_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_back_left_vertex_fixture()) + tests.unit.fixtures.vortex_fixtures.make_back_left_vertex_fixture() + ) self.back_right_vertex_fixture = ( - tests.unit.fixtures.vortex_fixtures.make_back_right_vertex_fixture()) + tests.unit.fixtures.vortex_fixtures.make_back_right_vertex_fixture() + ) def tearDown(self): """This method is automatically called before each testing method to tear @@ -85,43 +91,83 @@ def test_class(self): # Test that the objects are all the right type. self.assertIsInstance(self.ring_vortex_fixture, ps.aerodynamics.RingVortex) - self.assertIsInstance(self.ring_vortex_fixture.front_leg, - ps.aerodynamics.LineVortex, ) - self.assertIsInstance(self.ring_vortex_fixture.left_leg, - ps.aerodynamics.LineVortex) - self.assertIsInstance(self.ring_vortex_fixture.back_leg, - ps.aerodynamics.LineVortex) - self.assertIsInstance(self.ring_vortex_fixture.right_leg, - ps.aerodynamics.LineVortex, ) + self.assertIsInstance( + self.ring_vortex_fixture.front_leg, + ps.aerodynamics.LineVortex, + ) + self.assertIsInstance( + self.ring_vortex_fixture.left_leg, ps.aerodynamics.LineVortex + ) + self.assertIsInstance( + self.ring_vortex_fixture.back_leg, ps.aerodynamics.LineVortex + ) + self.assertIsInstance( + self.ring_vortex_fixture.right_leg, + ps.aerodynamics.LineVortex, + ) # Test that the vortex objects' coordinates were correctly set. - self.assertTrue(np.allclose(self.ring_vortex_fixture.front_leg.origin, - self.front_right_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.front_leg.termination, - self.front_left_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.left_leg.origin, - self.front_left_vertex_fixture)) - self.assertTrue(np.allclose(self.ring_vortex_fixture.left_leg.termination, - self.back_left_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.back_leg.origin, - self.back_left_vertex_fixture)) - self.assertTrue(np.allclose(self.ring_vortex_fixture.back_leg.termination, - self.back_right_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.right_leg.origin, - self.back_right_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.right_leg.termination, - self.front_right_vertex_fixture, )) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.front_leg.origin, + self.front_right_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.front_leg.termination, + self.front_left_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.left_leg.origin, self.front_left_vertex_fixture + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.left_leg.termination, + self.back_left_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.back_leg.origin, self.back_left_vertex_fixture + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.back_leg.termination, + self.back_right_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.right_leg.origin, + self.back_right_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.right_leg.termination, + self.front_right_vertex_fixture, + ) + ) # Test that the vortex objects' strengths were correctly set. self.assertEqual(self.ring_vortex_fixture.strength, self.strength_fixture) - self.assertEqual(self.ring_vortex_fixture.front_leg.strength, - self.strength_fixture) - self.assertEqual(self.ring_vortex_fixture.left_leg.strength, - self.strength_fixture) - self.assertEqual(self.ring_vortex_fixture.back_leg.strength, - self.strength_fixture) - self.assertEqual(self.ring_vortex_fixture.right_leg.strength, - self.strength_fixture) + self.assertEqual( + self.ring_vortex_fixture.front_leg.strength, self.strength_fixture + ) + self.assertEqual( + self.ring_vortex_fixture.left_leg.strength, self.strength_fixture + ) + self.assertEqual( + self.ring_vortex_fixture.back_leg.strength, self.strength_fixture + ) + self.assertEqual( + self.ring_vortex_fixture.right_leg.strength, self.strength_fixture + ) def test_update_strength(self): """This method tests the update_strength method. @@ -138,14 +184,18 @@ def test_update_strength(self): # Test that all the strength's have been updated correctly. self.assertEqual(self.ring_vortex_fixture.strength, new_strength_fixture) - self.assertEqual(self.ring_vortex_fixture.front_leg.strength, - new_strength_fixture) - self.assertEqual(self.ring_vortex_fixture.left_leg.strength, - new_strength_fixture) - self.assertEqual(self.ring_vortex_fixture.back_leg.strength, - new_strength_fixture) - self.assertEqual(self.ring_vortex_fixture.right_leg.strength, - new_strength_fixture) + self.assertEqual( + self.ring_vortex_fixture.front_leg.strength, new_strength_fixture + ) + self.assertEqual( + self.ring_vortex_fixture.left_leg.strength, new_strength_fixture + ) + self.assertEqual( + self.ring_vortex_fixture.back_leg.strength, new_strength_fixture + ) + self.assertEqual( + self.ring_vortex_fixture.right_leg.strength, new_strength_fixture + ) # Revert the change. self.ring_vortex_fixture.update_strength(strength=old_strength_fixture) @@ -165,52 +215,102 @@ def test_update_position(self): # Create fixtures to hold the soon-to-be new values of the ring vortex's # position. new_front_right_vertex_fixture = old_front_right_vertex_fixture + np.array( - [1, 0, 0]) + [1, 0, 0] + ) new_front_left_vertex_fixture = old_front_left_vertex_fixture + np.array( - [1, 0, 0]) + [1, 0, 0] + ) new_back_left_vertex_fixture = old_back_left_vertex_fixture + np.array( - [1, 0, 0]) + [1, 0, 0] + ) new_back_right_vertex_fixture = old_back_right_vertex_fixture + np.array( - [1, 0, 0]) + [1, 0, 0] + ) # Update the ring vortex fixture's position. self.ring_vortex_fixture.update_position( front_right_vertex=new_front_right_vertex_fixture, front_left_vertex=new_front_left_vertex_fixture, back_left_vertex=new_back_left_vertex_fixture, - back_right_vertex=new_back_right_vertex_fixture, ) + back_right_vertex=new_back_right_vertex_fixture, + ) # Test that the position of the ring vortex was correctly updated. - self.assertTrue(np.allclose(self.ring_vortex_fixture.front_right_vertex, - new_front_right_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.front_left_vertex, - new_front_left_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.back_left_vertex, - new_back_left_vertex_fixture)) - self.assertTrue(np.allclose(self.ring_vortex_fixture.back_right_vertex, - new_back_right_vertex_fixture, )) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.front_right_vertex, + new_front_right_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.front_left_vertex, + new_front_left_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.back_left_vertex, new_back_left_vertex_fixture + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.back_right_vertex, + new_back_right_vertex_fixture, + ) + ) # Check that the positions of the child objects have been correctly updated. - self.assertTrue(np.allclose(self.ring_vortex_fixture.front_leg.origin, - new_front_right_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.front_leg.termination, - new_front_left_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.left_leg.origin, - new_front_left_vertex_fixture)) - self.assertTrue(np.allclose(self.ring_vortex_fixture.left_leg.termination, - new_back_left_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.back_leg.origin, - new_back_left_vertex_fixture)) - self.assertTrue(np.allclose(self.ring_vortex_fixture.back_leg.termination, - new_back_right_vertex_fixture, )) - self.assertTrue(np.allclose(self.ring_vortex_fixture.right_leg.origin, - new_back_right_vertex_fixture)) - self.assertTrue(np.allclose(self.ring_vortex_fixture.right_leg.termination, - new_front_right_vertex_fixture, )) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.front_leg.origin, + new_front_right_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.front_leg.termination, + new_front_left_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.left_leg.origin, new_front_left_vertex_fixture + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.left_leg.termination, + new_back_left_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.back_leg.origin, new_back_left_vertex_fixture + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.back_leg.termination, + new_back_right_vertex_fixture, + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.right_leg.origin, new_back_right_vertex_fixture + ) + ) + self.assertTrue( + np.allclose( + self.ring_vortex_fixture.right_leg.termination, + new_front_right_vertex_fixture, + ) + ) # Revert the changes. self.ring_vortex_fixture.update_position( front_right_vertex=old_front_right_vertex_fixture, front_left_vertex=old_front_left_vertex_fixture, back_left_vertex=old_back_left_vertex_fixture, - back_right_vertex=old_back_right_vertex_fixture, ) + back_right_vertex=old_back_right_vertex_fixture, + )