├── airfoils ├── coord_flat_plate_10.dat ├── coord_parabolic_10.dat ├── coord_parabolic_20.dat ├── coord_parabolic_50.dat ├── coord_flat_plate_100.dat └── coord_parabolic_100.dat ├── coord_parabolic_front_20.dat ├── coord_parabolic_rear_30.dat ├── main.py ├── utils.py └── README.md /airfoils/coord_flat_plate_10.dat: -------------------------------------------------------------------------------- 1 | 0.000 0.000 2 | 0.100 0.000 3 | 0.200 0.000 4 | 0.300 0.000 5 | 0.400 0.000 6 | 0.500 0.000 7 | 0.600 0.000 8 | 0.700 0.000 9 | 0.800 0.000 10 | 0.900 0.000 11 | 1.000 0.000 12 | -------------------------------------------------------------------------------- /airfoils/coord_parabolic_10.dat: -------------------------------------------------------------------------------- 1 | 0.000 0.000 2 | 0.100 0.036 3 | 0.200 0.064 4 | 0.300 0.084 5 | 0.400 0.096 6 | 0.500 0.100 7 | 0.600 0.096 8 | 0.700 0.084 9 | 0.800 0.064 10 | 0.900 0.036 11 | 1.000 0.000 12 | -------------------------------------------------------------------------------- /coord_parabolic_front_20.dat: -------------------------------------------------------------------------------- 1 | 0.000 0.000 2 | 0.050 0.019 3 | 0.100 0.036 4 | 0.150 0.051 5 | 0.200 0.064 6 | 0.250 0.075 7 | 0.300 0.084 8 | 0.350 0.091 9 | 0.400 0.096 10 | 0.450 0.099 11 | 0.500 0.100 12 | 0.550 0.099 13 | 0.600 0.096 14 | 0.650 0.091 15 | 0.700 0.084 16 | 0.750 0.075 17 | 0.800 0.064 18 | 0.850 0.051 19 | 0.900 0.036 20 | 0.950 0.019 21 | 1.000 0.000 22 | -------------------------------------------------------------------------------- /airfoils/coord_parabolic_20.dat: -------------------------------------------------------------------------------- 1 | 0.000 0.000 2 | 0.050 0.019 3 | 0.100 0.036 4 | 0.150 0.051 5 | 0.200 0.064 6 | 0.250 0.075 7 | 0.300 0.084 8 | 0.350 0.091 9 | 0.400 0.096 10 | 0.450 0.099 11 | 0.500 0.100 12 | 0.550 0.099 13 | 0.600 0.096 14 | 0.650 0.091 15 | 0.700 0.084 16 | 0.750 0.075 17 | 0.800 0.064 18 | 0.850 0.051 19 | 0.900 0.036 20 | 0.950 0.019 21 | 1.000 0.000 22 | -------------------------------------------------------------------------------- /coord_parabolic_rear_30.dat: -------------------------------------------------------------------------------- 1 | 2.000 0.000 2 | 2.025 0.008 3 | 2.050 0.015 4 | 2.075 0.022 5 | 2.100 0.028 6 | 2.125 0.033 7 | 2.150 0.038 8 | 2.175 0.043 9 | 2.200 0.047 10 | 2.225 0.050 11 | 2.250 0.053 12 | 2.275 0.056 13 | 2.300 0.058 14 | 2.325 0.059 15 | 2.350 0.060 16 | 2.375 0.060 17 | 2.400 0.060 18 | 2.425 0.059 19 | 2.450 0.058 20 | 2.475 0.056 21 | 2.500 0.053 22 | 2.525 0.050 23 | 2.550 0.047 24 | 2.575 0.043 25 | 2.600 0.038 26 | 2.625 0.033 27 | 2.650 0.028 28 | 2.675 0.022 29 | 2.700 0.015 30 | 2.725 0.008 31 | 2.750 -0.000 32 | -------------------------------------------------------------------------------- /airfoils/coord_parabolic_50.dat: -------------------------------------------------------------------------------- 1 | 0.000 0.000 2 | 0.020 0.008 3 | 0.040 0.015 4 | 0.060 0.023 5 | 0.080 0.029 6 | 0.100 0.036 7 | 0.120 0.042 8 | 0.140 0.048 9 | 0.160 0.054 10 | 0.180 0.059 11 | 0.200 0.064 12 | 0.220 0.069 13 | 0.240 0.073 14 | 0.260 0.077 15 | 0.280 0.081 16 | 0.300 0.084 17 | 0.320 0.087 18 | 0.340 0.090 19 | 0.360 0.092 20 | 0.380 0.094 21 | 0.400 0.096 22 | 0.420 0.097 23 | 0.440 0.099 24 | 0.460 0.099 25 | 0.480 0.100 26 | 0.500 0.100 27 | 0.520 0.100 28 | 0.540 0.099 29 | 0.560 0.099 30 | 0.580 0.097 31 | 0.600 0.096 32 | 0.620 0.094 33 | 0.640 0.092 34 | 0.660 0.090 35 | 0.680 0.087 36 | 0.700 0.084 37 | 0.720 0.081 38 | 0.740 0.077 39 | 0.760 0.073 40 | 0.780 0.069 41 | 0.800 0.064 42 | 0.820 0.059 43 | 0.840 0.054 44 | 0.860 0.048 45 | 0.880 0.042 46 | 0.900 0.036 47 | 0.920 0.029 48 | 0.940 0.023 49 | 0.960 0.015 50 | 0.980 0.008 51 | 1.000 0.000 52 | -------------------------------------------------------------------------------- /airfoils/coord_flat_plate_100.dat: -------------------------------------------------------------------------------- 1 | 0.000 0.000 2 | 0.010 0.000 3 | 0.020 0.000 4 | 0.030 0.000 5 | 0.040 0.000 6 | 0.050 0.000 7 | 0.060 0.000 8 | 0.070 0.000 9 | 0.080 0.000 10 | 0.090 0.000 11 | 0.100 0.000 12 | 0.110 0.000 13 | 0.120 0.000 14 | 0.130 0.000 15 | 0.140 0.000 16 | 0.150 0.000 17 | 0.160 0.000 18 | 0.170 0.000 19 | 0.180 0.000 20 | 0.190 0.000 21 | 0.200 0.000 22 | 0.210 0.000 23 | 0.220 0.000 24 | 0.230 0.000 25 | 0.240 0.000 26 | 0.250 0.000 27 | 0.260 0.000 28 | 0.270 0.000 29 | 0.280 0.000 30 | 0.290 0.000 31 | 0.300 0.000 32 | 0.310 0.000 33 | 0.320 0.000 34 | 0.330 0.000 35 | 0.340 0.000 36 | 0.350 0.000 37 | 0.360 0.000 38 | 0.370 0.000 39 | 0.380 0.000 40 | 0.390 0.000 41 | 0.400 0.000 42 | 0.410 0.000 43 | 0.420 0.000 44 | 0.430 0.000 45 | 0.440 0.000 46 | 0.450 0.000 47 | 0.460 0.000 48 | 0.470 0.000 49 | 0.480 0.000 50 | 0.490 0.000 51 | 0.500 0.000 52 | 0.510 0.000 53 | 0.520 0.000 54 | 0.530 0.000 55 | 0.540 0.000 56 | 0.550 0.000 57 | 0.560 0.000 58 | 0.570 0.000 59 | 0.580 0.000 60 | 0.590 0.000 61 | 0.600 0.000 62 | 0.610 0.000 63 | 0.620 0.000 64 | 0.630 0.000 65 | 0.640 0.000 66 | 0.650 0.000 67 | 0.660 0.000 68 | 0.670 0.000 69 | 0.680 0.000 70 | 0.690 0.000 71 | 0.700 0.000 72 | 0.710 0.000 73 | 0.720 0.000 74 | 0.730 0.000 75 | 0.740 0.000 76 | 0.750 0.000 77 | 0.760 0.000 78 | 0.770 0.000 79 | 0.780 0.000 80 | 0.790 0.000 81 | 0.800 0.000 82 | 0.810 0.000 83 | 0.820 0.000 84 | 0.830 0.000 85 | 0.840 0.000 86 | 0.850 0.000 87 | 0.860 0.000 88 | 0.870 0.000 89 | 0.880 0.000 90 | 0.890 0.000 91 | 0.900 0.000 92 | 0.910 0.000 93 | 0.920 0.000 94 | 0.930 0.000 95 | 0.940 0.000 96 | 0.950 0.000 97 | 0.960 0.000 98 | 0.970 0.000 99 | 0.980 0.000 100 | 0.990 0.000 101 | 1.000 0.000 102 | -------------------------------------------------------------------------------- /airfoils/coord_parabolic_100.dat: -------------------------------------------------------------------------------- 1 | 0.000 0.000 2 | 0.010 0.004 3 | 0.020 0.008 4 | 0.030 0.012 5 | 0.040 0.015 6 | 0.050 0.019 7 | 0.060 0.023 8 | 0.070 0.026 9 | 0.080 0.029 10 | 0.090 0.033 11 | 0.100 0.036 12 | 0.110 0.039 13 | 0.120 0.042 14 | 0.130 0.045 15 | 0.140 0.048 16 | 0.150 0.051 17 | 0.160 0.054 18 | 0.170 0.056 19 | 0.180 0.059 20 | 0.190 0.062 21 | 0.200 0.064 22 | 0.210 0.066 23 | 0.220 0.069 24 | 0.230 0.071 25 | 0.240 0.073 26 | 0.250 0.075 27 | 0.260 0.077 28 | 0.270 0.079 29 | 0.280 0.081 30 | 0.290 0.082 31 | 0.300 0.084 32 | 0.310 0.086 33 | 0.320 0.087 34 | 0.330 0.088 35 | 0.340 0.090 36 | 0.350 0.091 37 | 0.360 0.092 38 | 0.370 0.093 39 | 0.380 0.094 40 | 0.390 0.095 41 | 0.400 0.096 42 | 0.410 0.097 43 | 0.420 0.097 44 | 0.430 0.098 45 | 0.440 0.099 46 | 0.450 0.099 47 | 0.460 0.099 48 | 0.470 0.100 49 | 0.480 0.100 50 | 0.490 0.100 51 | 0.500 0.100 52 | 0.510 0.100 53 | 0.520 0.100 54 | 0.530 0.100 55 | 0.540 0.099 56 | 0.550 0.099 57 | 0.560 0.099 58 | 0.570 0.098 59 | 0.580 0.097 60 | 0.590 0.097 61 | 0.600 0.096 62 | 0.610 0.095 63 | 0.620 0.094 64 | 0.630 0.093 65 | 0.640 0.092 66 | 0.650 0.091 67 | 0.660 0.090 68 | 0.670 0.088 69 | 0.680 0.087 70 | 0.690 0.086 71 | 0.700 0.084 72 | 0.710 0.082 73 | 0.720 0.081 74 | 0.730 0.079 75 | 0.740 0.077 76 | 0.750 0.075 77 | 0.760 0.073 78 | 0.770 0.071 79 | 0.780 0.069 80 | 0.790 0.066 81 | 0.800 0.064 82 | 0.810 0.062 83 | 0.820 0.059 84 | 0.830 0.056 85 | 0.840 0.054 86 | 0.850 0.051 87 | 0.860 0.048 88 | 0.870 0.045 89 | 0.880 0.042 90 | 0.890 0.039 91 | 0.900 0.036 92 | 0.910 0.033 93 | 0.920 0.029 94 | 0.930 0.026 95 | 0.940 0.023 96 | 0.950 0.019 97 | 0.960 0.015 98 | 0.970 0.012 99 | 0.980 0.008 100 | 0.990 0.004 101 | 1.000 0.000 102 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # coded by Pablo N. Zitelli 3 | # https://www.linkedin.com/in/pablozitelli 4 | ########################################### 5 | 6 | import sys 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | from utils import * 10 | 11 | # aerodynamic parameters 12 | alphaDeg = 10.0 13 | vInf = 1.0 14 | 15 | # actual code 16 | 17 | # airfoils initialization 18 | airfoils = [] 19 | 20 | airfoils.append(Airfoil('Front', 'coord_parabolic_front_20.dat')) 21 | airfoils.append(Airfoil('Rear', 'coord_parabolic_rear_30.dat')) 22 | #airfoils.append(Airfoil('Flat plate', 'coord_flat_plate_10.dat')) 23 | 24 | # induced velocity on panel i collocation point due to unit vortex on panel j (all airfoils) 25 | for am in airfoils: 26 | for pi in am.panels: 27 | for an in airfoils: 28 | for pj in an.panels: 29 | pi.unitVelIndCalc(pj.qPoint) 30 | 31 | # RHS (independent vector) definition 32 | N = 0 33 | for a in airfoils: 34 | N = N + a.panelNum 35 | 36 | b = np.zeros(N) 37 | vectorInf = np.array([vInf*np.cos(alphaDeg*np.pi/180.0), vInf*np.sin(alphaDeg*np.pi/180.0), 0.0]) 38 | 39 | i = 0 40 | for a in airfoils: 41 | for p in a.panels: 42 | b[i] = -np.dot(vectorInf, p.n) 43 | i = i + 1 44 | 45 | # influence coefficients matrix definition 46 | A = np.zeros((N, N)) 47 | 48 | i = 0 49 | for a in airfoils: 50 | for pi in a.panels: 51 | for j in range(N): 52 | A[i,j] = np.dot(pi.unitVelInd[j], pi.n) 53 | 54 | i = i + 1 55 | 56 | # linear system solution 57 | gamma = np.linalg.solve(A, b) 58 | 59 | # results 60 | i = 0 61 | for a in airfoils: 62 | for p in a.panels: 63 | p.setGamma(gamma[i]) 64 | p.dCpCalc(vInf) 65 | i = i + 1 66 | 67 | # total quantity of panels in system 68 | totalPanels = [] 69 | for a in airfoils: 70 | totalPanels = totalPanels + a.panels 71 | 72 | # airfoil lift 73 | for a in airfoils: 74 | for p in a.panels: 75 | p.totalIndVelCalc(totalPanels) 76 | p.liftCalc(vectorInf) 77 | 78 | a.liftCalc(vInf) 79 | 80 | writeResults(airfoils) 81 | 82 | # plot airfoils 83 | airfoilsNameList = [] 84 | fig, ax = plt.subplots() 85 | for a in airfoils: 86 | airfoilsNameList.append(a.name) 87 | x = [p.nodes[0].coord[0] for p in a.panels] 88 | x.append(a.panels[-1].nodes[1].coord[0]) 89 | y = [p.nodes[0].coord[1] for p in a.panels] 90 | y.append(a.panels[-1].nodes[1].coord[1]) 91 | ax.plot(x, y, label=a.name) 92 | 93 | plt.axis('equal') 94 | plt.title('airfoils setup') 95 | plt.xlabel('x [m]') 96 | plt.ylabel('y [m]') 97 | plt.legend(airfoilsNameList) 98 | plt.grid() 99 | 100 | # delta Cp along airfoils 101 | f = 2 102 | for a in airfoils: 103 | # chord vector 104 | cVec = a.panels[-1].nodes[1].coord - a.panels[0].nodes[0].coord 105 | 106 | sC = [np.dot(p.mPoint-a.panels[0].nodes[0].coord, cVec)/a.c for p in a.panels] 107 | dCpPlot = [p.dCp for p in a.panels] 108 | 109 | plt.figure(f) 110 | plt.title(a.name) 111 | plt.plot(sC, dCpPlot, 'k') 112 | plt.xlabel('x/c') 113 | plt.ylabel('dCp') 114 | f = f + 1 115 | 116 | plt.show() 117 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # coded by Pablo N. Zitelli 3 | # https://www.linkedin.com/in/pablozitelli 4 | ########################################### 5 | 6 | import numpy as np 7 | 8 | # node class definition 9 | class Node: 10 | def __init__(self, coord, name): 11 | self.coord = coord 12 | self.id = name 13 | 14 | # panel class definition 15 | class Panel: 16 | def __init__(self, nodes, name): 17 | self.unitVelInd = [] 18 | self.nodes = nodes 19 | self.id = name 20 | self.lift = 0.0 21 | 22 | # tangent unit vector and panel length 23 | tVec = self.nodes[1].coord - self.nodes[0].coord 24 | self.len = np.linalg.norm(tVec) 25 | self.t = tVec/self.len 26 | 27 | # normal unit vector 28 | kVersor = np.array([0.0, 0.0, 1.0]) 29 | nVec = np.cross(kVersor, self.t) 30 | nMag = np.linalg.norm(nVec) 31 | self.n = nVec/nMag 32 | 33 | # quarter chord point coordinates 34 | self.qPoint = self.nodes[0].coord + 0.25*tVec 35 | 36 | # collocation point coordinates 37 | self.cPoint = self.nodes[0].coord + 0.75*tVec 38 | 39 | # midpoint coordinates 40 | self.mPoint = self.nodes[0].coord + 0.5*tVec 41 | 42 | # calculation of induced velocity produced by unit vortex 43 | def unitVelIndCalc(self, quarterPoint): 44 | vector = self.cPoint - quarterPoint 45 | r = np.linalg.norm(vector) 46 | u = (0.5/(np.pi*r**2))*vector[1] 47 | v = - (0.5/(np.pi*r**2))*vector[0] 48 | self.unitVelInd.append(np.array([u, v, 0.0])) 49 | 50 | def setGamma(self, gamma): 51 | self.gamma = gamma 52 | 53 | # delta Cp calculation across panel 54 | def dCpCalc(self, velInf): 55 | self.dCp = 2*self.gamma/(velInf*self.len) 56 | 57 | # total velocity induced by all vortices except itself 58 | def totalIndVelCalc(self, panels): 59 | self.totalVelInd = np.array([0.0, 0.0, 0.0]) 60 | for p in panels: 61 | if self != p: 62 | distVec = self.qPoint - p.qPoint 63 | r = np.linalg.norm(distVec) 64 | u = p.gamma*(0.5/(np.pi*r**2))*distVec[1] 65 | v = - p.gamma*(0.5/(np.pi*r**2))*distVec[0] 66 | self.totalVelInd = self.totalVelInd + np.array([u, v, 0.0]) 67 | 68 | # panel lift calculation (generalized Kutta-Joukowski) 69 | def liftCalc(self, velInf): 70 | vInf = np.linalg.norm(velInf) 71 | self.lift = 1.2*vInf*self.gamma*(1 + np.dot(velInf, self.totalVelInd)/vInf**2) 72 | 73 | # airfoil class definition 74 | class Airfoil: 75 | def __init__(self, name, coordFile): 76 | self.name = name 77 | self.panels = [] 78 | 79 | # nodes coordinates reading 80 | nodesFile = np.loadtxt(coordFile) 81 | 82 | # nodes creation 83 | nodes = [] 84 | count = 1 85 | for i in nodesFile: 86 | nodes.append(Node(np.array([i[0], i[1], 0.0]), count)) 87 | count = count + 1 88 | 89 | # panels creation 90 | for i in range(len(nodes)-1): 91 | self.panels.append(Panel([nodes[i], nodes[i+1]], i+1)) 92 | 93 | self.panelNum = len(self.panels) 94 | 95 | # chord calculation 96 | cVec = self.panels[-1].nodes[1].coord - self.panels[0].nodes[0].coord 97 | self.c = np.linalg.norm(cVec) 98 | 99 | # aerodynamic characteristics: Gamma, L and Cl 100 | def liftCalc(self, vInf): 101 | self.Gamma = 0.0 102 | self.L = 0.0 103 | 104 | for p in self.panels: 105 | self.Gamma = self.Gamma + p.gamma 106 | self.L = self.L + p.lift 107 | 108 | self.Cl = 2.0*self.L/(1.2*vInf**2*self.c) 109 | 110 | # domain (grid) class definition for streamlines 111 | class Domain: 112 | def __init__(self, nx, ny, xMin, xMax, yMin, yMax): 113 | 114 | self.grid = [] 115 | 116 | x = np.linspace(xMin, xMax, nx) 117 | y = np.linspace(yMin, yMax, ny) 118 | 119 | # grid point class definition 120 | class Point: 121 | def __init__(self, x, y): 122 | self.coord = np.array([x, y, 0.0]) 123 | 124 | # write results to file and terminal 125 | def writeResults(airfoils): 126 | with open('results.dat', 'w') as out: 127 | print(40*'=' + '\n') 128 | out.write(40*'=' + '\n') 129 | 130 | for a in airfoils: 131 | print('Airfoil {0:s} aerodynamic characteristics:'.format(a.name)) 132 | print('Gamma = {0:.4f} m2/s'.format(a.Gamma)) 133 | print('L = {0:.4f} N/m'.format(a.L)) 134 | print('Cl = {0:.4f}\n'.format(a.Cl)) 135 | print(40*'=' + '\n') 136 | 137 | out.write('Airfoil {0:s} aerodynamic characteristics:\n'.format(a.name)) 138 | out.write('Gamma = {0:.4f} m2/s\n'.format(a.Gamma)) 139 | out.write('L = {0:.4f} N/m\n'.format(a.L)) 140 | out.write('Cl = {0:.4f}\n'.format(a.Cl)) 141 | out.write(40*'=' + '\n') 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lumped-vortex panel method 2 | 3 | ## Introduction 4 | This is the simplest model which can be found under the panel method category. Lumped (or _discrete_) vortex means that every panel has a discrete vortex located at the panel quarter chord point. This model is suited to analyze thin airfoils, since the mean curvature line is discretized in panels. The present code is designed in order to handle the multi-element problem. 5 | 6 | This project is useful for students and curious of aerodynamics, since despite being very simple from the theory point of view, one can: 7 | - Achieve strong insight on how to apply the fundamentals of aerodynamics. 8 | - Understand how the panels and/or airfoils interact with each others. 9 | - Take the first steps in programming (the code is written as much as possible as OOP). 10 | - Investigate some numerical issues which arise when discretizing in a large number of panels. 11 | - Have useful and estimated results for validation with other more sofisticated methods. 12 | 13 | ## Using the script 14 | Once downloaded, you can try running `main.py` and the following will happen: 15 | - Three windows showing: 16 | - Plot of two parabolic airfoils in tandem, called _Front_ and _Rear_ in the domain. 17 | - Plot of delta Cp (difference between lower and upper Cp) along relative chord (x/c) for _Front_ airfoil. 18 | - Plot of delta Cp along relative chord for _Rear_ airfoil. 19 | - Airfoils aerodynamic characteristics shown in terminal window: 20 | - Gamma: circulation associated to the specific airfoil. 21 | - L: lift (per unit span) generated by the specific airfoil. 22 | - CL: lift coefficient generated by the specific airfoil. 23 | - A file called `results.dat` with the same results listed above. 24 | 25 | ### Setting an analysis 26 | 1. In line 7 of file `main.py` declare the incidence angle (in degrees) of the free stream velocity respect th _x_ axis, this angle is called _alpha_ as can be seen in the following image (from _Low-Speed Aerodynamics_ by Katz & Plotkin). 27 | 2. The freestream velocity magnitude is declared in line 8. 28 | 3. Airfoils are declared appending objects initialized with `Airfoil('name', 'coordinates_file.dat')` to `airfoils` array, as seen in lines 15 and 16 of file `main.py`. 29 | 4. Finally, run `main.py`. 30 | 31 | ![image](https://github.com/pzitelli84/discrete-vortex-panel-method/assets/8440605/74f7adc4-d11f-47ed-9bd6-cb4acae9e762) 32 | 33 | ### Defining airfoils 34 | Each airfoil used in the problem must be defined separately in a file containing ordered nodes (rows), which columns represent the _x_ and _y_ coordinates. The files must be in the same directory as `main.py`. Take for instance `coord_parabolic_front_20.dat`, where you can see how coordinates are defined. The order of nodes must follow the orientarion shown in te above image, i.e.: each panel is defined with the frst node upstream and the second downstream. 35 | 36 | When creating an object instance of `Airfoil` class, two parameters must be passed: 37 | - A string which defines its name for identification purposes. 38 | - A second string with the nodes coordinates file name. 39 | 40 | Several coordinate files can be found inside `airfoils` directory, in their names the type of airfoil is described as well as the number of panels used. It is importan to remark that the nodes coordinates are measured in the global reference system, so when having a multi-element configuration, the location of every airfoil is taken from the nodes coordinates file. 41 | 42 | ### `main.py` overview 43 | This is the skeleton of the script, where instructions to solve the problem are found. It uses methods defined in `utils.py`. The logics of the step-by-step can be followed straight forward and it is recommended not to modify the structure. 44 | 45 | ### `utils.py` overview 46 | All the class definitions, functions and calculations are written in this file. The code is commented so the user can quickly understand what is being done by the script. This is fundamental when comparing the code to the math found in the theory. 47 | 48 | ## Theory background 49 | As was mentioned in the introduction, this approach is the simplest one which can be found under the vast variety of panel methods flavours. Due to its simplicity, it is very easy to be coded. Since there is not any distribution of vorticity along the panel length but a discrete vortex attached to it, the formulation of the flow tangency boundary condition is found straight forward. 50 | 51 | The induced velocities are found on every control point (located at the three-quarter panel length) pplying the Biot-Savart Law. Then, the dot product of the induced velocities times the unit normal vector of each panel is equated to zero. A linear algebraic equation system is obtained, where the vortex strengths of each panel are the unknowns. 52 | 53 | Once the system is solved, the vortex strengths are used to calculate the Cp difference along the airfoils, their individual lift using the Generalized Kutta-Joukowski Theorem as well as the individual CL. 54 | 55 | ### Numerical issues 56 | An interesting issue with this type of methods is that, contrary to intuition, oscillations on the Cp difference along the airfoils chord are observed when the airfoil is discretized in a larger number of panels. The reader is encouraged to investigate this phenomenon typically found in methods with discrete unknowns values instead of distributed ones. Katz & Plotkin give some insight on this. 57 | 58 | ### Recommended bibliography 59 | For a broader and detailed explanation, the reader is recommended to study from: 60 | - Low-Speed Aerodynamics, J. Katz & A. Plotkin. 61 | - An Introduction to Theoretical and Computational Aerodynamics, J. Moran. 62 | - Theoretical and Computational Aerodynamics, T. Sengupta. 63 | 64 | ## Future improvements 65 | - Streamline plots. 66 | --------------------------------------------------------------------------------