#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Program flutter_SDPM_NACARML55E11 calculates the flutter boundary of the
wing described in NACA RM L55E11, using the Source and Doublet Panel Method.

This code is part of the SDPMflut Python distribution.
Copyright (C) 2024 Grigorios Dimitriadis

This program is free software: you can redistribute it and/or modify it 
under the terms of the GNU General Public License as published by the 
Free Software Foundation, either version 3 of the License, or (at your 
option) any later version.
 
This program is distributed in the hope that it will be useful, but 
WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 
Public License for more details.

You should have received a copy of the GNU General Public License along 
with this program. If not, see <https://www.gnu.org/licenses/>.
"""

# Input installation directory
install_dir=r"C:\Users\pasti\Documents\Python\SDPMflut_v0.8\Common"
# install_dir=r"/Users/Username/Documents/Python/SDPMflut_v0.8/Common/" # Linux/Mac OS example
# install_dir=r"C:\Users\Username\Documents\Python\SDPMflut\Common"  # Windows example
# Import libraries and packages
import numpy as np
import matplotlib.pyplot as plt
import sys
sys.path.append(install_dir)
from SDPMgeometry import SDPMgeometry_trap_fun
import flutsol
import FEmodes
import SDPMcalcs
# Acquire SDPMflut trap and body data types
tp_trap, tp_body, _=SDPMcalcs.SDPMdtypes()

# Run data from wind tunnel flutter tests; there is a single subsonic
# flutter point. Source:
# Experimental flutter results for cantilever-wing models. W.J. Tuovila and 
# J.L. McCarty. NACA RM L55E11. June 14, 1955.
# Free stream Mach number
Machdata=np.array([0.45])
# Free stream air density (kg/m^3)
rhodata=515.37882*np.array([0.0023])
# Mass ratio Mh./(pi*rhodata*b*c0^2/4)
mubardata=np.array([52.0])
# Flutter speed (m/s)
Uflutdata=np.array([495.0])*0.3048
# Nondimensional flutter speed
Ustardata=np.array([0.6030]) 
# Flutter frequency (rad/s)
freqflutdata=2.0*np.pi*np.array([120.0])
# Wind-off natural frequencies
wndata=2.0*np.pi*np.array([36.0, 210.0, 242.0])
# Frequency of first torsion 
omega_alpha=wndata[1]
# Calculate flutter ratio
freqflutratio=freqflutdata/omega_alpha
# Total number of runs
nruns=Machdata.size;
                    
# Select reduced frequency values
kvec=np.array([0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.7, 1.0])

# Select airspeed range in m/s
Uv=np.linspace(100,170,num=101)

# Choose order of pressure coefficient equation
cp_order=2 # 1 for linear and 2 for second order

# Set values of mean (or steady) angles of attack and sideslip
alpha0=np.array([0.0*np.pi/180.0])  # Angle of attack in rad
beta0=0.0*np.pi/180.0  # Angle of sideslip in rad

# Set number of bodies
nbody=1
# Initialize body struct array
body=np.zeros(nbody,dtype=tp_body)

# Input first body
ibody=0         # Index of body
name='wing'     # Name of body
# Choose numbers of panels for this wing and its wake
nhalf=20        # Number of spanwise panels per half-wing.
mhalf=20        # Number of chordwise panels on one side of wing (upper or lower).
nchords=10      # Set length of wake in chord lengths
Dxw=1   # Chordwise wake spacing parameter. Dxw=0: No wake, Dxw=-1: One 
        # chordwise panel, Dxw>0: The chordwise wake spacing will be 
        # Dxw  times the mean chordwise spacing of the corresponding 
        # wing.
# Set number of trapezoidal sections for this wing
ntrap=1
# Initialize trapezoidal section struct array
trap=np.zeros(ntrap,dtype=tp_trap)

# Input wing geometry
LamdaLE=15.0*np.pi/180.0        # Sweep angle at the leading edge
c0=0.0254*2.0/np.cos(LamdaLE)   # Root chord in m
bhalf=5.35/2.0*c0               # Span in m of half-wing
lamda=1.0                       # Taper ratio
roottwist=0.0*np.pi/180.0       # Twist angle at the root in rad
tiptwist=0.0*np.pi/180.0        # Twist angle at the root in rad
twistcent=0.0                   # Chordwise axis around which twist is defined
dihedral=0.0*np.pi/180.0        # Dihedral angle in rad
# Chordwise distance of root leading edge from previous
# trapezoidal section's tip leading edge
xledist=0.0  
# Set airfoil name (must be the filename of a function in the Common directory)
airfoil='flatplate_wedge'
# Set airfoil parameters
tc=0.01                     # Half thickness-to chord-ratio 
wedge_length=0.25/(0.0410/2.0)    # Length in half thicknesses of leading and trailing edge wedges
# Assemble airfoil parameter values
airfoil_params=np.array([tc, wedge_length])
# Reference areas and lengths for all bodies in the flow
Sref=c0*(1+lamda)*bhalf  # Reference surface area
bref=2*bhalf    # Reference span length
cref=c0         # The reduced frequency is based on c0
# Reference axis location
xf0=c0/4  # Quarter chord of mean aerodynamic chord
yf0=0;
zf0=0;

# Arrange all data into trapezoidal sections
trap[0]=np.array([(c0,xledist,bhalf,lamda,LamdaLE,roottwist,tiptwist,twistcent,dihedral,airfoil,airfoil_params,airfoil,airfoil_params)],dtype=tp_trap)

# Calculate panel aspect ratio
panelAR=(c0/mhalf)/(bhalf/nhalf)
if panelAR < 0.1:
    sys.exit('Panel aspect ratio too low. Increase n or decrease m.')
    
# Minimum number of spanwise panels per trapezoidal section
nmin=3
# Chordwise panel distribution: 1 constant, 2 denser at the leading edge
linchord=0
# Spanwise panel distribution: 1 constant, 2 denser at the wing tip(s)
linspan=0
# Define root leading edge
lexyz=np.array([0, 0, 0])
# Define roll, pitch and yaw angles
rollpitchyaw=np.array([0, 0, 0])*np.pi/180;
# Define roll, pitch and yaw centre (x,y,z position of rotation centre)
rollpitchyaw_cent=np.array([0, 0, 0]);
# Input body description
mirroredwing=2 # If mirroredwing=-1: a left half-wing will be created
                # If mirroredwing=1: a right half-wing will be created
                # If mirroredwing=2: two mirrored half-wings will be created.
# dir_tau is the direction in which the unit tangent vector  for this wing
# (tauxx, tauxy, tauxz) has a zero component
dir_tau=2
# Calculate vertices of wing panels
body=SDPMgeometry_trap_fun(body,ibody,mhalf,nhalf,nchords,Dxw,mirroredwing,linchord,linspan,trap,name,dir_tau,rollpitchyaw,rollpitchyaw_cent,lexyz,nmin,cref)

# File name of Matlab mat file that contains the structural model
fname='Modes_Subsonic_NACARML55E11.mat' 
# Choose number of modes to include in the flutter calculation
nmodes=4    # Cannot exceed number of modes in FE model
zeta0=0.0*np.ones(nmodes) # Structural damping ratios
# Parameter to determine if the structural model concerns a half wing or a
# full wing.
halfwing=1 # halfwing=1: half-wing. halfwing=0: full wing

# Plot all bodies
fig, axx = plt.subplots(subplot_kw={"projection": "3d"})
for i in range (0,len(body)):
    axx.plot_surface(body['Xp0'][i], body['Yp0'][i], body['Zp0'][i])
# End for
axx.set_proj_type('ortho')  # FOV = 0 deg
axx.axis('equal')
axx.set_xlabel("$x$", labelpad=10)
axx.set_ylabel("$y$", labelpad=10)
axx.set_zlabel("$z$", labelpad=-2)
axx.view_init(26, -120)
plt.show()

# Assemble the indices of the body panels, spanwise body panels, wake
# panels etc. for all bodies.
allbodies=SDPMcalcs.allbodyindex(body)

# Acquire structural matrices and mode shapes
A, C, E, wn, xxplot, yyplot, zzplot, modeshapesx, modeshapesy, modeshapesz, \
    modeshapesRx, modeshapesRy, modeshapesRz=FEmodes.FE_matrices(fname,zeta0,nmodes)

# Interpolate mode shapes onto panel control points
body=FEmodes.SDPMmodeinterp(xxplot,yyplot,modeshapesx,modeshapesy,modeshapesz,modeshapesRx,modeshapesRy,modeshapesRz,body)

# Assemble mode shapes for all bodies into global matrices
allbodies=SDPMcalcs.modeshape_assemble(body,allbodies,nmodes)
                
# Initialize results arrays for all runs
Ustarvec=np.zeros((nruns))    # Flutter speed index
Uflutvec=np.zeros((nruns))    # Flutter speed in m/s
dynpressvec=np.zeros((nruns)) # Flutter dynamic pressure in Pa
freqflutvec=np.zeros((nruns)) # Flutter frequency in rad/s
kflutvec=np.zeros((nruns))    # Reduced flutter frequency

print('Calculating flutter solutions for all experimental test cases')
for irun in range (0,nruns):
    print('')
    print('Simulating run '+str(irun+1))
    
    # Set Mach number of current run
    Mach=Machdata[irun]
    # Set mass ratio of current run
    mubar=mubardata[irun]
    # Set density of current run
    rho=rhodata[irun]
    # Calculate subsonic compressibility factor
    beta=np.sqrt(1-Mach**2) 
    
    # Calculate steady aerodynamic pressures and loads
    body,allbodies,Aphi,Bphi,Cphi,barUinf,barVinf,barWinf,Cx0,Cy0,Cz0,Cl0,Cm0,Cn0=SDPMcalcs.steadysolve(body,allbodies,cp_order,Mach,beta,alpha0,beta0,xf0,yf0,zf0,Sref,cref,bref,install_dir)
    
    Uflut,freqflut,kflut,dynpressflut,omega,zeta=flutsol.flutsolve_flex(body,allbodies,kvec,Uv,nmodes,Aphi,Bphi,Cphi,barUinf,barVinf,barWinf,c0,Mach,beta,cp_order,A,C,E,rho,wn,halfwing,install_dir)
    
    # Print out flutter solution
    if Uflut != 0: # If there is a flutter point
        # Store flutter data for this run
        Uflutvec[irun]=Uflut
        # Calculate flutter speed index
        Ustarvec[irun]=Uflut/(c0/2.0*omega_alpha*np.sqrt(mubar))
        dynpressvec[irun]=dynpressflut
        freqflutvec[irun]=freqflut
        kflutvec[irun]=kflut
        # Compare to experimental data
        print('Flutter speed (m/s)')
        print('SDPM      Exp.')
        print(np.array([Uflut,Uflutdata[irun]]))
        print('Flutter frequency (rad/s)')
        print('SDPM      Exp.')
        print(np.array([freqflut,freqflutdata[irun]]))
    else:
        print('Could not find a flutter point for run '+str(irun))
    # End if
# End loop for irun

if Uflutvec.size == 1:
    # Plot natural frequency variation with airspeed
    fig, axx = plt.subplots()
    for i in range (0,nmodes):
        axx.plot(Uv, omega[i,:],'b-')
    # End for
    axx.plot(Uflutvec, freqflutvec,'ro', label = "SDPM")
    axx.plot(Uflutdata, freqflutdata,'kx', label = "Exp.")
    axx.set_xlabel("Q_{\\infty}$")
    axx.set_ylabel("$\\omega_n$ (rad/s)")
    axx.grid()
    axx.legend(loc="upper left")

    # Plot damping ratio variatio with
    fig, axx = plt.subplots()
    for i in range (0,nmodes):
        axx.plot(Uv, zeta[i,:],'b-')
    # End for
    axx.plot(Uflutvec, 0.0,'ro', label = "SDPM")
    axx.plot(Uflutdata, 0.0,'kx', label = "Exp.")
    axx.set_xlabel("Q_{\\infty}$")
    axx.set_ylabel("$\\zeta$")
    axx.grid()
    axx.legend(loc="upper left")
else:
    # Plot flutter airspeed
    fig, axx = plt.subplots()
    axx.plot(Machdata, Uflutvec, label = "SDPM")
    axx.plot(Machdata, Uflutdata, "o",label = "Exp.",)
    axx.set_xlabel("$M_{\\infty}$")
    axx.set_ylabel("$U_F$ (m/s)")
    axx.grid()
    axx.legend(loc="upper left")
    
    # Plot flutter frequency
    fig, axx = plt.subplots()
    axx.plot(Machdata, freqflutvec, label = "SDPM")
    axx.plot(Machdata, freqflutdata, "o",label = "Exp.",)
    axx.set_xlabel("$M_{\\infty}$")
    axx.set_ylabel("$\omega_F$ (rad/s)")
    axx.grid()
    axx.legend(loc="upper right")
    
    # Plot flutter speed index
    fig, axx = plt.subplots()
    axx.plot(Machdata, Ustarvec, label = "SDPM")
    axx.plot(Machdata, Ustardata, "o",label = "Exp.",)
    axx.set_xlabel("$M_{\\infty}$")
    axx.set_ylabel("$U^*_F$")
    axx.grid()
    axx.legend(loc="upper right")
    
    # Plot flutter frequency ratio
    fig, axx = plt.subplots()
    axx.plot(Machdata, freqflutvec/omega_alpha, label = "SDPM")
    axx.plot(Machdata, freqflutratio, "o",label = "Exp.",)
    axx.set_xlabel("$M_{\\infty}$")
    axx.set_ylabel("$\\omega_F/\\omega_{a}$")
    axx.grid()
    axx.legend(loc="upper right")
# End if


