diff --git a/database/airfoils/xf-n64015a-il-100000-n5.csv b/database/airfoils/xf-n64015a-il-100000-n5.csv new file mode 100755 index 00000000..8fd8bae1 --- /dev/null +++ b/database/airfoils/xf-n64015a-il-100000-n5.csv @@ -0,0 +1,132 @@ +Xfoil polar. Reynolds number fixed. Mach number fixed +Polar key,xf-n64015a-il-100000-n5 +Airfoil,n64015a-il +Reynolds number,100000 +Ncrit,5 +Mach,0 +Max Cl/Cd,36.8079 +Max Cl/Cd alpha,5.75 +Url,http://airfoiltools.com/polar/csv?polar=xf-n64015a-il-100000-n5 + +Alpha,Cl,Cd,Cdp,Cm,Top_Xtr,Bot_Xtr +-15.000,-0.7511,0.11842,0.11288,-0.0148,1.0000,0.0333 +-14.750,-0.7867,0.10562,0.09996,-0.0228,1.0000,0.0330 +-14.500,-0.8199,0.09535,0.08953,-0.0293,1.0000,0.0327 +-14.250,-0.8495,0.08708,0.08106,-0.0340,1.0000,0.0325 +-14.000,-0.8757,0.08015,0.07393,-0.0373,1.0000,0.0324 +-13.750,-0.8987,0.07420,0.06775,-0.0396,1.0000,0.0323 +-13.500,-0.9188,0.06903,0.06232,-0.0410,1.0000,0.0323 +-13.250,-0.9358,0.06449,0.05752,-0.0416,1.0000,0.0324 +-13.000,-0.9498,0.06046,0.05322,-0.0415,1.0000,0.0325 +-12.750,-0.9606,0.05686,0.04931,-0.0410,1.0000,0.0327 +-12.500,-0.9681,0.05362,0.04577,-0.0399,1.0000,0.0329 +-12.250,-0.9722,0.05071,0.04254,-0.0387,1.0000,0.0332 +-12.000,-0.9719,0.04808,0.03963,-0.0372,1.0000,0.0336 +-11.750,-0.9620,0.04587,0.03732,-0.0364,1.0000,0.0341 +-11.500,-0.9522,0.04404,0.03540,-0.0354,1.0000,0.0348 +-11.250,-0.9424,0.04240,0.03366,-0.0343,1.0000,0.0357 +-11.000,-0.9316,0.04079,0.03191,-0.0331,1.0000,0.0368 +-10.750,-0.9186,0.03916,0.03008,-0.0319,1.0000,0.0381 +-10.500,-0.9023,0.03751,0.02823,-0.0310,1.0000,0.0394 +-10.250,-0.8834,0.03595,0.02644,-0.0301,1.0000,0.0405 +-10.000,-0.8641,0.03443,0.02495,-0.0295,1.0000,0.0417 +-9.750,-0.8476,0.03317,0.02369,-0.0286,1.0000,0.0431 +-9.500,-0.8321,0.03203,0.02250,-0.0274,1.0000,0.0451 +-9.250,-0.8163,0.03096,0.02132,-0.0261,1.0000,0.0476 +-9.000,-0.8020,0.02982,0.02015,-0.0246,1.0000,0.0499 +-8.750,-0.7908,0.02879,0.01915,-0.0227,1.0000,0.0522 +-8.500,-0.7799,0.02784,0.01818,-0.0206,1.0000,0.0549 +-8.250,-0.7688,0.02702,0.01725,-0.0183,1.0000,0.0584 +-8.000,-0.7616,0.02606,0.01638,-0.0156,1.0000,0.0620 +-7.750,-0.7532,0.02530,0.01560,-0.0128,1.0000,0.0665 +-7.500,-0.7476,0.02453,0.01485,-0.0095,1.0000,0.0713 +-7.250,-0.7432,0.02385,0.01420,-0.0060,1.0000,0.0772 +-7.000,-0.7404,0.02323,0.01360,-0.0022,1.0000,0.0840 +-6.750,-0.7294,0.02252,0.01292,-0.0001,0.9968,0.0951 +-6.500,-0.7004,0.02138,0.01195,-0.0017,0.9866,0.1203 +-6.250,-0.6737,0.02012,0.01099,-0.0030,0.9760,0.1690 +-6.000,-0.6517,0.01859,0.01001,-0.0037,0.9646,0.2620 +-5.750,-0.6341,0.01723,0.00949,-0.0029,0.9524,0.3973 +-5.500,-0.6063,0.01690,0.00945,-0.0031,0.9429,0.4872 +-5.250,-0.5777,0.01681,0.00936,-0.0032,0.9324,0.5301 +-5.000,-0.5490,0.01678,0.00928,-0.0033,0.9223,0.5593 +-4.750,-0.5182,0.01679,0.00920,-0.0037,0.9137,0.5842 +-4.500,-0.4915,0.01690,0.00927,-0.0031,0.9029,0.6069 +-4.250,-0.4622,0.01708,0.00941,-0.0028,0.8947,0.6293 +-4.000,-0.4353,0.01720,0.00948,-0.0022,0.8845,0.6456 +-3.750,-0.4070,0.01723,0.00942,-0.0020,0.8756,0.6573 +-3.500,-0.3802,0.01714,0.00920,-0.0018,0.8665,0.6680 +-3.250,-0.3515,0.01714,0.00914,-0.0017,0.8575,0.6751 +-3.000,-0.3248,0.01698,0.00884,-0.0016,0.8491,0.6836 +-2.750,-0.2967,0.01694,0.00875,-0.0015,0.8402,0.6888 +-2.500,-0.2691,0.01682,0.00853,-0.0015,0.8324,0.6956 +-2.250,-0.2429,0.01672,0.00836,-0.0012,0.8234,0.7017 +-2.000,-0.2145,0.01665,0.00822,-0.0012,0.8161,0.7071 +-1.750,-0.1892,0.01655,0.00804,-0.0010,0.8077,0.7143 +-1.500,-0.1610,0.01649,0.00795,-0.0009,0.8004,0.7195 +-1.250,-0.1341,0.01645,0.00787,-0.0008,0.7927,0.7253 +-1.000,-0.1082,0.01637,0.00772,-0.0005,0.7851,0.7326 +-0.750,-0.0800,0.01636,0.00770,-0.0005,0.7786,0.7378 +-0.500,-0.0541,0.01633,0.00766,-0.0003,0.7709,0.7447 +-0.250,-0.0262,0.01629,0.00757,-0.0002,0.7652,0.7509 +0.000,0.0000,0.01632,0.00764,0.0000,0.7572,0.7572 +0.250,0.0263,0.01629,0.00757,0.0002,0.7509,0.7652 +0.500,0.0541,0.01633,0.00766,0.0003,0.7447,0.7709 +0.750,0.0801,0.01636,0.00770,0.0005,0.7378,0.7786 +1.000,0.1082,0.01637,0.00772,0.0005,0.7326,0.7851 +1.250,0.1341,0.01645,0.00787,0.0008,0.7253,0.7927 +1.500,0.1610,0.01649,0.00795,0.0009,0.7195,0.8005 +1.750,0.1892,0.01655,0.00804,0.0010,0.7143,0.8077 +2.000,0.2145,0.01665,0.00822,0.0012,0.7071,0.8161 +2.250,0.2429,0.01672,0.00836,0.0012,0.7017,0.8235 +2.500,0.2691,0.01682,0.00853,0.0015,0.6956,0.8324 +2.750,0.2967,0.01694,0.00875,0.0015,0.6888,0.8402 +3.000,0.3248,0.01698,0.00884,0.0016,0.6836,0.8491 +3.250,0.3515,0.01713,0.00914,0.0017,0.6751,0.8575 +3.500,0.3802,0.01714,0.00920,0.0018,0.6680,0.8665 +3.750,0.4070,0.01723,0.00942,0.0020,0.6573,0.8756 +4.000,0.4353,0.01720,0.00947,0.0022,0.6456,0.8845 +4.250,0.4622,0.01707,0.00941,0.0028,0.6293,0.8947 +4.500,0.4915,0.01690,0.00927,0.0031,0.6069,0.9030 +4.750,0.5182,0.01678,0.00920,0.0037,0.5842,0.9137 +5.000,0.5490,0.01678,0.00928,0.0033,0.5593,0.9224 +5.250,0.5777,0.01681,0.00936,0.0032,0.5300,0.9324 +5.500,0.6063,0.01690,0.00944,0.0031,0.4872,0.9429 +5.750,0.6342,0.01723,0.00949,0.0029,0.3973,0.9525 +6.000,0.6518,0.01859,0.01000,0.0036,0.2619,0.9646 +6.250,0.6738,0.02012,0.01099,0.0030,0.1690,0.9760 +6.500,0.7005,0.02138,0.01194,0.0017,0.1202,0.9867 +6.750,0.7295,0.02252,0.01291,0.0000,0.0951,0.9969 +7.000,0.7404,0.02322,0.01359,0.0022,0.0840,1.0000 +7.250,0.7432,0.02385,0.01420,0.0060,0.0772,1.0000 +7.500,0.7476,0.02453,0.01484,0.0095,0.0713,1.0000 +7.750,0.7533,0.02529,0.01560,0.0128,0.0665,1.0000 +8.000,0.7617,0.02605,0.01637,0.0155,0.0619,1.0000 +8.250,0.7690,0.02701,0.01725,0.0182,0.0583,1.0000 +8.500,0.7802,0.02784,0.01818,0.0205,0.0549,1.0000 +8.750,0.7911,0.02879,0.01915,0.0227,0.0522,1.0000 +9.000,0.8025,0.02982,0.02015,0.0245,0.0499,1.0000 +9.250,0.8168,0.03096,0.02132,0.0260,0.0475,1.0000 +9.500,0.8327,0.03203,0.02250,0.0273,0.0451,1.0000 +9.750,0.8482,0.03317,0.02370,0.0285,0.0431,1.0000 +10.000,0.8647,0.03443,0.02496,0.0294,0.0417,1.0000 +10.250,0.8841,0.03596,0.02645,0.0300,0.0405,1.0000 +10.500,0.9029,0.03752,0.02824,0.0309,0.0394,1.0000 +10.750,0.9192,0.03916,0.03009,0.0318,0.0381,1.0000 +11.000,0.9323,0.04080,0.03192,0.0329,0.0368,1.0000 +11.250,0.9431,0.04241,0.03367,0.0341,0.0357,1.0000 +11.500,0.9530,0.04404,0.03541,0.0352,0.0348,1.0000 +11.750,0.9629,0.04588,0.03733,0.0362,0.0341,1.0000 +12.000,0.9729,0.04811,0.03965,0.0371,0.0336,1.0000 +12.250,0.9729,0.05073,0.04258,0.0385,0.0332,1.0000 +12.500,0.9688,0.05366,0.04582,0.0398,0.0329,1.0000 +12.750,0.9614,0.05690,0.04936,0.0408,0.0327,1.0000 +13.000,0.9507,0.06050,0.05327,0.0414,0.0325,1.0000 +13.250,0.9367,0.06454,0.05758,0.0414,0.0324,1.0000 +13.500,0.9196,0.06911,0.06241,0.0408,0.0323,1.0000 +13.750,0.8995,0.07430,0.06785,0.0394,0.0323,1.0000 +14.000,0.8764,0.08028,0.07406,0.0370,0.0324,1.0000 +14.250,0.8501,0.08724,0.08122,0.0336,0.0325,1.0000 +14.500,0.8206,0.09555,0.08973,0.0289,0.0327,1.0000 +14.750,0.7872,0.10590,0.10025,0.0224,0.0330,1.0000 +15.000,0.7514,0.11884,0.11330,0.0143,0.0333,1.0000 diff --git a/docs/make.jl b/docs/make.jl index 70a21bf5..5d50e5ed 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -50,6 +50,7 @@ makedocs( "examples/blownwing-aero.md", # "examples/blownwing-acoustics.md", "examples/blownwing-asm.md", + "examples/prowim-aero.md", ], "eVTOL Aircraft" => [ "examples/vahana-vehicle.md", diff --git a/docs/src/api/flowunsteady-openvsp.md b/docs/src/api/flowunsteady-openvsp.md index 1eb962d7..955c22b2 100644 --- a/docs/src/api/flowunsteady-openvsp.md +++ b/docs/src/api/flowunsteady-openvsp.md @@ -1,4 +1,4 @@ -# [Importing OpenVSP geometry](@id openvsp_import) +# [OpenVSP geometry](@id openvsp_import) ```@docs FLOWUnsteady.read_degengeom diff --git a/docs/src/examples/blownwing-aero.md b/docs/src/examples/blownwing-aero.md index a9ed7012..106b67a2 100644 --- a/docs/src/examples/blownwing-aero.md +++ b/docs/src/examples/blownwing-aero.md @@ -1,4 +1,4 @@ -# [Aerodynamic Solution](@id blownwingaero) +# [Wing-on-Prop Interactions](@id blownwingaero) ```@raw html
@@ -10,6 +10,17 @@
``` +In this example we show mount propellers on a swept wing. +The wing is modeled using the actuator line model that represents the wing +as a lifting line. +This wing model is accurate for capturing wing-on-prop interactions. +For instance, the rotor will experience an unsteady blade loading (and +increased tonal noise) caused by the turning of the flow ahead of the wing +leading edge. +However, this simple wing model is not adecuate for capturing +prop-on-wing interactions (see [the next two sections](@ref asm) to +accurately predict prop-on-wing interactions). + ```julia #=############################################################################## @@ -199,8 +210,8 @@ for ri in 1:nrotors # Account for angle of attack of wing nrm = sqrt(x^2 + z^2) - x = nrm*cosd(AOAwing) - z = -nrm*sind(AOAwing) + x = (x==0 ? 1 : sign(x))*nrm*cosd(AOAwing) + z = -(z==0 ? 1 : sign(z))*nrm*sind(AOAwing) # Translate rotor to its position along wing O = [x, y, z] # New position diff --git a/docs/src/examples/blownwing-asm.md b/docs/src/examples/blownwing-asm.md index 5c05be38..c01356fe 100644 --- a/docs/src/examples/blownwing-asm.md +++ b/docs/src/examples/blownwing-asm.md @@ -17,13 +17,17 @@ vorticity at the three-quarter chord, as shown here: The ALM is very accurate for isolated wings and even cases with mild wake interactions. However, for cases with stronger wake interactions (e.g., a wake directly -impinging on the wing surface), we have developed an actuator surface model -(ASM) that introduces the surface vorticity into the LES domain that better -represents the physics. -This is done by spreading the surface vorticity following a pressure-like -distribution, which ends up producing a velocity field at the wing surface -that minimizes the flow that crosses the airfoil centerline, thus better -representing a solid surface: +impinging on the wing surface), the ALM can lead to unphysical results as +the flow tends to cross the airfoil centerline. +To address this, we have developed an actuator surface model +(ASM) to embed the wing surface in the LES domain and better +represent the physics. + +The ASM spreads the surface vorticity following a pressure-like +distribution. +This produces a velocity field at the wing surface that minimizes the mass +flow that crosses the airfoil centerline, thus better representing a solid +surface: ```@raw html
@@ -32,9 +36,11 @@ representing a solid surface:

``` -For an in-depth discussion of the actuator line and surface models -implemented in FLOWUnsteady, see Chapter 6 in -[Alvarez' Dissertation](https://scholarsarchive.byu.edu/etd/9589).[^2] +For an in-depth discussion of the actuator models implemented in +FLOWUnsteady, see Chapter 6 in +[Alvarez' Dissertation](https://scholarsarchive.byu.edu/etd/9589)[^2] +(also published in +[Alvarez & Ning, 2023](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[^3]). [^2]: E. J. Alvarez (2022), "Reformulated Vortex Particle Method and @@ -42,6 +48,10 @@ implemented in FLOWUnsteady, see Chapter 6 in Dissertation, Brigham Young University*. [**[VIDEO]**](https://www.nas.nasa.gov/pubs/ams/2022/08-09-22.html) [**[PDF]**](https://scholarsarchive.byu.edu/etd/9589/) +[^3]: E. J. Alvarez and A. Ning (2023), "Meshless Large-Eddy Simulation of + Propeller–Wing Interactions with Reformulated Vortex Particle Method," + *Journal of Aircraft*. + [**[DOI]**](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[**[PDF]**](https://scholarsarchive.byu.edu/facpub/6902/) In order to activate the actuator surface model, we define the following parameters: @@ -74,7 +84,7 @@ include_unsteadyforce = true # Include unsteady force add_unsteadyforce = false # Whether to add the unsteady force to Ftot or to simply output it include_parasiticdrag = true # Include parasitic-drag force -add_skinfriction = true # If false, the parasitic drag is purely parasitic, meaning no skin friction +add_skinfriction = true # If false, the parasitic drag is purely form, meaning no skin friction calc_cd_from_cl = false # Whether to calculate cd from cl or effective AOA wing_polar_file = "xf-rae101-il-1000000.csv" # Airfoil polar for parasitic drag ``` @@ -88,7 +98,7 @@ that uses the vortex sheet: forces = [] # Calculate Kutta-Joukowski force -kuttajoukowski = uns.generate_calc_aerodynamicforce_kuttajoukowski(KJforce_type, +kuttajoukowski = uns.generate_aerodynamicforce_kuttajoukowski(KJforce_type, sigma_vlm_surf, sigma_rotor_surf, vlm_vortexsheet, vlm_vortexsheet_overlap, vlm_vortexsheet_distribution, @@ -173,10 +183,8 @@ uns.run_simulation( ... ) ``` -!!! info "ASM and High Fidelity" - ASM uses a very high density of particles at the wing - surface (~100k particles per wing) to accuratelly introduce the solid - boundary into the LES. - This increases the computational cost of the simulation considerably. - Hence, we recommend using ASM only for high-fidelity simulations. +!!! info "ASM Example" + The [next section](@ref prowimaero) shows an example on how to + set up and run a simulation using the actuator surface model. + diff --git a/docs/src/examples/prowim-aero.md b/docs/src/examples/prowim-aero.md new file mode 100644 index 00000000..2a5833b4 --- /dev/null +++ b/docs/src/examples/prowim-aero.md @@ -0,0 +1,673 @@ +# [Prop-on-Wing Interactions](@id prowimaero) + +In this example we use the [actuator surface model](@ref asm) (ASM) to +more accurately predict the effects of props blowing on a wing. +This case simulates the PROWIM experiment in +[Leo Veldhuis' dissertation](https://repository.tudelft.nl/islandora/object/uuid%3A8ffbde9c-b483-40de-90e0-97095202fbe3) +(2005), and reproduces the validation study published in +[Alvarez & Ning (2023)](https://arc.aiaa.org/doi/10.2514/1.C037279). + +In this example you can vary the fidelity of the simulation setting the +following parameters: + +| Parameter | Mid-low fidelity | Mid-high fidelity | High fidelity | Description | +| :-------: | :--------------: | :---------------: | :-----------: | :---------- | +| `n_wing` | `50` | `50` | `100` | Number of wing elements per semispan | +| `n_rotor` | `12` | `20` | `50` | Number of blade elements per blade | +| `nsteps_per_rev` | `36` | `36` | `72` | Time steps per revolution | +| `p_per_step` | `2` | `5` | `5` | Particle sheds per time step | +| `shed_starting` | `false` | `false` | `true` | Whether to shed starting vortex | +| `shed_unsteady` | `false` | `false` | `true` | Whether to shed vorticity from unsteady loading | +| `treat_wake` | `true` | `true` | `false` | Treat wake to avoid instabilities | +| `vlm_vortexsheet_overlap` | `2.125/10` | `2.125/10` | `2.125` | Particle overlap in ASM vortex sheet | +| `vpm_integration` | `vpm.euler` | RK3``^\star`` | RK3``^\star`` | VPM time integration scheme | +| `vpm_SFS` | None``^\dag`` | Dynamic``^\ddag`` | Dynamic``^\ddag`` | VPM LES subfilter-scale model | + +* ``^\star``*RK3:* `vpm_integration = vpm.rungekutta3` +* ``^\dag``*None:* `vpm_SFS = vpm.SFS_none` +* ``^\ddag``*Dynamic:* `vpm_SFS = vpm.SFS_Cd_twolevel_nobackscatter` + +(Mid-low fidelity settings may be inadequate for capturing prop-on-wing interactions, unless using `p_per_step=5`) + + + +```julia +#=############################################################################## +# DESCRIPTION + Validation of prop-on-wing interactions with twin props mounted mid span + blowing on a wing. This case simulates the PROWIM experiment in Leo + Veldhuis' dissertation (2005), “Propeller Wing Aerodynamic Interference.” + + In this simulation we use the actuator surface model for the wing in order + to accurately capture prop-on-wing interactional effects. The rotors still + use the actuator line model. + + The high-fidelity settings replicate the results presented in Alvarez & + Ning (2023), "Meshless Large-Eddy Simulation of Propeller–Wing Interactions + with Reformulated Vortex Particle Method," Sec. IV.B, also available in + Alvarez (2022), "Reformulated Vortex Particle Method and Meshless Large Eddy + Simulation of Multirotor Aircraft," Sec. 8.4. + +# ABOUT + * Author : Eduardo J. Alvarez (edoalvarez.com) + * Email : Edo.AlvarezR@gmail.com + * Created : January 2024 + * Last updated : January 2024 + * License : MIT +=############################################################################### + + +import FLOWUnsteady as uns +import FLOWUnsteady: vlm, vpm + +run_name = "prowim" # Name of this simulation +save_path = run_name*"-example2" # Where to save this simulation +prompt = true # Whether to prompt the user +paraview = true # Whether to visualize with Paraview + +add_wing = true # Whether to add wing to simulation +add_rotors = true # Whether to add rotors to simulation + +# ----------------- GEOMETRY PARAMETERS ---------------------------------------- +# Wing geometry +b = 2*0.64 # (m) span length +ar = 5.33 # Aspect ratio b/c_tip +tr = 1.0 # Taper ratio c_tip/c_root +twist_root = 0.0 # (deg) twist at root +twist_tip = 0.0 # (deg) twist at tip +lambda = 0.0 # (deg) sweep +gamma = 0.0 # (deg) dihedral +thickness_w = 0.15 # Thickness t/c of wing airfoil + +# Rotor geometry +rotor_file = "beaver.csv" # Rotor geometry +data_path = uns.def_data_path # Path to rotor database +read_polar = vlm.ap.read_polar2 # What polar reader to use +pitch = 2.5 # (deg) collective pitch of blades +xfoil = false # Whether to run XFOIL +ncrit = 6 # Turbulence criterion for XFOIL + +# Read radius of this rotor and number of blades +R, B = uns.read_rotor(rotor_file; data_path=data_path)[[1,3]] + +# Vehicle assembly +AOAwing = 0.0 # (deg) wing angle of attack +spanpos = [-0.46875, 0.46875] # Semi-span position of each rotor, 2*y/b +xpos = [-0.8417, -0.8417] # x-position of rotors relative to LE, x/c +zpos = [0.0, 0.0] # z-position of rotors relative to LE, z/c +CWs = [false, true] # Rotation direction of each rotor: outboard up +# CWs = [true, false] # Rotation direction of each rotor: inboard up +nrotors = length(spanpos) # Number of rotors + +# Discretization +n_wing = 50 # Number of spanwise elements per side +r_wing = 2.0 # Geometric expansion of elements +# n_rotor = 20 # Number of blade elements per blade +n_rotor = 12 +r_rotor = 1/10 # Geometric expansion of elements + +# Check that we declared all the inputs that we need for each rotor +@assert nrotors==length(spanpos)==length(xpos)==length(zpos)==length(CWs) ""* + "Invalid rotor inputs! Check that spanpos, xpos, zpos, and CWs have the same length" + +# ----------------- SIMULATION PARAMETERS -------------------------------------- +# Freestream +magVinf = 49.5 # (m/s) freestream velocity +AOA = 4.0 # (deg) vehicle angle of attack +rho = 1.225 # (kg/m^3) air density +mu = 1.79e-5 # (kg/ms) air dynamic viscosity +speedofsound = 342.35 # (m/s) speed of sound +qinf = 0.5*rho*magVinf^2 # (Pa) reference static pressure +Vinf(X, t) = magVinf*[cosd(AOA), 0, sind(AOA)] # Freestream function + +# Rotor operation +J = 0.85 # Advance ratio Vinf/(nD) +RPM = 60*magVinf/(J*2*R) # RPM + +# Reference non-dimensional parameters +Rec = rho * magVinf * (b/ar) / mu # Chord-based wing Reynolds number +ReD = 2*pi*RPM/60*R * rho/mu * 2*R # Diameter-based rotor Reynolds number +Mtip = 2*pi*RPM/60 * R / speedofsound # Tip Mach number + +println(""" + Vinf: $(round(magVinf, digits=1)) m/s + RPM: $(RPM) + Mtip: $(round(Mtip, digits=3)) + ReD: $(round(ReD, digits=0)) + Rec: $(round(Rec, digits=0)) +""") + + +# NOTE: Modify the variable `AOA` in order to change the angle of attack. +# `AOAwing` will only change the angle of attack of the wing (while +# leaving the propellers unaffected), while `AOA` changes the angle of +# attack of the freestream (affecting both wing and props). + +# ----------------- SOLVER PARAMETERS ------------------------------------------ + +# Aerodynamic solver +VehicleType = uns.UVLMVehicle # Unsteady solver +# VehicleType = uns.QVLMVehicle # Quasi-steady solver +const_solution = VehicleType==uns.QVLMVehicle # Whether to assume that the + # solution is constant or not +# Time parameters +nrevs = 8 # Number of revolutions in simulation +nsteps_per_rev = 36 # Time steps per revolution +nsteps = const_solution ? 2 : nrevs*nsteps_per_rev # Number of time steps +ttot = nsteps/nsteps_per_rev / (RPM/60) # (s) total simulation time + +# VPM particle shedding +# p_per_step = 5 # Sheds per time step +p_per_step = 2 +shed_starting = false # Whether to shed starting vortex (NOTE: starting vortex might make simulation unstable with AOA>8) +shed_unsteady = false # Whether to shed vorticity from unsteady loading +unsteady_shedcrit = 0.001 # Shed unsteady loading whenever circulation + # fluctuates by more than this ratio +treat_wake = true # Treat wake to avoid instabilities +max_particles = 1 # Maximum number of particles +max_particles += add_rotors * (nrotors*((2*n_rotor+1)*B)*nsteps*p_per_step) +max_particles += add_wing * (nsteps+1)*(2*n_wing*(p_per_step+1) + p_per_step) + +# Regularization +sigma_vlm_surf = b/200 # VLM-on-VPM smoothing radius (σLBV of wing) +sigma_rotor_surf= R/80 # Rotor-on-VPM smoothing radius (σ of rotor) +lambda_vpm = 2.125 # VPM core overlap + # VPM smoothing radius (σ of wakes) +sigma_vpm_overwrite = lambda_vpm * 2*pi*R/(nsteps_per_rev*p_per_step) + +# Rotor solver +vlm_rlx = 0.3 # VLM relaxation <-- this also applied to rotors +hubtiploss_correction = ( (0.75, 10, 0.5, 0.05), (1, 1, 1, 1.0) ) # Hub/tip correction +# VPM solver +# vpm_integration = vpm.rungekutta3 # VPM temporal integration scheme +vpm_integration = vpm.euler + +vpm_viscous = vpm.Inviscid() # VPM viscous diffusion scheme + # Uncomment this to make it viscous +# vpm_viscous = vpm.CoreSpreading(-1, -1, vpm.zeta_fmm; beta=100.0, itmax=20, tol=1e-1) + +vpm_SFS = vpm.SFS_none # VPM LES subfilter-scale model +# vpm_SFS = vpm.DynamicSFS(vpm.Estr_fmm, vpm.pseudo3level_positive; + # alpha=0.999, maxC=1.0, + # clippings=[vpm.clipping_backscatter]) + +# NOTE: By default we make this simulation inviscid since at such high Reynolds +# number the viscous effects in the wake are actually negligible. +# Notice that while viscous diffusion is negligible, turbulent diffusion +# is important and non-negigible, so we have activated the subfilter-scale +# (SFS) model. + +if VehicleType == uns.QVLMVehicle + # Mute warnings regarding potential colinear vortex filaments. This is + # needed since the quasi-steady solver will probe induced velocities at the + # lifting line of the blade + uns.vlm.VLMSolver._mute_warning(true) +end + +println(""" + Resolving wake for $(round(ttot*magVinf/b, digits=1)) span distances +""") + + + + + +# ----------------- ACTUATOR SURFACE MODEL PARAMETERS (WING) ------------------- + +# ---------- Vortex sheet parameters --------------- +vlm_vortexsheet = true # Spread wing circulation as a vortex sheet (activates the ASM) +vlm_vortexsheet_overlap = 2.125/10 # Particle overlap in vortex sheet +vlm_vortexsheet_distribution = uns.g_pressure # Distribution of the vortex sheet + +vlm_vortexsheet_sigma_tbv = thickness_w*(b/ar) / 128 # Smoothing radius of trailing bound vorticity, σTBV for VLM-on-VPM + +vlm_vortexsheet_maxstaticparticle = 10^6 # Particles to preallocate for vortex sheet + +if add_wing && vlm_vortexsheet + max_particles += vlm_vortexsheet_maxstaticparticle +end + + +# ---------- Force calculation parameters ---------- +KJforce_type = "regular" # KJ force evaluated at middle of bound vortices +# KJforce_type = "averaged" # KJ force evaluated at average vortex sheet +# KJforce_type = "weighted" # KJ force evaluated at strength-weighted vortex sheet + +include_trailingboundvortex = false # Include trailing bound vortices in force calculations + +include_freevortices = false # Include free vortices in force calculation +include_freevortices_TBVs = false # Include trailing bound vortex in free-vortex force + +include_unsteadyforce = true # Include unsteady force +add_unsteadyforce = false # Whether to add the unsteady force to Ftot or to simply output it + +include_parasiticdrag = true # Include parasitic-drag force +add_skinfriction = true # If false, the parasitic drag is purely form, meaning no skin friction +calc_cd_from_cl = true # Whether to calculate cd from cl or effective AOA +# calc_cd_from_cl = false + +# NOTE: We use a polar at a low Reynolds number (100k as opposed to 600k from +# the experiment) as this particular polar better resembles the drag of +# the tripped airfoil used in the experiment +wing_polar_file = "xf-n64015a-il-100000-n5.csv" # Airfoil polar for parasitic drag (from airfoiltools.com) + + +if include_freevortices && Threads.nthreads()==1 + @warn("Free-vortex force calculation requested, but Julia was initiated"* + " with only one CPU thread. This will be extremely slow!"* + " Initate Julia with `-t num` where num is the number of cores"* + " availabe to speed up the computation.") +end + + + + + +# ----------------- WAKE TREATMENT --------------------------------------------- + +wake_treatments = [] + +# Remove particles by particle strength: remove particles neglibly weak, remove +# particles potentially blown up +rmv_strngth = 2.0 * magVinf*(b/ar)/2 * magVinf*ttot/nsteps/p_per_step # Reference strength (maxCL=2.0) +minmaxGamma = rmv_strngth*[0.0001, 10.0] # Strength bounds (removes particles outside of these bounds) +wake_treatment_strength = uns.remove_particles_strength( minmaxGamma[1]^2, minmaxGamma[2]^2; every_nsteps=1) + +if treat_wake + push!(wake_treatments, wake_treatment_strength) +end + + + + + +# ----------------- 1) VEHICLE DEFINITION -------------------------------------- + +# -------- Generate components +println("Generating geometry...") + +# Generate wing +wing = vlm.simpleWing(b, ar, tr, twist_root, lambda, gamma; + twist_tip=twist_tip, n=n_wing, r=r_wing); + +# Pitch wing to its angle of attack +O = [0.0, 0.0, 0.0] # New position +Oaxis = uns.gt.rotation_matrix2(0, -AOAwing, 0) # New orientation +vlm.setcoordsystem(wing, O, Oaxis) + +# Generate rotors +rotors = vlm.Rotor[] +for ri in 1:nrotors + + # Generate rotor + rotor = uns.generate_rotor(rotor_file; + pitch=pitch, + n=n_rotor, CW=CWs[ri], blade_r=r_rotor, + altReD=[RPM, J, mu/rho], + xfoil=xfoil, + ncrit=ncrit, + data_path=data_path, + read_polar=read_polar, + verbose=true, + verbose_xfoil=false, + plot_disc=false + ); + + # Simulate only one rotor if the wing is not in the simulation + if !add_wing + push!(rotors, rotor) + break + end + + # Determine position along wing LE + y = spanpos[ri]*b/2 + x = abs(y)*tand(lambda) + xpos[ri]*b/ar + z = abs(y)*tand(gamma) + zpos[ri]*b/ar + + # Account for angle of attack of wing + nrm = sqrt(x^2 + z^2) + x = (x==0 ? 1 : sign(x))*nrm*cosd(AOAwing) + z = -(z==0 ? 1 : sign(z))*nrm*sind(AOAwing) + + # Translate rotor to its position along wing + O_r = [x, y, z] # New position + Oaxis_r = uns.gt.rotation_matrix2(0, 0, 0) # New orientation + vlm.setcoordsystem(rotor, O_r, Oaxis_r; user=true) + + push!(rotors, rotor) +end + + +# -------- Generate vehicle +println("Generating vehicle...") + +# System of all FLOWVLM objects +system = vlm.WingSystem() + +if add_wing + vlm.addwing(system, "Wing", wing) +end + +if add_rotors + for (ri, rotor) in enumerate(rotors) + vlm.addwing(system, "Rotor$(ri)", rotor) + end +end + +# System solved through VLM solver +vlm_system = vlm.WingSystem() +add_wing ? vlm.addwing(vlm_system, "Wing", wing) : nothing + +# Systems of rotors +rotor_systems = add_rotors ? (rotors, ) : NTuple{0, Array{vlm.Rotor, 1}}() + +# System that will shed a VPM wake +wake_system = vlm.WingSystem() # System that will shed a VPM wake +add_wing ? vlm.addwing(wake_system, "Wing", wing) : nothing + # NOTE: Do NOT include rotor when using the quasi-steady solver +if VehicleType != uns.QVLMVehicle && add_rotors + for (ri, rotor) in enumerate(rotors) + vlm.addwing(wake_system, "Rotor$(ri)", rotor) + end +end + +# Pitch vehicle to its angle of attack (0 in this case since we have tilted the freestream instead) +O = [0.0, 0.0, 0.0] # New position +Oaxis = uns.gt.rotation_matrix2(0, 0, 0) # New orientation +vlm.setcoordsystem(system, O, Oaxis) + +vehicle = VehicleType( system; + vlm_system=vlm_system, + rotor_systems=rotor_systems, + wake_system=wake_system + ); + + + + + + +# ------------- 2) MANEUVER DEFINITION ----------------------------------------- +# Non-dimensional translational velocity of vehicle over time +Vvehicle(t) = [-1, 0, 0] # <---- Vehicle is traveling in the -x direction + +# Angle of the vehicle over time +anglevehicle(t) = zeros(3) + +# RPM control input over time (RPM over `RPMref`) +RPMcontrol(t) = 1.0 + +angles = () # Angle of each tilting system (none) +RPMs = add_rotors ? (RPMcontrol, ) : () # RPM of each rotor system + +maneuver = uns.KinematicManeuver(angles, RPMs, Vvehicle, anglevehicle) + + + + + + +# ------------- 3) SIMULATION DEFINITION --------------------------------------- + +Vref = 0.0 # Reference velocity to scale maneuver by +RPMref = RPM # Reference RPM to scale maneuver by +Vinit = Vref*Vvehicle(0) # Initial vehicle velocity +Winit = pi/180*(anglevehicle(1e-6) - anglevehicle(0))/(1e-6*ttot) # Initial angular velocity + +simulation = uns.Simulation(vehicle, maneuver, Vref, RPMref, ttot; + Vinit=Vinit, Winit=Winit); + + + + + +# ------------- *) AERODYNAMIC FORCES ------------------------------------------ +# Here we define the different components of aerodynamic of force that we desire +# to capture with the wing using the actuator surface model + +forces = [] + +# Calculate Kutta-Joukowski force +kuttajoukowski = uns.generate_aerodynamicforce_kuttajoukowski(KJforce_type, + sigma_vlm_surf, sigma_rotor_surf, + vlm_vortexsheet, vlm_vortexsheet_overlap, + vlm_vortexsheet_distribution, + vlm_vortexsheet_sigma_tbv; + vehicle=vehicle) +push!(forces, kuttajoukowski) + +# Free-vortex force +if include_freevortices + freevortices = uns.generate_calc_aerodynamicforce_freevortices( + vlm_vortexsheet_maxstaticparticle, + sigma_vlm_surf, + vlm_vortexsheet, + vlm_vortexsheet_overlap, + vlm_vortexsheet_distribution, + vlm_vortexsheet_sigma_tbv; + Ffv=uns.Ffv_direct, + include_TBVs=include_freevortices_TBVs + ) + push!(forces, freevortices) +end + +# Force due to unsteady circulation +if include_unsteadyforce + unsteady(args...; optargs...) = uns.calc_aerodynamicforce_unsteady(args...; + add_to_Ftot=add_unsteadyforce, optargs...) + + push!(forces, unsteady) +end + +# Parasatic-drag force (form drag and skin friction) +if include_parasiticdrag + parasiticdrag = uns.generate_aerodynamicforce_parasiticdrag( + wing_polar_file; + read_path=joinpath(data_path, "airfoils"), + calc_cd_from_cl=calc_cd_from_cl, + add_skinfriction=add_skinfriction, + Mach=speedofsound!=nothing ? magVinf/speedofsound : nothing + ) + + push!(forces, parasiticdrag) +end + + +# Stitch all the forces into one function +function calc_aerodynamicforce_fun(vlm_system, args...; per_unit_span=false, optargs...) + + # Delete any previous force field + fieldname = per_unit_span ? "ftot" : "Ftot" + if fieldname in keys(vlm_system.sol) + pop!(vlm_system.sol, fieldname) + end + + Ftot = nothing + + for (fi, force) in enumerate(forces) + Ftot = force(vlm_system, args...; per_unit_span=per_unit_span, optargs...) + end + + return Ftot +end + + + + + + +# ------------- 4) MONITORS DEFINITIONS ---------------------------------------- + +# Generate wing monitor +L_dir = [-sind(AOA), 0, cosd(AOA)] # Direction of lift +D_dir = [ cosd(AOA), 0, sind(AOA)] # Direction of drag + +monitor_wing = uns.generate_monitor_wing(wing, Vinf, b, ar, + rho, qinf, nsteps; + calc_aerodynamicforce_fun=calc_aerodynamicforce_fun, + include_trailingboundvortex=include_trailingboundvortex, + L_dir=L_dir, + D_dir=D_dir, + save_path=save_path, + run_name=run_name*"-wing", + figname="wing monitor", + ) + +# Generate rotors monitor +monitor_rotors = uns.generate_monitor_rotors(rotors, J, rho, RPM, nsteps; + t_scale=RPM/60, # Scaling factor for time in plots + t_lbl="Revolutions", # Label for time axis + save_path=save_path, + run_name=run_name*"-rotors", + figname="rotors monitor", + ) + +# Generate monitor of flow enstrophy (indicates numerical stability) +monitor_enstrophy = uns.generate_monitor_enstrophy(; + save_path=save_path, + run_name=run_name, + figname="enstrophy monitor" + ) + +# Generate monitor of SFS model coefficient Cd +monitor_Cd = uns.generate_monitor_Cd(; save_path=save_path, + run_name=run_name, + figname="Cd monitor" + ) + + +# Concatenate monitors +all_monitors = [monitor_enstrophy, monitor_Cd] + +add_wing ? push!(all_monitors, monitor_wing) : nothing +add_rotors ? push!(all_monitors, monitor_rotors) : nothing + +monitors = uns.concatenate(all_monitors...) + +# Concatenate user-defined runtime function +extra_runtime_function = uns.concatenate(monitors, wake_treatments...) + + + +# ------------- 5) RUN SIMULATION ---------------------------------------------- +println("Running simulation...") + + +# Run simulation +uns.run_simulation(simulation, nsteps; + + # ----- SIMULATION OPTIONS ------------- + Vinf=Vinf, + rho=rho, mu=mu, sound_spd=speedofsound, + + # ----- SOLVERS OPTIONS ---------------- + vpm_integration=vpm_integration, + vpm_viscous=vpm_viscous, + vpm_SFS=vpm_SFS, + + p_per_step=p_per_step, + max_particles=max_particles, + + sigma_vpm_overwrite=sigma_vpm_overwrite, + sigma_rotor_surf=sigma_rotor_surf, + sigma_vlm_surf=sigma_vlm_surf, + + vlm_rlx=vlm_rlx, + vlm_vortexsheet=vlm_vortexsheet, + vlm_vortexsheet_overlap=vlm_vortexsheet_overlap, + vlm_vortexsheet_distribution=vlm_vortexsheet_distribution, + vlm_vortexsheet_sigma_tbv=vlm_vortexsheet_sigma_tbv, + max_static_particles=vlm_vortexsheet_maxstaticparticle, + + hubtiploss_correction=hubtiploss_correction, + + shed_starting=shed_starting, + shed_unsteady=shed_unsteady, + unsteady_shedcrit=unsteady_shedcrit, + + extra_runtime_function=extra_runtime_function, + + # ----- OUTPUT OPTIONS ------------------ + save_path=save_path, + run_name=run_name, + prompt=prompt, + save_wopwopin=false, # <--- Generates input files for PSU-WOPWOP noise analysis if true + + ); + + + + + + +# ----------------- 6) VISUALIZATION ------------------------------------------- +if paraview + println("Calling Paraview...") + + # Files to open in Paraview + files = joinpath(save_path, run_name*"_pfield...xmf;") + + if add_rotors + for ri in 1:nrotors + for bi in 1:B + global files *= run_name*"_Rotor$(ri)_Blade$(bi)_loft...vtk;" + end + end + end + + if add_wing + files *= run_name*"_Wing_vlm...vtk;" + end + + # Call Paraview + run(`paraview --data=$(files)`) + +end +``` +```@raw html + + Mid-low fidelity run time: 13 minutes a Dell Precision 7760 laptop.
+ Mid-high fidelity run time: 70 minutes a Dell Precision 7760 laptop.
+ High fidelity runtime: ~2 days on a 16-core AMD EPYC 7302 processor. +
+

+``` + +```@raw html +
+

+ Mid-High Fidelity +
+ Pic here +



+ High Fidelity +
+ Pic here +


+
+``` + +The favorable comparison with the experiment at $\alpha=0^\circ$ and +$4^\circ$ confirms that ASM accurately predicts prop-on-wing +interactions up to a moderate angle of attack. At $\alpha=10^\circ$ we suspect +that the wing is mildly stalled in the experiment, leading to a larger discrepancy (further +discussed in [Alvarez' Dissertation](https://scholarsarchive.byu.edu/etd/9589)[^1] +and +[Alvarez & Ning, 2023](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[^2]). + +!!! info "Source file" + Full example available under + [examples/prowim/](https://github.com/byuflowlab/FLOWUnsteady/blob/master/examples/prowim). + + + +[^1]: E. J. Alvarez (2022), "Reformulated Vortex Particle Method and + Meshless Large Eddy Simulation of Multirotor Aircraft," *Doctoral + Dissertation, Brigham Young University*. + [**[VIDEO]**](https://www.nas.nasa.gov/pubs/ams/2022/08-09-22.html) + [**[PDF]**](https://scholarsarchive.byu.edu/etd/9589/) +[^2]: E. J. Alvarez and A. Ning (2023), "Meshless Large-Eddy Simulation of + Propeller–Wing Interactions with Reformulated Vortex Particle Method," + *Journal of Aircraft*. + [**[DOI]**](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[**[PDF]**](https://scholarsarchive.byu.edu/facpub/6902/) + diff --git a/docs/src/generate_examples.jl b/docs/src/generate_examples.jl index eb09ce04..40e9e98e 100644 --- a/docs/src/generate_examples.jl +++ b/docs/src/generate_examples.jl @@ -9,4 +9,5 @@ include("generate_examples_heaving.jl") include("generate_examples_propeller.jl") include("generate_examples_rotorhover.jl") include("generate_examples_blownwing.jl") +include("generate_examples_prowim.jl") include("generate_examples_vahana.jl") diff --git a/docs/src/generate_examples_blownwing.jl b/docs/src/generate_examples_blownwing.jl index 5e14edc4..ea5160f7 100644 --- a/docs/src/generate_examples_blownwing.jl +++ b/docs/src/generate_examples_blownwing.jl @@ -8,7 +8,7 @@ remote_url = "https://edoalvar2.groups.et.byu.net/public/FLOWUnsteady/" open(joinpath(output_path, output_name*"-aero.md"), "w") do fout println(fout, """ - # [Aerodynamic Solution](@id blownwingaero) + # [Wing-on-Prop Interactions](@id blownwingaero) ```@raw html
@@ -20,6 +20,17 @@ open(joinpath(output_path, output_name*"-aero.md"), "w") do fout
``` + In this example we show mount propellers on a swept wing. + The wing is modeled using the actuator line model that represents the wing + as a lifting line. + This wing model is accurate for capturing wing-on-prop interactions. + For instance, the rotor will experience an unsteady blade loading (and + increased tonal noise) caused by the turning of the flow ahead of the wing + leading edge. + However, this simple wing model is not adecuate for capturing + prop-on-wing interactions (see [the next two sections](@ref asm) to + accurately predict prop-on-wing interactions). + """) println(fout, "```julia") @@ -159,13 +170,17 @@ open(joinpath(output_path, output_name*"-asm.md"), "w") do fout The ALM is very accurate for isolated wings and even cases with mild wake interactions. However, for cases with stronger wake interactions (e.g., a wake directly - impinging on the wing surface), we have developed an actuator surface model - (ASM) that introduces the surface vorticity into the LES domain that better - represents the physics. - This is done by spreading the surface vorticity following a pressure-like - distribution, which ends up producing a velocity field at the wing surface - that minimizes the flow that crosses the airfoil centerline, thus better - representing a solid surface: + impinging on the wing surface), the ALM can lead to unphysical results as + the flow tends to cross the airfoil centerline. + To address this, we have developed an actuator surface model + (ASM) to embed the wing surface in the LES domain and better + represent the physics. + + The ASM spreads the surface vorticity following a pressure-like + distribution. + This produces a velocity field at the wing surface that minimizes the mass + flow that crosses the airfoil centerline, thus better representing a solid + surface: ```@raw html
@@ -174,9 +189,11 @@ open(joinpath(output_path, output_name*"-asm.md"), "w") do fout

``` - For an in-depth discussion of the actuator line and surface models - implemented in FLOWUnsteady, see Chapter 6 in - [Alvarez' Dissertation](https://scholarsarchive.byu.edu/etd/9589).[^2] + For an in-depth discussion of the actuator models implemented in + FLOWUnsteady, see Chapter 6 in + [Alvarez' Dissertation](https://scholarsarchive.byu.edu/etd/9589)[^2] + (also published in + [Alvarez & Ning, 2023](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[^3]). [^2]: E. J. Alvarez (2022), "Reformulated Vortex Particle Method and @@ -184,6 +201,10 @@ open(joinpath(output_path, output_name*"-asm.md"), "w") do fout Dissertation, Brigham Young University*. [**[VIDEO]**](https://www.nas.nasa.gov/pubs/ams/2022/08-09-22.html) [**[PDF]**](https://scholarsarchive.byu.edu/etd/9589/) + [^3]: E. J. Alvarez and A. Ning (2023), "Meshless Large-Eddy Simulation of + Propeller–Wing Interactions with Reformulated Vortex Particle Method," + *Journal of Aircraft*. + [**[DOI]**](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[**[PDF]**](https://scholarsarchive.byu.edu/facpub/6902/) In order to activate the actuator surface model, we define the following parameters: @@ -216,7 +237,7 @@ open(joinpath(output_path, output_name*"-asm.md"), "w") do fout add_unsteadyforce = false # Whether to add the unsteady force to Ftot or to simply output it include_parasiticdrag = true # Include parasitic-drag force - add_skinfriction = true # If false, the parasitic drag is purely parasitic, meaning no skin friction + add_skinfriction = true # If false, the parasitic drag is purely form, meaning no skin friction calc_cd_from_cl = false # Whether to calculate cd from cl or effective AOA wing_polar_file = "xf-rae101-il-1000000.csv" # Airfoil polar for parasitic drag ``` @@ -230,7 +251,7 @@ open(joinpath(output_path, output_name*"-asm.md"), "w") do fout forces = [] # Calculate Kutta-Joukowski force - kuttajoukowski = uns.generate_calc_aerodynamicforce_kuttajoukowski(KJforce_type, + kuttajoukowski = uns.generate_aerodynamicforce_kuttajoukowski(KJforce_type, sigma_vlm_surf, sigma_rotor_surf, vlm_vortexsheet, vlm_vortexsheet_overlap, vlm_vortexsheet_distribution, @@ -315,12 +336,10 @@ open(joinpath(output_path, output_name*"-asm.md"), "w") do fout ) ``` - !!! info "ASM and High Fidelity" - ASM uses a very high density of particles at the wing - surface (~100k particles per wing) to accuratelly introduce the solid - boundary into the LES. - This increases the computational cost of the simulation considerably. - Hence, we recommend using ASM only for high-fidelity simulations. + !!! info "ASM Example" + The [next section](@ref prowimaero) shows an example on how to + set up and run a simulation using the actuator surface model. + """) end diff --git a/docs/src/generate_examples_prowim.jl b/docs/src/generate_examples_prowim.jl new file mode 100644 index 00000000..cacd0c80 --- /dev/null +++ b/docs/src/generate_examples_prowim.jl @@ -0,0 +1,122 @@ +# ------------- BLOWN WING EXAMPLE --------------------------------------------- +output_name = "prowim" +example_path = joinpath(uns.examples_path, "prowim") + +remote_url = "https://edoalvar2.groups.et.byu.net/public/FLOWUnsteady/" + +# -------- Aero Solution -------------------------------------------------------- +open(joinpath(output_path, output_name*"-aero.md"), "w") do fout + + println(fout, """ + # [Prop-on-Wing Interactions](@id prowimaero) + + In this example we use the [actuator surface model](@ref asm) (ASM) to + more accurately predict the effects of props blowing on a wing. + This case simulates the PROWIM experiment in + [Leo Veldhuis' dissertation](https://repository.tudelft.nl/islandora/object/uuid%3A8ffbde9c-b483-40de-90e0-97095202fbe3) + (2005), and reproduces the validation study published in + [Alvarez & Ning (2023)](https://arc.aiaa.org/doi/10.2514/1.C037279). + + In this example you can vary the fidelity of the simulation setting the + following parameters: + + | Parameter | Mid-low fidelity | Mid-high fidelity | High fidelity | Description | + | :-------: | :--------------: | :---------------: | :-----------: | :---------- | + | `n_wing` | `50` | `50` | `100` | Number of wing elements per semispan | + | `n_rotor` | `12` | `20` | `50` | Number of blade elements per blade | + | `nsteps_per_rev` | `36` | `36` | `72` | Time steps per revolution | + | `p_per_step` | `2` | `5` | `5` | Particle sheds per time step | + | `shed_starting` | `false` | `false` | `true` | Whether to shed starting vortex | + | `shed_unsteady` | `false` | `false` | `true` | Whether to shed vorticity from unsteady loading | + | `treat_wake` | `true` | `true` | `false` | Treat wake to avoid instabilities | + | `vlm_vortexsheet_overlap` | `2.125/10` | `2.125/10` | `2.125` | Particle overlap in ASM vortex sheet | + | `vpm_integration` | `vpm.euler` | RK3``^\\star`` | RK3``^\\star`` | VPM time integration scheme | + | `vpm_SFS` | None``^\\dag`` | Dynamic``^\\ddag`` | Dynamic``^\\ddag`` | VPM LES subfilter-scale model | + + * ``^\\star``*RK3:* `vpm_integration = vpm.rungekutta3` + * ``^\\dag``*None:* `vpm_SFS = vpm.SFS_none` + * ``^\\ddag``*Dynamic:* `vpm_SFS = vpm.SFS_Cd_twolevel_nobackscatter` + + (Mid-low fidelity settings may be inadequate for capturing prop-on-wing interactions, unless using `p_per_step=5`) + + + """) + + println(fout, "```julia") + + open(joinpath(example_path, "prowim.jl"), "r") do fin + + ignore = false + + for l in eachline(fin) + if contains(l, "6) POSTPROCESSING") + break + end + + # if l=="#=" || contains(l, "# Uncomment this") + if l=="#=" + ignore=true + end + + if !ignore && !contains(l, "save_code=") + println(fout, l) + end + + if l=="=#" || contains(l, "# paraview = false") + ignore=false + end + end + end + + println(fout, "```") + + println(fout, """ + ```@raw html + + Mid-low fidelity run time: 13 minutes a Dell Precision 7760 laptop.
+ Mid-high fidelity run time: 70 minutes a Dell Precision 7760 laptop.
+ High fidelity runtime: ~2 days on a 16-core AMD EPYC 7302 processor. +
+

+ ``` + + ```@raw html +
+

+ Mid-High Fidelity +
+ Pic here +



+ High Fidelity +
+ Pic here +


+
+ ``` + + The favorable comparison with the experiment at \$\\alpha=0^\\circ\$ and + \$4^\\circ\$ confirms that ASM accurately predicts prop-on-wing + interactions up to a moderate angle of attack. At \$\\alpha=10^\\circ\$ we suspect + that the wing is mildly stalled in the experiment, leading to a larger discrepancy (further + discussed in [Alvarez' Dissertation](https://scholarsarchive.byu.edu/etd/9589)[^1] + and + [Alvarez & Ning, 2023](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[^2]). + + !!! info "Source file" + Full example available under + [examples/prowim/](https://github.com/byuflowlab/FLOWUnsteady/blob/master/examples/prowim). + + + + [^1]: E. J. Alvarez (2022), "Reformulated Vortex Particle Method and + Meshless Large Eddy Simulation of Multirotor Aircraft," *Doctoral + Dissertation, Brigham Young University*. + [**[VIDEO]**](https://www.nas.nasa.gov/pubs/ams/2022/08-09-22.html) + [**[PDF]**](https://scholarsarchive.byu.edu/etd/9589/) + [^2]: E. J. Alvarez and A. Ning (2023), "Meshless Large-Eddy Simulation of + Propeller–Wing Interactions with Reformulated Vortex Particle Method," + *Journal of Aircraft*. + [**[DOI]**](https://arc.aiaa.org/doi/abs/10.2514/1.C037279)[**[PDF]**](https://scholarsarchive.byu.edu/facpub/6902/) + """) + +end diff --git a/examples/blownwing/blownwing.jl b/examples/blownwing/blownwing.jl index bdf0046a..3b0fcb45 100644 --- a/examples/blownwing/blownwing.jl +++ b/examples/blownwing/blownwing.jl @@ -206,8 +206,8 @@ for ri in 1:nrotors # Account for angle of attack of wing nrm = sqrt(x^2 + z^2) - x = nrm*cosd(AOAwing) - z = -nrm*sind(AOAwing) + x = (x==0 ? 1 : sign(x))*nrm*cosd(AOAwing) + z = -(z==0 ? 1 : sign(z))*nrm*sind(AOAwing) # Translate rotor to its position along wing O = [x, y, z] # New position diff --git a/examples/prowim/prowim.jl b/examples/prowim/prowim.jl new file mode 100644 index 00000000..2a8f6961 --- /dev/null +++ b/examples/prowim/prowim.jl @@ -0,0 +1,641 @@ +#=############################################################################## +# DESCRIPTION + Validation of prop-on-wing interactions with twin props mounted mid span + blowing on a wing. This case simulates the PROWIM experiment in Leo + Veldhuis' dissertation (2005), “Propeller Wing Aerodynamic Interference.” + + In this simulation we use the actuator surface model for the wing in order + to accurately capture prop-on-wing interactional effects. The rotors still + use the actuator line model. + + The high-fidelity settings replicate the results presented in Alvarez & + Ning (2023), "Meshless Large-Eddy Simulation of Propeller–Wing Interactions + with Reformulated Vortex Particle Method," Sec. IV.B, also available in + Alvarez (2022), "Reformulated Vortex Particle Method and Meshless Large Eddy + Simulation of Multirotor Aircraft," Sec. 8.4. + +# ABOUT + * Author : Eduardo J. Alvarez (edoalvarez.com) + * Email : Edo.AlvarezR@gmail.com + * Created : January 2024 + * Last updated : January 2024 + * License : MIT +=############################################################################### + +#= +Use the following parameters to obtain the desired fidelity + +---- MID-LOW FIDELITY ------ +treat_wake = true # Treat wake to avoid instabilities +n_wing = 50 # Number of wing elements per side +n_rotor = 12 # Number of blade elements per blade +nsteps_per_rev = 36 # Time steps per revolution +p_per_step = 2 # Sheds per time step + +shed_starting = false # Whether to shed starting vortex +shed_unsteady = false # Whether to shed vorticity from unsteady loading +vlm_vortexsheet_overlap = 2.125/10 # Particle overlap in ASM vortex sheet + +vpm_integration = vpm.euler # VPM time integration scheme +vpm_SFS = vpm.SFS_none # VPM LES subfilter-scale model + +---- MID FIDELITY ------ +treat_wake = true +n_wing = 50 +n_rotor = 20 +nsteps_per_rev = 36 +p_per_step = 5 + +shed_starting = false +shed_unsteady = false +vlm_vortexsheet_overlap = 2.125/10 + +vpm_integration = vpm.rungekutta3 +vpm_SFS = vpm.DynamicSFS(vpm.Estr_fmm, vpm.pseudo3level_positive; + alpha=0.999, maxC=1.0, + clippings=[vpm.clipping_backscatter]) + +---- HIGH FIDELITY ----- +treat_wake = false +n_wing = 100 +n_rotor = 50 +nsteps_per_rev = 72 +p_per_step = 5 + +shed_starting = AOA < 8 ? true : false +shed_unsteady = true +vlm_vortexsheet_overlap = 2.125 + +vpm_integration = vpm.rungekutta3 +vpm_SFS = vpm.DynamicSFS(vpm.Estr_fmm, vpm.pseudo3level_positive; + alpha=0.999, maxC=1.0, + clippings=[vpm.clipping_backscatter]) +=# + +import FLOWUnsteady as uns +import FLOWUnsteady: vlm, vpm + +run_name = "prowim" # Name of this simulation +save_path = run_name*"-example2" # Where to save this simulation +prompt = true # Whether to prompt the user +paraview = true # Whether to visualize with Paraview + +add_wing = true # Whether to add wing to simulation +add_rotors = true # Whether to add rotors to simulation + +# ----------------- GEOMETRY PARAMETERS ---------------------------------------- +# Wing geometry +b = 2*0.64 # (m) span length +ar = 5.33 # Aspect ratio b/c_tip +tr = 1.0 # Taper ratio c_tip/c_root +twist_root = 0.0 # (deg) twist at root +twist_tip = 0.0 # (deg) twist at tip +lambda = 0.0 # (deg) sweep +gamma = 0.0 # (deg) dihedral +thickness_w = 0.15 # Thickness t/c of wing airfoil + +# Rotor geometry +rotor_file = "beaver.csv" # Rotor geometry +data_path = uns.def_data_path # Path to rotor database +read_polar = vlm.ap.read_polar2 # What polar reader to use +pitch = 2.5 # (deg) collective pitch of blades +xfoil = false # Whether to run XFOIL +ncrit = 6 # Turbulence criterion for XFOIL + +# Read radius of this rotor and number of blades +R, B = uns.read_rotor(rotor_file; data_path=data_path)[[1,3]] + +# Vehicle assembly +AOAwing = 0.0 # (deg) wing angle of attack +spanpos = [-0.46875, 0.46875] # Semi-span position of each rotor, 2*y/b +xpos = [-0.8417, -0.8417] # x-position of rotors relative to LE, x/c +zpos = [0.0, 0.0] # z-position of rotors relative to LE, z/c +CWs = [false, true] # Rotation direction of each rotor: outboard up +# CWs = [true, false] # Rotation direction of each rotor: inboard up +nrotors = length(spanpos) # Number of rotors + +# Discretization +n_wing = 50 # Number of spanwise elements per side +r_wing = 2.0 # Geometric expansion of elements +# n_rotor = 20 # Number of blade elements per blade +n_rotor = 12 +r_rotor = 1/10 # Geometric expansion of elements + +# Check that we declared all the inputs that we need for each rotor +@assert nrotors==length(spanpos)==length(xpos)==length(zpos)==length(CWs) ""* + "Invalid rotor inputs! Check that spanpos, xpos, zpos, and CWs have the same length" + +# ----------------- SIMULATION PARAMETERS -------------------------------------- +# Freestream +magVinf = 49.5 # (m/s) freestream velocity +AOA = 4.0 # (deg) vehicle angle of attack +rho = 1.225 # (kg/m^3) air density +mu = 1.79e-5 # (kg/ms) air dynamic viscosity +speedofsound = 342.35 # (m/s) speed of sound +qinf = 0.5*rho*magVinf^2 # (Pa) reference static pressure +Vinf(X, t) = magVinf*[cosd(AOA), 0, sind(AOA)] # Freestream function + +# Rotor operation +J = 0.85 # Advance ratio Vinf/(nD) +RPM = 60*magVinf/(J*2*R) # RPM + +# Reference non-dimensional parameters +Rec = rho * magVinf * (b/ar) / mu # Chord-based wing Reynolds number +ReD = 2*pi*RPM/60*R * rho/mu * 2*R # Diameter-based rotor Reynolds number +Mtip = 2*pi*RPM/60 * R / speedofsound # Tip Mach number + +println(""" + Vinf: $(round(magVinf, digits=1)) m/s + RPM: $(RPM) + Mtip: $(round(Mtip, digits=3)) + ReD: $(round(ReD, digits=0)) + Rec: $(round(Rec, digits=0)) +""") + + +# NOTE: Modify the variable `AOA` in order to change the angle of attack. +# `AOAwing` will only change the angle of attack of the wing (while +# leaving the propellers unaffected), while `AOA` changes the angle of +# attack of the freestream (affecting both wing and props). + +# ----------------- SOLVER PARAMETERS ------------------------------------------ + +# Aerodynamic solver +VehicleType = uns.UVLMVehicle # Unsteady solver +# VehicleType = uns.QVLMVehicle # Quasi-steady solver +const_solution = VehicleType==uns.QVLMVehicle # Whether to assume that the + # solution is constant or not +# Time parameters +nrevs = 8 # Number of revolutions in simulation +nsteps_per_rev = 36 # Time steps per revolution +nsteps = const_solution ? 2 : nrevs*nsteps_per_rev # Number of time steps +ttot = nsteps/nsteps_per_rev / (RPM/60) # (s) total simulation time + +# VPM particle shedding +# p_per_step = 5 # Sheds per time step +p_per_step = 2 +shed_starting = false # Whether to shed starting vortex (NOTE: starting vortex might make simulation unstable with AOA>8) +shed_unsteady = false # Whether to shed vorticity from unsteady loading +unsteady_shedcrit = 0.001 # Shed unsteady loading whenever circulation + # fluctuates by more than this ratio +treat_wake = true # Treat wake to avoid instabilities +max_particles = 1 # Maximum number of particles +max_particles += add_rotors * (nrotors*((2*n_rotor+1)*B)*nsteps*p_per_step) +max_particles += add_wing * (nsteps+1)*(2*n_wing*(p_per_step+1) + p_per_step) + +# Regularization +sigma_vlm_surf = b/200 # VLM-on-VPM smoothing radius (σLBV of wing) +sigma_rotor_surf= R/80 # Rotor-on-VPM smoothing radius (σ of rotor) +lambda_vpm = 2.125 # VPM core overlap + # VPM smoothing radius (σ of wakes) +sigma_vpm_overwrite = lambda_vpm * 2*pi*R/(nsteps_per_rev*p_per_step) + +# Rotor solver +vlm_rlx = 0.3 # VLM relaxation <-- this also applied to rotors +hubtiploss_correction = ( (0.75, 10, 0.5, 0.05), (1, 1, 1, 1.0) ) # Hub/tip correction +# VPM solver +# vpm_integration = vpm.rungekutta3 # VPM temporal integration scheme +vpm_integration = vpm.euler + +vpm_viscous = vpm.Inviscid() # VPM viscous diffusion scheme + # Uncomment this to make it viscous +# vpm_viscous = vpm.CoreSpreading(-1, -1, vpm.zeta_fmm; beta=100.0, itmax=20, tol=1e-1) + +vpm_SFS = vpm.SFS_none # VPM LES subfilter-scale model +# vpm_SFS = vpm.DynamicSFS(vpm.Estr_fmm, vpm.pseudo3level_positive; + # alpha=0.999, maxC=1.0, + # clippings=[vpm.clipping_backscatter]) + +# NOTE: By default we make this simulation inviscid since at such high Reynolds +# number the viscous effects in the wake are actually negligible. +# Notice that while viscous diffusion is negligible, turbulent diffusion +# is important and non-negigible, so we have activated the subfilter-scale +# (SFS) model. + +if VehicleType == uns.QVLMVehicle + # Mute warnings regarding potential colinear vortex filaments. This is + # needed since the quasi-steady solver will probe induced velocities at the + # lifting line of the blade + uns.vlm.VLMSolver._mute_warning(true) +end + +println(""" + Resolving wake for $(round(ttot*magVinf/b, digits=1)) span distances +""") + + + + + +# ----------------- ACTUATOR SURFACE MODEL PARAMETERS (WING) ------------------- + +# ---------- Vortex sheet parameters --------------- +vlm_vortexsheet = true # Spread wing circulation as a vortex sheet (activates the ASM) +vlm_vortexsheet_overlap = 2.125/10 # Particle overlap in vortex sheet +vlm_vortexsheet_distribution = uns.g_pressure # Distribution of the vortex sheet + +vlm_vortexsheet_sigma_tbv = thickness_w*(b/ar) / 128 # Smoothing radius of trailing bound vorticity, σTBV for VLM-on-VPM + +vlm_vortexsheet_maxstaticparticle = 10^6 # Particles to preallocate for vortex sheet + +if add_wing && vlm_vortexsheet + max_particles += vlm_vortexsheet_maxstaticparticle +end + + +# ---------- Force calculation parameters ---------- +KJforce_type = "regular" # KJ force evaluated at middle of bound vortices +# KJforce_type = "averaged" # KJ force evaluated at average vortex sheet +# KJforce_type = "weighted" # KJ force evaluated at strength-weighted vortex sheet + +include_trailingboundvortex = false # Include trailing bound vortices in force calculations + +include_freevortices = false # Include free vortices in force calculation +include_freevortices_TBVs = false # Include trailing bound vortex in free-vortex force + +include_unsteadyforce = true # Include unsteady force +add_unsteadyforce = false # Whether to add the unsteady force to Ftot or to simply output it + +include_parasiticdrag = true # Include parasitic-drag force +add_skinfriction = true # If false, the parasitic drag is purely form, meaning no skin friction +calc_cd_from_cl = true # Whether to calculate cd from cl or effective AOA +# calc_cd_from_cl = false + +# NOTE: We use a polar at a low Reynolds number (100k as opposed to 600k from +# the experiment) as this particular polar better resembles the drag of +# the tripped airfoil used in the experiment +wing_polar_file = "xf-n64015a-il-100000-n5.csv" # Airfoil polar for parasitic drag (from airfoiltools.com) + + +if include_freevortices && Threads.nthreads()==1 + @warn("Free-vortex force calculation requested, but Julia was initiated"* + " with only one CPU thread. This will be extremely slow!"* + " Initate Julia with `-t num` where num is the number of cores"* + " availabe to speed up the computation.") +end + + + + + +# ----------------- WAKE TREATMENT --------------------------------------------- + +wake_treatments = [] + +# Remove particles by particle strength: remove particles neglibly weak, remove +# particles potentially blown up +rmv_strngth = 2.0 * magVinf*(b/ar)/2 * magVinf*ttot/nsteps/p_per_step # Reference strength (maxCL=2.0) +minmaxGamma = rmv_strngth*[0.0001, 10.0] # Strength bounds (removes particles outside of these bounds) +wake_treatment_strength = uns.remove_particles_strength( minmaxGamma[1]^2, minmaxGamma[2]^2; every_nsteps=1) + +if treat_wake + push!(wake_treatments, wake_treatment_strength) +end + + + + + +# ----------------- 1) VEHICLE DEFINITION -------------------------------------- + +# -------- Generate components +println("Generating geometry...") + +# Generate wing +wing = vlm.simpleWing(b, ar, tr, twist_root, lambda, gamma; + twist_tip=twist_tip, n=n_wing, r=r_wing); + +# Pitch wing to its angle of attack +O = [0.0, 0.0, 0.0] # New position +Oaxis = uns.gt.rotation_matrix2(0, -AOAwing, 0) # New orientation +vlm.setcoordsystem(wing, O, Oaxis) + +# Generate rotors +rotors = vlm.Rotor[] +for ri in 1:nrotors + + # Generate rotor + rotor = uns.generate_rotor(rotor_file; + pitch=pitch, + n=n_rotor, CW=CWs[ri], blade_r=r_rotor, + altReD=[RPM, J, mu/rho], + xfoil=xfoil, + ncrit=ncrit, + data_path=data_path, + read_polar=read_polar, + verbose=true, + verbose_xfoil=false, + plot_disc=false + ); + + # Simulate only one rotor if the wing is not in the simulation + if !add_wing + push!(rotors, rotor) + break + end + + # Determine position along wing LE + y = spanpos[ri]*b/2 + x = abs(y)*tand(lambda) + xpos[ri]*b/ar + z = abs(y)*tand(gamma) + zpos[ri]*b/ar + + # Account for angle of attack of wing + nrm = sqrt(x^2 + z^2) + x = (x==0 ? 1 : sign(x))*nrm*cosd(AOAwing) + z = -(z==0 ? 1 : sign(z))*nrm*sind(AOAwing) + + # Translate rotor to its position along wing + O_r = [x, y, z] # New position + Oaxis_r = uns.gt.rotation_matrix2(0, 0, 0) # New orientation + vlm.setcoordsystem(rotor, O_r, Oaxis_r; user=true) + + push!(rotors, rotor) +end + + +# -------- Generate vehicle +println("Generating vehicle...") + +# System of all FLOWVLM objects +system = vlm.WingSystem() + +if add_wing + vlm.addwing(system, "Wing", wing) +end + +if add_rotors + for (ri, rotor) in enumerate(rotors) + vlm.addwing(system, "Rotor$(ri)", rotor) + end +end + +# System solved through VLM solver +vlm_system = vlm.WingSystem() +add_wing ? vlm.addwing(vlm_system, "Wing", wing) : nothing + +# Systems of rotors +rotor_systems = add_rotors ? (rotors, ) : NTuple{0, Array{vlm.Rotor, 1}}() + +# System that will shed a VPM wake +wake_system = vlm.WingSystem() # System that will shed a VPM wake +add_wing ? vlm.addwing(wake_system, "Wing", wing) : nothing + # NOTE: Do NOT include rotor when using the quasi-steady solver +if VehicleType != uns.QVLMVehicle && add_rotors + for (ri, rotor) in enumerate(rotors) + vlm.addwing(wake_system, "Rotor$(ri)", rotor) + end +end + +# Pitch vehicle to its angle of attack (0 in this case since we have tilted the freestream instead) +O = [0.0, 0.0, 0.0] # New position +Oaxis = uns.gt.rotation_matrix2(0, 0, 0) # New orientation +vlm.setcoordsystem(system, O, Oaxis) + +vehicle = VehicleType( system; + vlm_system=vlm_system, + rotor_systems=rotor_systems, + wake_system=wake_system + ); + + + + + + +# ------------- 2) MANEUVER DEFINITION ----------------------------------------- +# Non-dimensional translational velocity of vehicle over time +Vvehicle(t) = [-1, 0, 0] # <---- Vehicle is traveling in the -x direction + +# Angle of the vehicle over time +anglevehicle(t) = zeros(3) + +# RPM control input over time (RPM over `RPMref`) +RPMcontrol(t) = 1.0 + +angles = () # Angle of each tilting system (none) +RPMs = add_rotors ? (RPMcontrol, ) : () # RPM of each rotor system + +maneuver = uns.KinematicManeuver(angles, RPMs, Vvehicle, anglevehicle) + + + + + + +# ------------- 3) SIMULATION DEFINITION --------------------------------------- + +Vref = 0.0 # Reference velocity to scale maneuver by +RPMref = RPM # Reference RPM to scale maneuver by +Vinit = Vref*Vvehicle(0) # Initial vehicle velocity +Winit = pi/180*(anglevehicle(1e-6) - anglevehicle(0))/(1e-6*ttot) # Initial angular velocity + +simulation = uns.Simulation(vehicle, maneuver, Vref, RPMref, ttot; + Vinit=Vinit, Winit=Winit); + + + + + +# ------------- *) AERODYNAMIC FORCES ------------------------------------------ +# Here we define the different components of aerodynamic of force that we desire +# to capture with the wing using the actuator surface model + +forces = [] + +# Calculate Kutta-Joukowski force +kuttajoukowski = uns.generate_aerodynamicforce_kuttajoukowski(KJforce_type, + sigma_vlm_surf, sigma_rotor_surf, + vlm_vortexsheet, vlm_vortexsheet_overlap, + vlm_vortexsheet_distribution, + vlm_vortexsheet_sigma_tbv; + vehicle=vehicle) +push!(forces, kuttajoukowski) + +# Free-vortex force +if include_freevortices + freevortices = uns.generate_calc_aerodynamicforce_freevortices( + vlm_vortexsheet_maxstaticparticle, + sigma_vlm_surf, + vlm_vortexsheet, + vlm_vortexsheet_overlap, + vlm_vortexsheet_distribution, + vlm_vortexsheet_sigma_tbv; + Ffv=uns.Ffv_direct, + include_TBVs=include_freevortices_TBVs + ) + push!(forces, freevortices) +end + +# Force due to unsteady circulation +if include_unsteadyforce + unsteady(args...; optargs...) = uns.calc_aerodynamicforce_unsteady(args...; + add_to_Ftot=add_unsteadyforce, optargs...) + + push!(forces, unsteady) +end + +# Parasatic-drag force (form drag and skin friction) +if include_parasiticdrag + parasiticdrag = uns.generate_aerodynamicforce_parasiticdrag( + wing_polar_file; + read_path=joinpath(data_path, "airfoils"), + calc_cd_from_cl=calc_cd_from_cl, + add_skinfriction=add_skinfriction, + Mach=speedofsound!=nothing ? magVinf/speedofsound : nothing + ) + + push!(forces, parasiticdrag) +end + + +# Stitch all the forces into one function +function calc_aerodynamicforce_fun(vlm_system, args...; per_unit_span=false, optargs...) + + # Delete any previous force field + fieldname = per_unit_span ? "ftot" : "Ftot" + if fieldname in keys(vlm_system.sol) + pop!(vlm_system.sol, fieldname) + end + + Ftot = nothing + + for (fi, force) in enumerate(forces) + Ftot = force(vlm_system, args...; per_unit_span=per_unit_span, optargs...) + end + + return Ftot +end + + + + + + +# ------------- 4) MONITORS DEFINITIONS ---------------------------------------- + +# Generate wing monitor +L_dir = [-sind(AOA), 0, cosd(AOA)] # Direction of lift +D_dir = [ cosd(AOA), 0, sind(AOA)] # Direction of drag + +monitor_wing = uns.generate_monitor_wing(wing, Vinf, b, ar, + rho, qinf, nsteps; + calc_aerodynamicforce_fun=calc_aerodynamicforce_fun, + include_trailingboundvortex=include_trailingboundvortex, + L_dir=L_dir, + D_dir=D_dir, + save_path=save_path, + run_name=run_name*"-wing", + figname="wing monitor", + ) + +# Generate rotors monitor +monitor_rotors = uns.generate_monitor_rotors(rotors, J, rho, RPM, nsteps; + t_scale=RPM/60, # Scaling factor for time in plots + t_lbl="Revolutions", # Label for time axis + save_path=save_path, + run_name=run_name*"-rotors", + figname="rotors monitor", + ) + +# Generate monitor of flow enstrophy (indicates numerical stability) +monitor_enstrophy = uns.generate_monitor_enstrophy(; + save_path=save_path, + run_name=run_name, + figname="enstrophy monitor" + ) + +# Generate monitor of SFS model coefficient Cd +monitor_Cd = uns.generate_monitor_Cd(; save_path=save_path, + run_name=run_name, + figname="Cd monitor" + ) + + +# Concatenate monitors +all_monitors = [monitor_enstrophy, monitor_Cd] + +add_wing ? push!(all_monitors, monitor_wing) : nothing +add_rotors ? push!(all_monitors, monitor_rotors) : nothing + +monitors = uns.concatenate(all_monitors...) + +# Concatenate user-defined runtime function +extra_runtime_function = uns.concatenate(monitors, wake_treatments...) + + + +# ------------- 5) RUN SIMULATION ---------------------------------------------- +println("Running simulation...") + + +# Run simulation +uns.run_simulation(simulation, nsteps; + + # ----- SIMULATION OPTIONS ------------- + Vinf=Vinf, + rho=rho, mu=mu, sound_spd=speedofsound, + + # ----- SOLVERS OPTIONS ---------------- + vpm_integration=vpm_integration, + vpm_viscous=vpm_viscous, + vpm_SFS=vpm_SFS, + + p_per_step=p_per_step, + max_particles=max_particles, + + sigma_vpm_overwrite=sigma_vpm_overwrite, + sigma_rotor_surf=sigma_rotor_surf, + sigma_vlm_surf=sigma_vlm_surf, + + vlm_rlx=vlm_rlx, + vlm_vortexsheet=vlm_vortexsheet, + vlm_vortexsheet_overlap=vlm_vortexsheet_overlap, + vlm_vortexsheet_distribution=vlm_vortexsheet_distribution, + vlm_vortexsheet_sigma_tbv=vlm_vortexsheet_sigma_tbv, + max_static_particles=vlm_vortexsheet_maxstaticparticle, + + hubtiploss_correction=hubtiploss_correction, + + shed_starting=shed_starting, + shed_unsteady=shed_unsteady, + unsteady_shedcrit=unsteady_shedcrit, + + extra_runtime_function=extra_runtime_function, + + # ----- OUTPUT OPTIONS ------------------ + save_path=save_path, + run_name=run_name, + prompt=prompt, + save_wopwopin=false, # <--- Generates input files for PSU-WOPWOP noise analysis if true + save_code= !contains(@__FILE__, "REPL") ? (@__FILE__) : "" + + ); + + + + + + +# ----------------- 6) VISUALIZATION ------------------------------------------- +if paraview + println("Calling Paraview...") + + # Files to open in Paraview + files = joinpath(save_path, run_name*"_pfield...xmf;") + + if add_rotors + for ri in 1:nrotors + for bi in 1:B + global files *= run_name*"_Rotor$(ri)_Blade$(bi)_loft...vtk;" + end + end + end + + if add_wing + files *= run_name*"_Wing_vlm...vtk;" + end + + # Call Paraview + run(`paraview --data=$(files)`) + +end