The Python code for this initial translation and implementation has been included at the bottom of this post. Initial attempts to verify the script appear to show that it is working well. Included in the paper from Pusan University [1] is an image of an DHMTU 4-40-1-10-1.5-60-15-3 section. This image was digitised and compared to the same section created by the initial GEVfoil implementation:

This graph is also redrawn below, with a stretched y-axis, in order to better compare the aerofoil fit, which appears to be excellent. However, it is worth reiterating a point from the previous post, that the validity of this paper is unknown. The author of this blog does not have any definitive original source from the Department of Hydrodynamics at the Marine Technical University (DHMTU), St. Petersburg. It is perhaps unsurprising that these aerofoils match, when both the script is based on the definition from this same source.

Efforts have been made to find additional DHMTU sections from which to compare. However, the author of this blog post enjoyed limited success due to a lack of these aerofoils in the western literature. This blog’s author has an old, low-resolution image, of a DHMTU 8-40-2-10-3-60-20-1.5, taken via the ‘Airfoil Calculator’ from the now defunct website S.E. Technology.

In spite of the low resolution, best efforts were made to digitise this image, and it was compared against the same section generated in GEVfoil. This comparisons also seems acceptable whilst examined with equal scale spacing:

However, when the y-axis is stretched there is some discrepancy, especially around the quarter-chord of the upper-surface, as can be seen in the image below. It is likely that this is simply due to errors introduced in the digitisation of the low-resolution image. Broadly, it can be said that the shape is similar, and there is no reason to distrust the result produced by this initial implementation of DHMTU in GEVfoil.

It is hoped that further examples will be found for comparison. The DHMTU Python implementation, as it stands, is included below. Please note, that unlike the rest of GEVfoil, this script is GNU General Purpose Licence (GPL), as it is based on the GPL Matlab script.

```
#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
"""
##############################################################################
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/>.
##############################################################################
A simple script to produce DHMTU aerofoils. This script is based on an
open-source script distributed under the GNU GPL, hence the inclusion of the
licence statements of the original script. This differs from the Creative
Commons Attribution-NonCommercial 3.0 Unported License elsewhere in GEVfoil.
Based on the Matlab of (Martin Hepperle) 2012-02-01 which was
converted from the Mathematica provided by Chang Chong-Hee, 1996
Rewritten into Python in April 2021 (Jason Moller)
@author: glypo (Jason Moller)
"""
class DHMTU(object):
"""A class for generating DHMTU aerofoils"""
def __init__(self, foil, points=100, chord=1):
super(DHMTU, self).__init__()
tu = foil[0] / 100
xt = foil[1] / 100
t1 = foil[2] / 100
x1 = foil[3] / 100
t2 = foil[4] / 100
x2 = foil[5] / 100
delb = foil[6] / 100
k = foil[7]
# Determine coefficients
c0 = t1
c1 = (t2-t1)/(x2-x1)
d1 = delb
d2 =(-2*delb + 3*tu + 2*delb*xt)/(xt-1)**2
d3 =( -delb + 2*tu + delb*xt)/(xt-1)**3
a0 = np.sqrt(2)*np.sqrt(k)*tu
a1 = (3*tu)/xt - (15*np.sqrt(k)*tu)/(4*np.sqrt(2)*np.sqrt(xt)) + d2*xt + 3*d3*xt - 3*d3*xt**2
a2 = (-2*d2) - (6*d3) - (3*tu)/(xt**2) + (5*np.sqrt(k)*tu)/(2*np.sqrt(2)*xt**(3/2)) + 6*d3*xt
a3 = -3*d3 + tu/xt**3 - (3*np.sqrt(k)*tu)/(4*np.sqrt(2)*xt**(5/2)) + d2/xt + 3*d3/xt
b0 = np.sqrt(2)*np.sqrt(k)*tu
b1 = (-8*t1*x1 - 16*t2*x1 + 15*np.sqrt(2)*np.sqrt(k)*tu*x1**(3/2) + 24*t1*x2 - 15*np.sqrt(2)*np.sqrt(k)*tu*np.sqrt(x1)*x2)/(8* x1*(x2-x1))
b2 = (5*np.sqrt(k)*tu)/(2*np.sqrt(2)*x1**(3/2)) + (3*t1*x2)/(x1**2*(x1-x2)) + (3*t2)/(x1*x2 - x1**2)
b3 = (-3*np.sqrt(k)*tu)/(4*np.sqrt(2)*x1**( 5/2)) + (t1*x2-t2*x1)/(x1**3*(x2-x1))
e1 = (2*t1 - 2*t2 + 3*t2*x1-2*t1*x2 - t2*x2) / (x1-x2-x1*x2 + x2**2);
e2 = (3*(t2-t1-t2*x1 +t1*x2))/((1-x2)**2*(x1-x2))
e3 = (t2-t1-t2*x1 +t1*x2)/((x1-x2)*(x2-1)**3)
# prepare table of points from trailing edge via upper surface to nose and back to trailing edge
phiRange = np.linspace(np.pi, 0, points)
xp = np.zeros(2 * points-1)
yp = np.zeros(2 * points-1)
# 1-based index of leading edge point
idxLE = points-1
for i, phi in enumerate(phiRange):
x = (1 + np.cos(phi)) / 2
# upper surface
if x <= xt:
yu = a0*np.sqrt(x) + a1*x + a2*x**2 + a3*x**3;
else:
yu = d1*(1-x) + d2*(1-x)**2 + d3*(1-x)**3
xp[idxLE-i] = x
yp[idxLE-i] = yu
# lower surface
if x <= x1:
yl = -b0*np.sqrt(x) - b1*x -b2*x**2 - b3*x**3
elif x <= x2:
yl = -c0 - c1*(x-x1)
else:
yl = -e1*(1-x) - e2*(1-x)**2 - e3*(1-x)**3
xp[idxLE+i] = x
yp[idxLE+i] = yl
# Scale the chord
xp = xp * chord
yp = yp * chord
#plt.plot(xp, yp, '.')
plt.plot(xp, yp)
plt.grid(True)
plt.axis('equal')
plt.show()
if __name__ == "__main__":
test = DHMTU([12, 35, 3, 10, 2, 80, 12, 2])
```

In summary, it is believed this initial implementation is working as expected. Thus, it is hoped that this blog’s author can shortly commence some aerodynamic analyses.

[1] Hepperle, M (2017). JavaFoil Theory Document. User Manual.

[2] Ho-Hwan, C., Chong-Hee C. (1996). Development of S-shaped Section (DHMTU Family). Unknown publication type.

The DHMTU established an aerofoil series whose geometries are defined analytically, which to my knowledge is the only series of parametrised aerofoils designed specifically for ground effect flight. The definition varies quite significantly from the NACA 4-digit aerofoils, already in GEVfoil. The aerofoil has a partly or fully flat base with an s-shaped mean camber line.

The DHMTU aerofoils notation has eight digits, given as follows:

**DHMTU Y _{1}-X_{1}-Y_{2}-X_{2}-Y_{3}-X_{3}-δ_{up}-R_{LE}**

Y_{1} | maximum ordinate of the upper surface (%c) |

X_{1} | abscissa of Y_{1} (%c) |

Y_{2} | ordinate of the start of the flat section (%c, positive value represents negative y) |

X_{2} | abscissa of Y_{2} (%c) |

Y_{3} | ordinate of the end of the flat section (%c, positive value represents negative y) |

X_{3} | absissa of Y_{3} (%c) |

δ_{up} | slope parameter of the upper trailing edge |

R_{LE} | leading edge radius parameter |

Some DHMTU sections from the literature include DHMTU 12-35-3-10-2-80-12-2 [1] and DHMTU 10-40-2-10-2-60-21-5 [2]. These names don’t exactly register in memory as readily as the likes of the NACA 0012! The JavaFoil manual has a very nice figure of the DHMTU section:

The above information is readily available in some western papers such as Moore’s [1], theses such as a Smuts [2] and manuals such as JavaFoil by Hepperle [3]. However, no further information is given. Moore, from 2002, references a South Korean website, which is now defunct. Hepperle references a Korean document from 1996; ‘*Development of S-shaped Section (DHMTU Family)*‘ by Chun Ho-Hwan, Chang Chong-Hee of Pusan University. No further information is given about this document, for example whether it’s a conference paper, a journal paper or perhaps a thesis, and despite my best efforts I was initially unable to find this document online.

With thanks to my friends in the Wing In Ground Effect group on groups.io I have managed to obtain the *‘Development of S-shaped Section’* paper in Korean [4] from a member’s personal archive (thank you, Marc!). I cannot recommend this group enough for those with an interest in ground effect craft, it is the successor to the long standing group of the same name on Yahoo Groups, whose service is moribund.

I have absolutely no command of the Korean language, however the equations and diagrams are mutually intelligible, and the online translators are adequate enough. I have done my best to capture this information below. At a future date I will explore options for more formally capturing my findings following some further analysis, in the hope that this fascinating aerofoil family can be recorded for prosperity in western literature.

The following is my best attempt (along with online translators) to understand this document.

The upper-surface is split into a fore and aft section, the fore starting at the leading edge and terminating at the maximum y ordinate, Y_{1}. The aft section somewhat intuitively starts at Y_{1} and ends at the trailing edge.

subject to \[0 < x < x_1\]

And secondly the upper-surface aft of the maximum ordinate:

\[ y_{upper} = d_0 + d_1(1-x) + d_2(1-x)^2 + d_3(1-x)^3 \]subject to \[ x_1 < x < 1\]

The parameters a_{0} to a_{4} are obtained via a fairly convoluted process.

Leading edge radius \[r_t = a_0/2\]

Leading edge parameter \[ r_{nose} = r / t+u^2\]

Radius at the point X_{1} is \[R = \frac{1}{(2d_2+6d_3(1-x_1)}\]

The paper then works through some derivation – and it is unclear throughout the paper whether this is the interpretation of the author, or whether this is the author translating from an original and unreferenced Russian document. Four conditions are given as a set of linear equations:

\[t_u = a_0 \sqrt{x_t} + a_1 x_t+a_2 x_t^2+a_3 x_t^3\]

\[t_u – a_0 \sqrt{x_t} = a_1 x_t+a_2 x_t^2+a_3 x_t^3\]

For the aerofoil aft of the maximum ordinate, the four parameters d_{0} to d_{3} are obtained via similarly convoluted process.

Mercifully, the third condition simple:

\[ d_0 = 0 \]The lower surface is split into three sections, the curved fore section, similar to the upper-section, a flat mid section and a curved aft section.

As with the upper-surface:

\[ y_{lower} = b_0 \sqrt{x} + b_1 x + b_2 x^2 +b_3 x^3 \]subject to: \[0 < x < x_1\]

The four parameters a_{0} to a_{4} are obtained via a fairly convoluted process.

t_{lower} is the ordinate of the lower surface at point x_{1}.

y” = 0 at point x_{1}.

Leading edge radius:

\[r_t=\frac{b_0^2}{2}\]At the point x_{1}:

That is to say, there is continuity at the interface of the sections.

The flat middle section is somewhat simply defined, between x_{1}<x<x_{2}:

Finally, the aft-section is defined in a manner not entirely dissimilar to the fore, between x_{2} < x < (c=1):

t_{lower2} is the ordinate of the lower surface at point x_{2}.

y” (x_{2}) = 0 at point x_{1}.

At x=1 y(1)=0 – that is to say the trailing edge is (1, 0), i.e. an abscissa of 1 with an ordinate of zero. Noting that we defining this aerofoil with a normalised chord at unity, as is customary with aerofoils.

At the point x_{2}:

Once again I believe the paper is just telling us there is continuity and no gradient change at the point where the mid and aft sections meet.

The pattern continues once one with linear expressions and coefficient definition:

The mathematics behind the DHMTU sections are certainly more complex than other analytically defined geometries such as the well known and used NACA 4-series and 6-series.

It cannot be stressed strongly enough – I have made my best attempt to understand a paper in a foreign language (Korean), I have no understanding as to the validity of said reference. Consequently, I cannot have much confidence in these equations as they are presented.

I do have access to some images of DHMTU sections. My next step will be to implement this series into GEVfoil and attempt to reproduce the shapes, to increase confidence.

[1] Moore, N., Wilson, P.A. and Peters, A.J. (2002) An investigation into wing in ground effect aerofoil geometry. In *RTO-MP-095. *NATO RTO.

[2] Smuts, E. (2009) A Computational Study of a Lifting Wing in Close Proximity to a Moving Ground Plane. Masters Dissertation.

[3] Hepperle, M (2017). JavaFoil Theory Document. User Manual.

[4] Ho-Hwan, C., Chong-Hee C. (1996). Development of S-shaped Section (DHMTU Family). Unknown publication type.

I’ve settled on GEVfoil (Ground Effect Vehicle Aerofoil) which may seem a little obvious, but clarity should at least be in my favour. The objectives remain the same;

- To create a set of tools to aid in the analysis of aerofoils in ground effect.
- To primarily focus on 2D effects, but not exclude 3D effects where appropriate and relevant.
- To utilise open-source tools as much as possible, for example links into the OpenFOAM CFD (Computational Fluid Dynamics) project.
- To maintain a blog of the development, and open-source the whole project when it has content enough to be worthy.

I have renamed previous blog posts with this in mind:

]]>Many of the simplified treatments for spacing points along an aerofoil use a logarithmic spacing biased towards the leading edge. This of course can create issues if spaced in the chord-wise direction (x/c). The surface-normal at the leading edge of a symmetric aerofoil is perpendicular to the stream-wise direction, and thus can become under-resolved, and even faceted, with a simple linear or logarithmic chord-wise spacing.

One of the more significant issues with linear or logarithmic spacing is that it simply does not discriminate on the basis of curvature, thus the growth factor becomes a qualitative parameter controlled by the aerodynamicist. Any other curvature, for example the upper-surface curvature caused by the s-shaped chord of a DHTMU aerofoil, is neglected.

Some computational treatments, though able to nicely capture curvature, are a little too complex for this simplified tool intended for 2D aerofoil analyses. I believe that the Ramer-Douglas-Peucker (RDP) algorithm is a simple yet powerful tool ideally suited to be applied to spacing points/nodes along a computational grid for for 2D aerofoil analyses.

RDP is a a decimation (down-sampling) algorithm, which although cannot increase resolution (create points) in areas of high-curvature, it can remove remove points in areas where curvature is low. This approach is quite simple and elegant. The algorithm works by creating a line between the start and end points, then recursively divides this line whilst each time finding the furthest point, marking these points to be kept if they are greater than a user-specified distance (epsilon).

As this requires a set of points, rather than a spline or other analytical curve, there are upsides and downsides. In the case of of analytic aerofoils, such as the DHTMU, or the NACA 4 that I originally scripted into GEVgoil, these need to be discretised before being down-sampled. However, for aerofoils that are already being supplied as a set of points, for example using the CSV method that is now also in GEVfoil, RDP can be applied directly.

To simplify matters further, RDP is available as a Python module via PIP: https://pypi.org/project/rdp/ Using this excellent ready-made library, I have constructed perhaps the simplest Python class, implementing this RDP module into GEVfoil:

```
from rdp import rdp
import numpy as np
class RDP(object):
"""A class to decimate aerofoil points"""
def __init__(self, x, y, e=0.0001):
xy = np.vstack((x, y)).T
self.reducedXY = rdp(xy, epsilon=e)
self.reducedX = self.reducedXY[:,0]
self.reducedY = self.reducedXY[:,1]
```

And of course, the proof is in the pudding. Some examples of the RDP module in action in GEVfoil with varying epsilon:

]]>Initially I had considered returning the aerofoil as a mathematical function, rather than a set of points. This concept may well be revisited in the future. However, most of the other input aerofoils will arrive as a set of coordinates of varying fidelity, from sources such as a the UIUC Airfoil Coordinates Database or from Comma Separated Variable (CSV) files. Therefore, the classes which will receive the aerofoil coordinate objects will deal with interpolating and appropriately converting coordinates into functions anyway. Consequently, as long as the linear-spacing is arbitrarily high enough a simple piece-wise cubic interpolation will suffice.

Note that the CSV reader itself is an abstraction of the AerofoilReader class, which I plan to later extend to read the DAT file from UIUC,

To that end, the CSV reader currently looks as follows:

```
import os
class AerofoilReader(object):
""" An abstract class to be inherited by classes for specific
aerofoil file types """
def __init__(self, filename):
super(AerofoilReader, self).__init__()
self.coord = []
self.title = [] # Column titles
self.label = [] # Aerofoil Label
foil = open(filename, 'r')
self.lines = foil.readlines()
class CSVReader(AerofoilReader):
""" Read in CSV. Assumes a header is present."""
def __init__(self, filepath, xcol, ycol, zcol=False):
super(CSVReader, self).__init__(filepath)
self.label = os.path.basename(filepath).split('.')[0]
inpcols = 3 # Two or three columns
if not zcol:
inpcols = 2
for i, line in enumerate(self.lines):
parts = line.rstrip().replace('"', '').split(',')
additional = []
if i > 0:
# Not the header
if len(parts) > inpcols:
# More than x, y & z - store other columns
for j in range(len(parts)):
if j != xcol and j != ycol and j != zcol:
additional.append(parts[j])
xyz = [parts[xcol], parts[ycol], parts[zcol]]
self.coord.append(xyz + additional)
else:
# It's the header
self.title = parts
```

The first step is starting with something basic, in this case a simple class to generate NACA 4 series aerofoils:

```
import numpy as np
import matplotlib.pyplot as plt
class NACA4(object):
"""A class for generating NACA 4 series aerofoils
TO-DO - linspace not good at LE capture """
def __init__(self, foil, points=200, chord=1):
super(NACA4, self).__init__()
m = float(foil[0])/100 # max camber
p = float(foil[1])/10 # chordwise position of max camber
t = float(foil[2:])/100 # thickness
x = np.linspace(0, chord, points)
self.x = x
yt = self.thickness(x, t)
yc = []
for coord in x:
if m:
yc.append(self.camber(m, p, coord))
else:
# No camber
yc.append(0)
y1 = yc + yt
y2 = yc - yt
self.y = y1
plt.plot(self.x, yc)
plt.plot(self.x, y1, '.')
plt.plot(self.x, y2, '.')
plt.axis('equal')
plt.show()
def thickness(self, x, t):
# Y thickness at given x point
return t / 0.2 * \
(0.2969*np.sqrt(x)-0.126*x-0.3516*x**2+0.2843*x**3-0.1015*x**4)
def camber(self, m, p, x):
# Return the camber of the aerofoil
if x <= p:
return m/p**2 * (2*p*x-x**2)
return m/(1-p)**2*((1-2*p)+2*p*x-x**2)
if __name__ == "__main__":
test = NACA4('2412')
```

For now, Matplotlib has been built into the class for visual reference. For example, 2412 generates:

And 0010 creates:

At this stage there is a clear issue around the leading edge, the function was interrogated with a linearly spaced x/chord-wise interval, this must be improved for the future to cluster points around the greatest curvature.

]]>