Unsteady Ring VLM Solver with Multiple Airplanes

"""Demonstrates running Ptera Software's UnsteadyRingVortexLatticeMethodSolver on three
airplanes flying in formation, each with custom flapping wings."""

import pterasoftware as ps

x_spacing = 13
y_spacing = 13

# Create the lead Airplane.
lead_airplane = ps.geometry.airplane.Airplane(
    wings=[
        ps.geometry.wing.Wing(
            wing_cross_sections=[
                ps.geometry.wing_cross_section.WingCrossSection(
                    airfoil=ps.geometry.airfoil.Airfoil(
                        name="naca0012",
                    ),
                    num_spanwise_panels=10,
                    chord=1.75,
                    control_surface_symmetry_type="symmetric",
                    spanwise_spacing="cosine",
                ),
                ps.geometry.wing_cross_section.WingCrossSection(
                    airfoil=ps.geometry.airfoil.Airfoil(
                        name="naca0012",
                    ),
                    num_spanwise_panels=None,
                    chord=1.5,
                    Lp_Wcsp_Lpp=(0.75, 6.0, 0.0),
                    control_surface_symmetry_type="symmetric",
                    spanwise_spacing=None,
                ),
            ],
            name="Main Wing",
            Ler_Gs_Cgs=(0.0, 0.25, 0.0),
            angles_Gs_to_Wn_ixyz=(0.0, 5.0, 0.0),
            symmetric=True,
            symmetryNormal_G=(0.0, 1.0, 0.0),
            symmetryPoint_G_Cg=(0.0, 0.0, 0.0),
            num_chordwise_panels=4,
            chordwise_spacing="uniform",
        ),
    ],
    name="Lead Airplane",
    Cg_GP1_CgP1=(0.0, 0.0, 0.0),
)

# Now define the lead Airplane's AirplaneMovement.
lead_airplane_movement = ps.movements.airplane_movement.AirplaneMovement(
    base_airplane=lead_airplane,
    wing_movements=[
        # Define the main Wing's WingMovement.
        ps.movements.wing_movement.WingMovement(
            base_wing=lead_airplane.wings[0],
            wing_cross_section_movements=[
                # Define the root WingCrossSection's WingCrossSectionMovement.
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=lead_airplane.wings[0].wing_cross_sections[
                        0
                    ]
                ),
                # Define the tip WingCrossSection's WingCrossSectionMovement.
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=lead_airplane.wings[0].wing_cross_sections[
                        1
                    ]
                ),
            ],
            ampAngles_Gs_to_Wn_ixyz=(25.0, 0.0, 0.0),
            periodAngles_Gs_to_Wn_ixyz=(1.0, 0.0, 0.0),
            spacingAngles_Gs_to_Wn_ixyz=("sine", "sine", "sine"),
            phaseAngles_Gs_to_Wn_ixyz=(0.0, 0.0, 0.0),
        ),
        # Define the reflected main Wing's WingMovement.
        ps.movements.wing_movement.WingMovement(
            base_wing=lead_airplane.wings[1],
            wing_cross_section_movements=[
                # Define the root WingCrossSection's WingCrossSectionMovement.
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=lead_airplane.wings[1].wing_cross_sections[
                        0
                    ]
                ),
                # Define the tip WingCrossSection's WingCrossSectionMovement.
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=lead_airplane.wings[1].wing_cross_sections[
                        1
                    ]
                ),
            ],
            ampAngles_Gs_to_Wn_ixyz=(25.0, 0.0, 0.0),
            periodAngles_Gs_to_Wn_ixyz=(1.0, 0.0, 0.0),
            spacingAngles_Gs_to_Wn_ixyz=("sine", "sine", "sine"),
            phaseAngles_Gs_to_Wn_ixyz=(0.0, 0.0, 0.0),
        ),
    ],
)

# Create the trailing right Airplane.
trailing_right_airplane = ps.geometry.airplane.Airplane(
    wings=[
        ps.geometry.wing.Wing(
            wing_cross_sections=[
                ps.geometry.wing_cross_section.WingCrossSection(
                    airfoil=ps.geometry.airfoil.Airfoil(
                        name="naca0012",
                    ),
                    num_spanwise_panels=10,
                    chord=1.75,
                    control_surface_symmetry_type="symmetric",
                    spanwise_spacing="cosine",
                ),
                ps.geometry.wing_cross_section.WingCrossSection(
                    airfoil=ps.geometry.airfoil.Airfoil(
                        name="naca0012",
                    ),
                    num_spanwise_panels=None,
                    chord=1.5,
                    Lp_Wcsp_Lpp=(0.75, 6.0, 0.0),
                    control_surface_symmetry_type="symmetric",
                    spanwise_spacing=None,
                ),
            ],
            name="Main Wing",
            Ler_Gs_Cgs=(0.0, 0.25, 0.0),
            angles_Gs_to_Wn_ixyz=(0.0, 5.0, 0.0),
            symmetric=True,
            symmetryNormal_G=(0.0, 1.0, 0.0),
            symmetryPoint_G_Cg=(0.0, 0.0, 0.0),
            num_chordwise_panels=4,
            chordwise_spacing="uniform",
        ),
    ],
    name="Trailing Right Airplane",
    Cg_GP1_CgP1=(x_spacing, y_spacing, 0.0),
)

# Create the trailing right Airplane's AirplaneMovement.
trailing_right_airplane_movement = ps.movements.airplane_movement.AirplaneMovement(
    base_airplane=trailing_right_airplane,
    wing_movements=[
        ps.movements.wing_movement.WingMovement(
            base_wing=trailing_right_airplane.wings[0],
            wing_cross_section_movements=[
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_right_airplane.wings[
                        0
                    ].wing_cross_sections[0]
                ),
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_right_airplane.wings[
                        0
                    ].wing_cross_sections[1]
                ),
            ],
            ampAngles_Gs_to_Wn_ixyz=(25.0, 0.0, 0.0),
            periodAngles_Gs_to_Wn_ixyz=(1.0, 0.0, 0.0),
            spacingAngles_Gs_to_Wn_ixyz=("sine", "sine", "sine"),
            phaseAngles_Gs_to_Wn_ixyz=(0.0, 0.0, 0.0),
        ),
        ps.movements.wing_movement.WingMovement(
            base_wing=trailing_right_airplane.wings[1],
            wing_cross_section_movements=[
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_right_airplane.wings[
                        1
                    ].wing_cross_sections[0]
                ),
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_right_airplane.wings[
                        1
                    ].wing_cross_sections[1]
                ),
            ],
            ampAngles_Gs_to_Wn_ixyz=(25.0, 0.0, 0.0),
            periodAngles_Gs_to_Wn_ixyz=(1.0, 0.0, 0.0),
            spacingAngles_Gs_to_Wn_ixyz=("sine", "sine", "sine"),
            phaseAngles_Gs_to_Wn_ixyz=(0.0, 0.0, 0.0),
        ),
    ],
)

# Create the trailing left Airplane.
trailing_left_airplane = ps.geometry.airplane.Airplane(
    wings=[
        ps.geometry.wing.Wing(
            wing_cross_sections=[
                ps.geometry.wing_cross_section.WingCrossSection(
                    airfoil=ps.geometry.airfoil.Airfoil(
                        name="naca0012",
                    ),
                    num_spanwise_panels=10,
                    chord=1.75,
                    control_surface_symmetry_type="symmetric",
                    spanwise_spacing="cosine",
                ),
                ps.geometry.wing_cross_section.WingCrossSection(
                    airfoil=ps.geometry.airfoil.Airfoil(
                        name="naca0012",
                    ),
                    num_spanwise_panels=None,
                    chord=1.5,
                    Lp_Wcsp_Lpp=(0.75, 6.0, 0.0),
                    control_surface_symmetry_type="symmetric",
                    spanwise_spacing=None,
                ),
            ],
            name="Main Wing",
            Ler_Gs_Cgs=(0.0, 0.25, 0.0),
            angles_Gs_to_Wn_ixyz=(0.0, 5.0, 0.0),
            symmetric=True,
            symmetryNormal_G=(0.0, 1.0, 0.0),
            symmetryPoint_G_Cg=(0.0, 0.0, 0.0),
            num_chordwise_panels=4,
            chordwise_spacing="uniform",
        ),
    ],
    name="Trailing Left Airplane",
    Cg_GP1_CgP1=(x_spacing, -y_spacing, 0.0),
)

# Create the trailing left Airplane's AirplaneMovement.
trailing_left_airplane_movement = ps.movements.airplane_movement.AirplaneMovement(
    base_airplane=trailing_left_airplane,
    wing_movements=[
        ps.movements.wing_movement.WingMovement(
            base_wing=trailing_left_airplane.wings[0],
            wing_cross_section_movements=[
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_left_airplane.wings[
                        0
                    ].wing_cross_sections[0]
                ),
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_left_airplane.wings[
                        0
                    ].wing_cross_sections[1]
                ),
            ],
            ampAngles_Gs_to_Wn_ixyz=(25.0, 0.0, 0.0),
            periodAngles_Gs_to_Wn_ixyz=(1.0, 0.0, 0.0),
            spacingAngles_Gs_to_Wn_ixyz=("sine", "sine", "sine"),
            phaseAngles_Gs_to_Wn_ixyz=(0.0, 0.0, 0.0),
        ),
        ps.movements.wing_movement.WingMovement(
            base_wing=trailing_left_airplane.wings[1],
            wing_cross_section_movements=[
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_left_airplane.wings[
                        1
                    ].wing_cross_sections[0]
                ),
                ps.movements.wing_cross_section_movement.WingCrossSectionMovement(
                    base_wing_cross_section=trailing_left_airplane.wings[
                        1
                    ].wing_cross_sections[1]
                ),
            ],
            ampAngles_Gs_to_Wn_ixyz=(25.0, 0.0, 0.0),
            periodAngles_Gs_to_Wn_ixyz=(1.0, 0.0, 0.0),
            spacingAngles_Gs_to_Wn_ixyz=("sine", "sine", "sine"),
            phaseAngles_Gs_to_Wn_ixyz=(0.0, 0.0, 0.0),
        ),
    ],
)

# Define an OperatingPoint. This defines the state at which all the Airplanes are
# operating.
example_operating_point = ps.operating_point.OperatingPoint(vCg__E=15.0, alpha=0.0)

# Define the OperatingPoint's OperatingPointMovement.
example_operating_point_movement = (
    ps.movements.operating_point_movement.OperatingPointMovement(
        base_operating_point=example_operating_point
    )
)

# Delete the extraneous pointers to the Airplanes and the OperatingPoint, as these are
# now accessible within their respective movement objects.
del lead_airplane
del trailing_right_airplane
del trailing_left_airplane
del example_operating_point

# Define the Movement. This contains each AirplaneMovement and the
# OperatingPointMovement.
example_movement = ps.movements.movement.Movement(
    airplane_movements=[
        lead_airplane_movement,
        trailing_right_airplane_movement,
        trailing_left_airplane_movement,
    ],
    operating_point_movement=example_operating_point_movement,
    num_cycles=3,
)

# Delete the extraneous pointers to the AirplaneMovements and the
# OperatingPointMovement, as these are now accessible within the Movement.
del lead_airplane_movement
del trailing_right_airplane_movement
del trailing_left_airplane_movement
del example_operating_point_movement

# Using the Movement, create an UnsteadyProblem.
example_problem = ps.problems.UnsteadyProblem(
    movement=example_movement,
)

# Define a new UnsteadyRingVortexLatticeMethodSolver.
example_solver = (
    ps.unsteady_ring_vortex_lattice_method.UnsteadyRingVortexLatticeMethodSolver(
        unsteady_problem=example_problem,
    )
)

# Delete the extraneous pointer to the UnsteadyProblem.
del example_problem

# Run the solver.
example_solver.run(
    prescribed_wake=True,
    show_progress=True,
)
print("The simulation has finished.")

# Save the solved solver to a compressed JSON file. This allows us to load the results
# later without re-running the simulation. Use ".json.gz" for gzip compression, which is
# recommended over plain JSONs for all but the smallest, unmeshed geometry objects.
print("Saving the solver...")
ps.save("example_solver.json.gz", example_solver)
print("Finished saving the solver.")

# Load the saved solver. The loaded object is identical to the original and can be
# passed to any output function.
print("Loading the saved solver...")
loaded_solver = ps.load("example_solver.json.gz")
print("Finished loading the saved solver.")

# Now that we have run the solver, we can create an animation of the results.
ps.output.animate(
    unsteady_solver=loaded_solver,
    scalar_type="lift",
    show_wake_vortices=True,
    save=True,
)

Output

Animation of the unsteady ring VLM simulation of an airplane formation.