diff --git a/doc/options.yaml b/doc/options.yaml index 0fd6c5d..61c6463 100644 --- a/doc/options.yaml +++ b/doc/options.yaml @@ -94,20 +94,28 @@ splay: desc: > Splay BC spreading factor. This controls how far the floating edges splay outwards and can be useful for increasing the volume overlap in overset meshes. + This variable may come in different formats: + (1) If it is a ``scalar``, the same value is used throughout all of the grid. + (2) If it is a ``1D list``, it must have a length of :py:data:`N` - 1. Each entry is used for the corresponding extrusion layer. E.g. ``splay: [0.2, 0.3, 0.4]`` would result in a grid with 3 extrusions where the first layer uses ``0.2``, the second layer uses ``0.3`` and the last one uses ``0.4``. + (3) If it is a ``2D list``, the values are interpolated linearly. E.g. ``splay: [[0.0, 0.2], [0.5, 0.4], [1.0, 0.0]]`` would result in a linear increase from ``0.2`` to ``0.4`` at half of the total :py:data:`N`. Then it would linearly decrease until it reaches ``0.0`` in the last extrusion. + splayEdgeOrthogonality: desc: > How hard to try to force orthogonality at splay edges. Should be between 0 and 0.5. + Please look at :py:data:`splay` for explanations and examples on the format. splayCornerOrthogonality: desc: > How hard to try to force orthogonality at splay corners. + Please look at :py:data:`splay` for explanations and examples on the format. cornerAngle: desc: > Maximum convex corner angle in degrees necessary to trigger the implicit node averaging scheme. See Section 8 of :ref:`Chan and Steger` for more information. + Please look at :py:data:`splay` for explanations and examples on the format. coarsen: desc: > @@ -190,6 +198,7 @@ epsE: If the geometry has very sharp corners, too much explicit smoothing will cause the solver to rapidly "soften" the corner and the grid will fold back on itself. In concave corners, additional smoothing will prevent lines from crossing (avoiding negative cells). See Section 3 of :ref:`Chan and Steger` for more information. + Please look at :py:data:`splay` for explanations and examples on the format. epsI: desc: > @@ -198,6 +207,7 @@ epsI: Generally increasing the implicit coefficient results in a more stable solution procedure. Usually this value should be twice the explicit smoothing parameter. See Section 3 of :ref:`Chan and Steger` for more information. + Please look at :py:data:`splay` for explanations and examples on the format. theta: desc: > @@ -206,6 +216,7 @@ theta: A single theta value is used for both in-plane directions. Typical values are ~2.0 to ~4.0. See Section 3 of :ref:`Chan and Steger` for more information. + Please look at :py:data:`splay` for explanations and examples on the format. volCoef: desc: > @@ -214,26 +225,25 @@ volCoef: Larger values will result in a more uniform cell volume distribution. Alternatively, use more :py:data:`volSmoothIter` for stronger local smoothing. See Section 5 of :ref:`Chan and Steger` for more information. + Please look at :py:data:`splay` for explanations and examples on the format. volBlend: desc: > The global volume blending coefficient. This value will typically be very small, especially if you have widely varying cell sizes. Typical values are from ~0 to 0.001. + Please look at :py:data:`splay` for explanations and examples on the format. volSmoothIter: desc: > The number of point-Jacobi local volume smoothing iterations to perform at each level. More iterations will result in a more uniform cell volume distribution. + Please look at :py:data:`splay` for explanations and examples on the format. -volSmoothSchedule: +growthRatios: desc: > - Define a piecewise linear schedule for volume smoothing iterations. - If provided, this supersedes :py:data:`volSmoothIter`. - This option is usually used to limit the number of smoothing iterations early in the extrusion to maintain orthogonality near the wall and ramp up the number of smoothing iterations later in the extrusion to achieve a more uniform cell volume distribution in the farfield. - An example of a smoothing schedule is ``"volSmoothSchedule": [[0, 10], [0.4, 50], [1.0, 100]]``. - In this example, the number of smoothing iterations increases linearly from 10 to 50 over the first 40% of grid levels. - For the remaining levels, the number of smoothing iterations increases linearly from 50 to 100. + Manually specify the growth ratio(s). If not ``None``, this superseeds :py:data:`marchDist`. + Please look at :py:data:`splay` for explanations and examples on the format. KSPRelTol: desc: > diff --git a/examples/corner/runCorner.py b/examples/corner/runCorner.py index 2389d2f..bcac703 100644 --- a/examples/corner/runCorner.py +++ b/examples/corner/runCorner.py @@ -2,7 +2,9 @@ This example shows how to set up dictionaries to run multiple extrusions with pyHypMulti. We use pyHypMulti to extrude a 90 deg corner twice with different smoothing settings. """ + import os +import numpy as np from pyhyp import pyHypMulti baseDir = os.path.dirname(os.path.abspath(__file__)) @@ -44,12 +46,31 @@ "volSmoothIter": 100, } + +# helper function to interpolate values for each grid-level +def ls(start, stop, dtype=np.float64): + return np.linspace(start, stop, commonOptions["N"] - 1, dtype=dtype).tolist() + + # Now set up specific options options1 = {"outputFile": "corner1_hyp.cgns"} options2 = {"epsE": 4.0, "epsI": 8.0, "outputFile": "corner2_hyp.cgns"} +options3 = { + "splay": [[0.0, 0.5], [0.5, 0.0], [1.0, 0.5]], + "splayEdgeOrthogonality": [[0.0, 0.1], [0.5, 0.2], [1.0, 0.3]], + "splayCornerOrthogonality": [[0.0, 0.2], [0.5, 0.3], [1.0, 0.5]], + "outputFile": "corner3_hyp.cgns", +} +options4 = { + "splay": ls(0.5, 0.0), + "splayEdgeOrthogonality": ls(0.1, 0.3), + "splayCornerOrthogonality": ls(0.2, 0.5), + "outputFile": "corner4_hyp.cgns", +} + # Gather options in a list -options = [options1, options2] +options = [options1, options2, options3, options4] hyp = pyHypMulti(options=options, commonOptions=commonOptions) hyp.combineCGNS(combinedFile=volumeFile) diff --git a/examples/naca0012/naca0012_euler.py b/examples/naca0012/naca0012_euler.py index 9c58c5e..642b06e 100644 --- a/examples/naca0012/naca0012_euler.py +++ b/examples/naca0012/naca0012_euler.py @@ -2,6 +2,7 @@ This script uses the NACA 0012 airfoil equation to generate 2D Euler mesh. This mesh has a sharp trailing edge. """ + import os import numpy from pyhyp import pyHyp diff --git a/examples/naca0012/naca0012_rans.py b/examples/naca0012/naca0012_rans.py index fe42608..424ea84 100644 --- a/examples/naca0012/naca0012_rans.py +++ b/examples/naca0012/naca0012_rans.py @@ -2,49 +2,15 @@ This script uses the NACA 0012 airfoil equation to generate a 2D RANS mesh. This mesh has a blunt trailing edge. """ + import os import numpy from pyhyp import pyHyp +import numpy as np +import argparse baseDir = os.path.dirname(os.path.abspath(__file__)) surfaceFile = os.path.join(baseDir, "naca0012_rans.fmt") -volumeFile = os.path.join(baseDir, "naca0012_rans.cgns") - -alpha = numpy.linspace(0, 2 * numpy.pi, 273) -x = numpy.cos(alpha) * 0.5 + 0.5 -y = numpy.zeros_like(x) - -for i in range(len(x)): - if i < len(x) / 2: - y[i] = 0.6 * ( - 0.2969 * numpy.sqrt(x[i]) - 0.1260 * x[i] - 0.3516 * x[i] ** 2 + 0.2843 * x[i] ** 3 - 0.1015 * x[i] ** 4 - ) - else: - y[i] = -0.6 * ( - 0.2969 * numpy.sqrt(x[i]) - 0.1260 * x[i] - 0.3516 * x[i] ** 2 + 0.2843 * x[i] ** 3 - 0.1015 * x[i] ** 4 - ) - -# Since the TE is open we need to close it. Close it multiple linear segments. -delta_y = numpy.linspace(y[-1], y[0], 32, endpoint=True) -delta_y = delta_y[1:] - -x = numpy.append(x, numpy.ones_like(delta_y)) -y = numpy.append(y, delta_y) - -# Write the plot3d input file: -f = open(surfaceFile, "w") -f.write("1\n") -f.write("%d %d %d\n" % (len(x), 2, 1)) -for iDim in range(3): - for j in range(2): - for i in range(len(x)): - if iDim == 0: - f.write("%g\n" % x[i]) - elif iDim == 1: - f.write("%g\n" % y[i]) - else: - f.write("%g\n" % (float(j))) -f.close() options = { # --------------------------- @@ -80,6 +46,152 @@ } -hyp = pyHyp(options=options) -hyp.run() -hyp.writeCGNS(volumeFile) +def extrudeDefaultCase(): + """ + This is the default where most values are scalars + """ + + volumeFile = os.path.join(baseDir, "naca0012_rans.cgns") + + generateSurfaceFile(surfaceFile) + hyp = extrudeVolumeMesh(options, volumeFile) + + return hyp, volumeFile + + +def extrudeConstantLayersCase(): + """ + Here, the first and last layers are kept constant (growth-ratio == 1.0) + """ + + volumeFile = os.path.join(baseDir, "naca0012_rans_constant_layers.cgns") + + options.update( + { + "nConstantStart": 5, + "nConstantEnd": 5, + } + ) + + generateSurfaceFile(surfaceFile) + hyp = extrudeVolumeMesh(options, volumeFile) + + return hyp, volumeFile + + +def extrudeScheduleCase(): + """ + Some variables are 'scheduled' which means their value changes depending on + the extrusion layer. The values are specified on intervals which are + linearly interpolated by pyHyp. + """ + volumeFile = os.path.join(baseDir, "naca0012_rans_schedule.cgns") + + options.update( + { + "epsE": [[0.0, 1.0], [0.2, 2.0], [1.0, 5.0]], + "epsI": [[0.0, 2.0], [0.2, 2.0], [1.0, 10.0]], + "theta": [[0.0, 3.0], [0.2, 2.5], [1.0, 0.0]], + "volBlend": [[0.0, 0.0001], [1.0, 0.1]], + "volSmoothIter": [[0.0, 100], [1.0, 500]], + "volCoef": [[0.0, 0.25], [1.0, 0.5]], + "growthRatios": [[0.0, 1.05], [1.0, 1.1]], + "cornerAngle": [[0.0, 110.0], [1.0, 120.0]], + } + ) + + generateSurfaceFile(surfaceFile) + hyp = extrudeVolumeMesh(options, volumeFile) + + return hyp, volumeFile + + +def extrudeExplicitCase(): + """ + Some variables are set 'explicitly'. This means, a list of values that + correspond to each layer is provided. + """ + + def ls(start, stop, dtype=np.float64): + return np.linspace(start, stop, options["N"] - 1, dtype=dtype).tolist() + + options.update( + { + "epsE": ls(1.0, 5.0), + "epsI": ls(2.0, 10.0), + "theta": ls(3.0, 0.0), + "volBlend": ls(0.0001, 0.1), + "volSmoothIter": ls(100, 500, dtype=np.int32), + "volCoef": ls(0.25, 0.5), + "growthRatios": ls(1.05, 1.3), + "cornerAngle": ls(110.0, 120.0), + } + ) + + volumeFile = os.path.join(baseDir, "naca0012_rans_explicit.cgns") + + generateSurfaceFile(surfaceFile) + hyp = extrudeVolumeMesh(options, volumeFile) + return hyp, volumeFile + + +def generateSurfaceFile(surface_file): + alpha = numpy.linspace(0, 2 * numpy.pi, 273) + x = numpy.cos(alpha) * 0.5 + 0.5 + y = numpy.zeros_like(x) + + for i in range(len(x)): + if i < len(x) / 2: + y[i] = 0.6 * ( + 0.2969 * numpy.sqrt(x[i]) - 0.1260 * x[i] - 0.3516 * x[i] ** 2 + 0.2843 * x[i] ** 3 - 0.1015 * x[i] ** 4 + ) + else: + y[i] = -0.6 * ( + 0.2969 * numpy.sqrt(x[i]) - 0.1260 * x[i] - 0.3516 * x[i] ** 2 + 0.2843 * x[i] ** 3 - 0.1015 * x[i] ** 4 + ) + + # Since the TE is open we need to close it. Close it multiple linear segments. + deltaY = numpy.linspace(y[-1], y[0], 32, endpoint=True) + deltaY = deltaY[1:] + + x = numpy.append(x, numpy.ones_like(deltaY)) + y = numpy.append(y, deltaY) + + # Write the plot3d input file: + f = open(surface_file, "w") + f.write("1\n") + f.write("%d %d %d\n" % (len(x), 2, 1)) + for iDim in range(3): + for j in range(2): + for i in range(len(x)): + if iDim == 0: + f.write("%g\n" % x[i]) + elif iDim == 1: + f.write("%g\n" % y[i]) + else: + f.write("%g\n" % (float(j))) + f.close() + + +def extrudeVolumeMesh(options, volumeFile): + hyp = pyHyp(options=options) + hyp.run() + hyp.writeCGNS(volumeFile) + + return hyp + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Process some integers.") + choices = ["default", "constant", "schedule", "explicit"] + parser.add_argument("--case", choices=choices, default=choices[0]) + args = parser.parse_args() + + if args.case == "constant": + extrudeConstantLayersCase() + elif args.case == "schedule": + extrudeScheduleCase() + elif args.case == "explicit": + extrudeExplicitCase() + else: + extrudeDefaultCase() diff --git a/examples/simpleOCart/runSimpleOCart.py b/examples/simpleOCart/runSimpleOCart.py index 7ab32a9..ffb7b66 100644 --- a/examples/simpleOCart/runSimpleOCart.py +++ b/examples/simpleOCart/runSimpleOCart.py @@ -1,11 +1,9 @@ import os from pyhyp.utils import simpleOCart +import argparse baseDir = os.path.dirname(os.path.abspath(__file__)) -# Usually this would be a nearfield volume file -# We use the corner surface file for convenience -nearFile = os.path.join(baseDir, "../corner/corner.cgns") # Set the other inputs dh = 0.05 @@ -13,8 +11,40 @@ nExtra = 49 sym = "y" mgcycle = 3 -outFile = os.path.join(baseDir, "simpleOCart.cgns") userOptions = {"cMax": 5.0} -# Run simpleOCart -simpleOCart(nearFile, dh, hExtra, nExtra, sym, mgcycle, outFile, userOptions=userOptions) + +def extrudeDefaultCase(): + # Usually this would be a nearfield volume file + # We use the corner surface file for convenience + nearFile = os.path.join(baseDir, "../corner/corner.cgns") + + outFile = os.path.join(baseDir, "simpleOCart.cgns") + + # Run simpleOCart + simpleOCart(nearFile, dh, hExtra, nExtra, sym, mgcycle, outFile, userOptions=userOptions) + + return outFile, hExtra + + +def extrudeNoSurfaceMeshCase(): + outFile = os.path.join(baseDir, "simpleOCart_no_surface_mesh.cgns") + + xBounds = [[0, 0, 0], [2, 2, 2]] + + # Run simpleOCart + simpleOCart(None, dh, hExtra, nExtra, sym, mgcycle, outFile, userOptions=userOptions, xBounds=xBounds) + + return outFile, hExtra + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Process some integers.") + choices = ["default", "noSurfaceMesh"] + parser.add_argument("--case", choices=choices, default=choices[0]) + args = parser.parse_args() + + if args.case == "noSurfaceMesh": + extrudeNoSurfaceMeshCase() + else: + extrudeDefaultCase() diff --git a/pyhyp/pyHyp.py b/pyhyp/pyHyp.py index 51d1511..fcddab5 100644 --- a/pyhyp/pyHyp.py +++ b/pyhyp/pyHyp.py @@ -16,6 +16,7 @@ ------- v. 1.0 - Initial Class Creation (GKK, 2013) """ + # ============================================================================= # Imports # ============================================================================= @@ -25,16 +26,35 @@ from . import MExt from collections import OrderedDict from copy import deepcopy +from tabulate import tabulate from baseclasses import BaseSolver from baseclasses.utils import Error from cgnsutilities.cgnsutilities import readGrid, combineGrids +class pyHypWarning(object): + """ + Format a warning message + """ + + def __init__(self, message): + msg = "\n+" + "-" * 78 + "+" + "\n" + "| pyHyp Warning: " + i = 19 + for word in message.split(): + if len(word) + i + 1 > 78: # Finish line and start new one + msg += " " * (78 - i) + "|\n| " + word + " " + i = 1 + len(word) + 1 + else: + msg += word + " " + i += len(word) + 1 + msg += " " * (78 - i) + "|\n" + "+" + "-" * 78 + "+" + "\n" + print(msg) + + # ============================================================================= # pyHypMulti class # ============================================================================= class pyHypMulti(object): - """ This is class can be used to run multiple pyHyp cases at once. """ @@ -158,7 +178,7 @@ def __init__(self, comm=None, options=None, commonOptions=None, debug=False, ski self.results = { "name": list(optionsDict.keys()), "outputFile": [0] * self.numGrids, - "gridRatio": [0] * self.numGrids, + "gridRatio": [""] * self.numGrids, "minQualityOverall": [0] * self.numGrids, "minVolumeOverall": [0] * self.numGrids, } @@ -229,11 +249,17 @@ def __init__(self, comm=None, options=None, commonOptions=None, debug=False, ski hypGrid.writeOutput() # Save results + nDecimals = 4 + self.results["name"][index] = optionName self.results["outputFile"][index] = hypGrid.options["outputfile"] - self.results["gridRatio"][index] = float(hypGrid.hyp.hypdata.gridratio) - self.results["minQualityOverall"][index] = float(hypGrid.hyp.hypdata.minqualityoverall) - self.results["minVolumeOverall"][index] = float(hypGrid.hyp.hypdata.minvolumeoverall) + self.results["minQualityOverall"][index] = roundSig( + float(hypGrid.hyp.hypdata.minqualityoverall), nDecimals + ) + self.results["minVolumeOverall"][index] = roundSig( + float(hypGrid.hyp.hypdata.minvolumeoverall), nDecimals + ) + self.results["gridRatio"][index] = hypGrid.getGrowthRatioString(nDecimals=nDecimals) # Delete object to free memory del hypGrid @@ -262,54 +288,7 @@ def writeLog(self): print("=" * 40) print("") - print("+----+-----------------------+-----------+-------------------+------------------+") - print("| ID | grid name | gridRatio | minQualityOverall | minVolumeOverall |") - print("+----+-----------------------+-----------+-------------------+------------------+") - - for index in range(self.numGrids): - # Crop filename - try: - filename = self.results["name"][index][:21] - except Exception: # noqa: E722 - filename = self.results["name"][index] - - # Get remaining data - gridRatio = self.results["gridRatio"][index] - minQualityOverall = self.results["minQualityOverall"][index] - minVolumeOverall = self.results["minVolumeOverall"][index] - - # Format string that will be printed - log_string1 = "| %02d " % index + "|" + " {0: <21} ".format(filename) + "|" - if type(gridRatio) is str: - log_string2 = ( - " {0: <9} ".format(gridRatio) - + "|" - + " " * 4 - + "{0: <10}".format(minQualityOverall) - + " " * 5 - + "|" - + " " * 4 - + "{0: <10}".format(minVolumeOverall) - + " " * 4 - + "|" - ) - else: - log_string2 = ( - " {:9.7f} ".format(gridRatio) - + "|" - + " " * 4 - + "{:9.8f}".format(minQualityOverall) - + " " * 5 - + "|" - + " " * 4 - + "{:8.4e}".format(minVolumeOverall) - + " " * 4 - + "|" - ) - - print(log_string1 + log_string2) - - print("+----+-----------------------+-----------+-------------------+------------------+") + print(tabulate(self.results, headers="keys")) print("") print("") @@ -405,8 +384,19 @@ def __init__(self, comm=None, options=None, debug=False): if options is None: raise Error("The options = keyword argument is *NOT* optional. " "It must always be provided") + # Deprecated options + deprecatedOptions = self._getDeprecatedOptions() + # Initialize the inherited BaseSolver - super().__init__(name, category, defaultOptions=defOpts, options=options, comm=comm, informs=informs) + super().__init__( + name, + category, + defaultOptions=defOpts, + options=options, + deprecatedOptions=deprecatedOptions, + comm=comm, + informs=informs, + ) # Import and set the hyp module curDir = os.path.basename(os.path.dirname(os.path.realpath(__file__))) @@ -580,10 +570,10 @@ def _getDefaultOptions(): "nTruncate": [int, -1], "marchDist": [float, 50.0], "nodeTol": [float, 1e-8], - "splay": [float, 0.25], - "splayEdgeOrthogonality": [float, 0.1], - "splayCornerOrthogonality": [float, 0.2], - "cornerAngle": [float, 60.0], + "splay": [(list, float), 0.25], + "splayEdgeOrthogonality": [(list, float), 0.1], + "splayCornerOrthogonality": [(list, float), 0.2], + "cornerAngle": [(list, float), 60.0], "coarsen": [int, 1], # --------------------------- # Elliptic Parameters @@ -601,16 +591,16 @@ def _getDefaultOptions(): "slExp": [float, 0.15], "ps0": [float, -1.0], "pGridRatio": [float, -1.0], + "growthRatios": [(list, type(None)), None], # ---------------------------------------- # Smoothing parameters (Hyperbolic only) # ---------------------------------------- - "epsE": [float, 1.0], - "epsI": [float, 2.0], - "theta": [float, 3.0], - "volCoef": [float, 0.25], - "volBlend": [float, 0.0001], - "volSmoothIter": [int, 100], - "volSmoothSchedule": [(list, type(None)), None], + "epsE": [(list, float), 1.0], + "epsI": [(list, float), 2.0], + "theta": [(list, float), 3.0], + "volCoef": [(list, float), 0.25], + "volBlend": [(list, float), 0.0001], + "volSmoothIter": [(list, int), 100], # ------------------------------- # Solution Parameters (Common) # ------------------------------- @@ -626,6 +616,14 @@ def _getDefaultOptions(): } return defOpts + @staticmethod + def _getDeprecatedOptions(): + deprecatedOptions = { + "volSmoothSchedule": "Please use 'volSmoothIter' for the same functionality", + } + + return deprecatedOptions + def run(self): """ Run given using the options given @@ -720,22 +718,8 @@ def _setOptions(self): self.hyp.hypinput.nconstantend = self.getOption("nConstantEnd") self.hyp.hypinput.ntruncate = self.getOption("nTruncate") self.hyp.hypinput.nopointreduce = self.getOption("noPointReduce") - self.hyp.hypinput.s0 = self.getOption("s0") - self.hyp.hypinput.marchdist = self.getOption("marchdist") - self.hyp.hypinput.ps0 = self.getOption("ps0") - self.hyp.hypinput.pgridratio = self.getOption("pGridRatio") self.hyp.hypinput.slexp = self.getOption("slExp") - self.hyp.hypinput.epse = self.getOption("epsE") - self.hyp.hypinput.epsi = self.getOption("epsI") - self.hyp.hypinput.theta = self.getOption("theta") - self.hyp.hypinput.volcoef = self.getOption("volCoef") - self.hyp.hypinput.volblend = self.getOption("volBlend") self.hyp.hypinput.cmax = self.getOption("cMax") - self.hyp.hypinput.volsmoothiter = self.getOption("volSmoothIter") - self.hyp.hypinput.splay = self.getOption("splay") - self.hyp.hypinput.splayedgeorthogonality = self.getOption("splayEdgeOrthogonality") - self.hyp.hypinput.splaycornerorthogonality = self.getOption("splayCornerOrthogonality") - self.hyp.hypinput.cornerangle = self.getOption("cornerangle") * numpy.pi / 180 self.hyp.hypinput.coarsen = self.getOption("coarsen") self.hyp.hypinput.kspreltol = self.getOption("kspRelTol") self.hyp.hypinput.kspmaxits = self.getOption("kspMaxIts") @@ -758,14 +742,296 @@ def _setOptions(self): f = self.getOption("sourceStrengthFile") self.hyp.hypinput.sourcestrengthfile = self._expandString(f) - sch = self.getOption("volSmoothSchedule") - if sch is not None: - sch = numpy.array(sch, "d") - # Make sure its normalized - low = sch[0, 0] - high = sch[-1, 0] - sch[:, 0] = (sch[:, 0] - low) / (high - low) - self.hyp.hypinput.volsmoothschedule = sch + # options that might be unique per layer + self.hyp.hypinput.volsmoothiter = self._expandPerLayerOption("volSmoothIter", dtype=numpy.int32) + self.hyp.hypinput.volblend = self._expandPerLayerOption("volBlend") + self.hyp.hypinput.volcoef = self._expandPerLayerOption("volCoef") + self.hyp.hypinput.epse = self._expandPerLayerOption("epsE") + self.hyp.hypinput.epsi = self._expandPerLayerOption("epsI") + self.hyp.hypinput.theta = self._expandPerLayerOption("theta") + self.hyp.hypinput.splay = self._expandPerLayerOption("splay") + self.hyp.hypinput.splayedgeorthogonality = self._expandPerLayerOption("splayEdgeOrthogonality") + self.hyp.hypinput.splaycornerorthogonality = self._expandPerLayerOption("splayCornerOrthogonality") + self.hyp.hypinput.cornerangle = self._expandPerLayerOption("cornerangle") * numpy.pi / 180 + + # determine marching parameters + fullDeltaS, self.marchDistance, self.growthRatios = self._determineMarchingParameters() + self.hyp.hypinput.fulldeltas = fullDeltaS + self.hyp.hypinput.marchdist = self.marchDistance + self._printMarchingParameters() + + # figure out pseudo grid ratio parameters + pGridRatio, ps0 = self._configurePseudoGridParameters() + self.hyp.hypinput.pgridratio = pGridRatio + self.hyp.hypinput.ps0 = ps0 + + def _determineMarchingParameters(self): + """ + Figure out what marching parameters to use. This logic is needed + because there are multiple ways to define those parameters. + + Returns + ------- + fullDeltaS : list[float] + The ideal height for each layer. + marchDist : float + The marching distance used. + growthRatios : list[float] + The growth ratio for each layer. + """ + # if the user specified an explicit growth-ratio, use this + optionsGrowthRatios = self.getOption("growthRatios") + if optionsGrowthRatios is not None: + growthRatios = self._expandPerLayerOption("growthRatios") + + if self.comm.Get_rank() == 0: + pyHypWarning( + "The option `growthRatios` has been specified. This takes precedence over `marchDist`, `nConstantStart` and `nConstantEnd`." + ) + else: + # no growth ratio was provided -> compute it + growthRatios = self._computeGrowthRatio() + + # finally compute each layers deltaS and set it in fortran + fullDeltaS = self._computeDeltaS(growthRatios) + + # figure out what marching distance to use + if optionsGrowthRatios is not None: + marchDist = numpy.sum(fullDeltaS) + else: + marchDist = self.getOption("marchDist") + + return fullDeltaS, marchDist, growthRatios + + def _printMarchingParameters(self): + """ + Print the marching parameters used. + """ + if not self.comm.Get_rank() == 0: + return + + nDecimals = 3 + growthRatioString = self.getGrowthRatioString(nDecimals) + + print("#--------------------#") + print(f"Grid Ratio: {growthRatioString}") + print(f"March Dist: {roundSig(self.marchDistance, nDecimals)}") + + def getGrowthRatioString(self, nDecimals=3): + """ + Returns a rounded string with the current growth ratio. If the growth + ratio is constant, a single value is return. Otherwise a range is + returned. + + Parameters + ---------- + nDecimals : int + The number of significant figures to round to. + + Returns + ------- + growthRatioString : str + The string holding the rounded growth ratio. + """ + minGrowthRatio = roundSig(numpy.min(self.growthRatios[self.growthRatios > 1]), nDecimals) + maxGrowthRatio = roundSig(numpy.max(self.growthRatios[self.growthRatios > 1]), nDecimals) + + if maxGrowthRatio - minGrowthRatio <= 0: + growthRatioString = f"{minGrowthRatio}" + else: + growthRatioString = f"{minGrowthRatio} - {maxGrowthRatio}" + + return growthRatioString + + def _configurePseudoGridParameters(self): + """ + Figures out if the user set pseudo grid parameters. If this is the + case, error checking makes sure they are reasonable. If the user did not + set it, an automatic value is computed. + + Returns + ------- + pGridRatio: float + Growth ratio for the pseudo grid. + ps0 : float + First layer height for the pseudo grid. + """ + + pGridRatio = self.getOption("pGridRatio") + ps0 = self.getOption("ps0") + s0 = self.getOption("s0") + + minGrowthRatio = numpy.min(self.growthRatios[self.growthRatios > 1]) + if pGridRatio == -1: + pGridRatio = minGrowthRatio + else: + if pGridRatio > minGrowthRatio: + raise Error(f"The `pGridRatio` option has to be lower than the lowest grid ratio ({minGrowthRatio})") + + # figure out initial pseudo grid initial offwall spacing + if ps0 <= 0: + ps0 = s0 / 2 + else: + if ps0 > s0: + raise Error(f"The `ps0` option has to be lower than the off wall spacing s0: ({s0})") + + return pGridRatio, ps0 + + def _computeDeltaS(self, growthRatios): + """ + Computes the ideal height for each layer. Due to smoothing, this exact + value will not be achieved in practice. + + Parameters + ---------- + growthRatios : list[float] + The growth ratio for each layer. + + Returns + ------- + fullDeltaS : list[float] + The ideal height for each layer. + """ + N = self.getOption("N") + s0 = self.getOption("s0") + fullDeltaS = numpy.zeros(N) + + # compute the delta S + fullDeltaS[1] = s0 + for n in range(2, N): + fullDeltaS[n] = fullDeltaS[n - 1] * growthRatios[n] + + return fullDeltaS + + def _computeGrowthRatio(self): + """ + Computes the growth ratio for each layer when no explicit value is given. + + Returns + ------- + growthRatios : list[float] + The growth ratio for each layer. + """ + # initial ratio and r is the grid ratio. + # function 'f' is S - s0*(1-r^n)/(1-r) where S is total length, s0 is + + nStart = self.getOption("nConstantStart") + nEnd = self.getOption("nConstantEnd") + S = self.getOption("marchDist") + N = self.getOption("N") + s0 = self.getOption("s0") + + def func(r): + func = nStart * s0 + + curSize = s0 + + # Next we will have M = N - nStart - nEnd layers of exponential growth. + for _j in range(N - 1 - nStart - nEnd): + curSize = curSize * r + func = func + curSize + + # Last stretch + curSize = curSize * r + + # Now add the last nEnd layers of constant size + func = func + nEnd * curSize + + # Finally the actual function is S - func + func = S - func + + return func + + # Do a bisection search + # Max and min bounds...root must be in here... + a = 1.0 + 1e-8 + b = 4.0 + ratio = -1 + + fa = func(a) + for _i in range(100): + c = (a + b) / 2 + f = func(c) + if abs(f) < 1e-10: # Converged + ratio = c + break + + if f * fa > 0: + a = c + else: + b = c + + # we need to return an array of growth-ratios + growthRatios = numpy.ones(N) + growthRatios[nStart + 1 : N - (nEnd - 1)] = ratio + + return growthRatios + + def _expandPerLayerOption(self, name, dtype=numpy.float64): + """ + Expands the option in question into an array that has a unique value + per layer. + + The option may come in different formats: + (1) If it is a scalar, the same value is used throughout all of the grid. + (2) If it is a 1D list, it must have a length of N - 1. Each entry is + used for the corresponding extrusion layer. E.g. [0.2, 0.3, 0.4] would + result in a grid with 3 extrusions where the first layer uses 0.2, the + second layer uses 0.3 and the last one uses 0.4. + (3) If it is a 2D list, the values are interpolated linearly. E.g. + [[0.0, 0.2], [0.5, 0.4], [1.0, 0.0]] would result in a linear increase + from 0.2 to 0.4 at half of the total N. Then it would linearly decrease + until it reaches 0.0 in the last extrusion. + + Parameters + ---------- + name : str + Name of the option in question + dtype : numpy.dtype + The datatype of the option in question. + + Returns + ------- + out : list[float] + The value of the option in question for each layer. + """ + inp = self.getOption(name) + N = self.getOption("N") + out = numpy.zeros(N, dtype=dtype) + + # if it is a scalar, just repeat it + if numpy.isscalar(inp): + out[:] = inp + return out + + # convert to numpy array as it is easier to work with + inp = numpy.array(inp) + + # if it is a 1d list, use it as is + if inp.ndim == 1: + # check the length, which should be N - 1 + if len(inp) != N - 1: + raise Error(f"If the `{name}` option is given as a 1D list, its length must be `N` - 1.") + + # not setting the first value because this is the first layer, + # which is given by the surface mesh + out[1:] = inp + + return out + + # if it is a 2d list, linearly interpolate it + # first make sure it is normalized + low = inp[0, 0] + high = inp[-1, 0] + inp[:, 0] = (inp[:, 0] - low) / (high - low) + + # then interpolate + x = numpy.arange(1, N) + xp = inp[:, 0] * (N - 2) + 1 + fp = inp[:, 1] + out[1:] = numpy.interp(x, xp, fp) + + return out def _expandString(self, s): """Expand a supplied string 's' to be of the constants.maxstring @@ -778,6 +1044,22 @@ def __del__(self): """ self.hyp.releasememory() + def getUsedMarchDistance(self): + """ + Returns the marching distance used. + + Returns + ------- + marchDist : float + The marching distance used. + """ + if not self.gridGenerated: + raise Error("Cannot return the used marching distance before extruding the grid.") + + marchDist = self.hyp.hypinput.marchdist + + return marchDist + def writeLayer(self, fileName, layer=1, meshType="plot3d", partitions=True): """ Write a single mesh layer out to a file for visualization or for other purposes. @@ -887,3 +1169,27 @@ def generateOutputName(inputFile, outputType): # Return the generated name return outputFile + + +def roundSig(x, p): + """ + Rounds the number to the significant figures requested. + This has been taken from here: https://stackoverflow.com/a/59888924 + + Parameters + ---------- + x : number + The number to be rounded. Also works with arrays + p : int + The amount of significant figures to round to. + + Returns + ------- + rounded : number + A scalar or list rounded to the significant figures requested + """ + x = numpy.asarray(x) + xPositive = numpy.where(numpy.isfinite(x) & (x != 0), numpy.abs(x), 10 ** (p - 1)) + mags = 10 ** (p - 1 - numpy.floor(numpy.log10(xPositive))) + rounded = numpy.round(x * mags) / mags + return rounded diff --git a/pyhyp/utils.py b/pyhyp/utils.py index 71973af..ca1a7f2 100644 --- a/pyhyp/utils.py +++ b/pyhyp/utils.py @@ -67,6 +67,11 @@ def simpleOCart(inputGrid, dh, hExtra, nExtra, sym, mgcycle, outFile, userOption # Read the nearfield file input_filename = inputGrid inputGrid = readGrid(input_filename) + # the input Grid can be None, in this case, we must have xBounds + elif inputGrid is None: + # make sure we have xbounds + if xBounds is None: + raise Error("If the inputGrid is None, xBounds must be provided") # if the input grid is not provided as a filename, it must be a Grid instance elif type(inputGrid) != Grid: # if not, raise an error diff --git a/setup.py b/setup.py index d142bf8..4f11f26 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ "mpi4py>=3.0", "mdolab-baseclasses>=1.3", "cgnsutilities>=2.5", + "tabulate", ], extras_require={ "docs": docs_require, diff --git a/src/3D/3D_code.F90 b/src/3D/3D_code.F90 index 6148609..ca096d4 100644 --- a/src/3D/3D_code.F90 +++ b/src/3D/3D_code.F90 @@ -15,46 +15,11 @@ subroutine runHyperbolic integer(kind=intType) :: i, ierr, idim, l, reason real(kind=realType) :: tmp - call calcGridRatio(N, nConstantStart, nConstantEnd, s0, marchDist, gridRatio) - - ! Defaults/Error checking - if (pGridRatio <= 0) then - pGridRatio = gridRatio - end if - - if (pGridRatio > gridRatio) then - if (myid == 0) then - print *, 'Erorr: The supplied pseudo grid ratio is too large. It must be less than', gridRatio - end if - call mpi_barrier(hyp_comm_world, ierr) - stop - end if - ! Set initial pseudo and real spacings - if (ps0 <= zero) then - deltaS = s0 / two - else - deltaS = ps0 - end if - - if (deltaS > s0) then - if (myid == 0) then - print *, 'Erorr: The supplied pseudo grid s0 is too large. It must be less than', s0 - end if - call mpi_barrier(hyp_comm_world, ierr) - stop - end if + deltaS = ps0 ! Write header (defined in 3D_utilities.f90) if (myid == 0) then - write (*, "(a)", advance="no") '#--------------------#' - print "(1x)" - write (*, "(a)", advance="no") "Grid Ratio:" - write (*, "(f8.4,1x)", advance="no") gridRatio - print "(1x)" - write (*, "(a)", advance="no") '#--------------------#' - print "(1x)" - call writeHeader end if @@ -257,29 +222,10 @@ subroutine volumeSmooth ! Working Parameters real(kind=realType) :: factor, nNeighbors, vSum, frac, low, high, frac2 - integer(kind=intType) :: i, iSize, iter, ierr, ii, jj, nIter + integer(kind=intType) :: i, iSize, iter, ierr, ii, jj real(kind=realType), allocatable, dimension(:) :: Vtmp logical :: search - if (allocated(volSmoothSchedule)) then - ! We need to determine how many smoothing iterations to do by - ! interpolating the volumeSmoothing Schedule - frac = (marchIter - one) / (N - 1) - - ! Just do a linear search for the bin: - do i = 1, size(volSmoothSchedule, 1) - 1 - if (frac >= volSmoothSchedule(i, 1) .and. frac <= volSmoothSchedule(i + 1, 1)) then - frac2 = (frac - volSmoothSchedule(i, 1)) / & - (volSmoothSchedule(i + 1, 1) - volSmoothSchedule(i, 1)) - low = volSmoothSchedule(i, 2) - high = volSmoothSchedule(i + 1, 2) - nIter = int(low + frac2 * (high - low)) - end if - end do - else - nIter = volSmoothIter - end if - ! Do a Jacobi volume smooth call VecGhostGetLocalForm(Volume, VolumeLocal, ierr) call EChk(ierr, __FILE__, __LINE__) @@ -289,7 +235,7 @@ subroutine volumeSmooth call VecGetArrayF90(VolumeLocal, Vptr, ierr) call EChk(ierr, __FILE__, __LINE__) - do iter = 1, nIter + do iter = 1, volSmoothIter(marchIter) ! Copy vptr to vtmp do i = 1, isize @@ -306,7 +252,7 @@ subroutine volumeSmooth vSum = vSum + Vtmp(lnPtr(1 + ii, i)) end do - vptr(i) = (one - volCoef) * Vtmp(i) + volCoef / nNeighbors * vSum + vptr(i) = (one - volCoef(marchIter)) * Vtmp(i) + volCoef(marchIter) / nNeighbors * vSum end do @@ -324,7 +270,7 @@ subroutine volumeSmooth call EChk(ierr, __FILE__, __LINE__) ! Now we do a global volume smooth - factor = half * (one + (one - volBlend)**(marchIter - 2)) + factor = half * (one + (one - volBlend(marchIter))**(marchIter - 2)) call VecGetArrayF90(Volume, Vptr, ierr) call EChk(ierr, __FILE__, __LINE__) @@ -619,9 +565,9 @@ subroutine calcResidual xjm1_m1 = xxm1(3 * jm1 - 2:3 * jm1) ! Get the 4th point based on the BC Type: - call getBC(BCType(1, i), bcVal(1, :, i), .True., splayEdgeOrthogonality, & + call getBC(BCType(1, i), bcVal(1, :, i), .True., splayEdgeOrthogonality(marchIter), & xx0, xjp1, xkp1, xjm1, xkm1) - call getBC(BCType(1, i), bcVal(1, :, i), .True., splayEdgeOrthogonality, & + call getBC(BCType(1, i), bcVal(1, :, i), .True., splayEdgeOrthogonality(marchIter), & xx0_m1, xjp1_m1, xkp1_m1, xjm1_m1, xkm1_m1) else if (topoType(i) == topoCorner) then @@ -629,16 +575,16 @@ subroutine calcResidual ! Get the 3rd and 4th points based on the BCTypes. call getBC(BCType(1, i), bcVal(1, :, i), .False., & - splayCornerOrthogonality, xx0, xkp1, xjp1, xkm1, xjm1) + splayCornerOrthogonality(marchIter), xx0, xkp1, xjp1, xkm1, xjm1) call getBC(BCType(2, i), bcVal(2, :, i), .False., & - splayCornerOrthogonality, xx0, xjp1, xkp1, xjm1, xkm1) + splayCornerOrthogonality(marchIter), xx0, xjp1, xkp1, xjm1, xkm1) call getBC(BCType(1, i), bcVal(1, :, i), .False., & - splayCornerOrthogonality, xx0_m1, xkp1_m1, xjp1_m1, xkm1_m1, xjm1_m1) + splayCornerOrthogonality(marchIter), xx0_m1, xkp1_m1, xjp1_m1, xkm1_m1, xjm1_m1) call getBC(BCType(2, i), bcVal(2, :, i), .False., & - splayCornerOrthogonality, xx0_m1, xjp1_m1, xkp1_m1, xjm1_m1, xkm1_m1) + splayCornerOrthogonality(marchIter), xx0_m1, xjp1_m1, xkp1_m1, xjm1_m1, xkm1_m1) end if ! Compute centered difference with respect to ksi and eta. This is the @@ -759,7 +705,7 @@ subroutine calcResidual maxAlpha = max(maxAlpha, alpha) ! Check maximum corner angle - if (maxAlpha > pi - cornerAngle) then + if (maxAlpha > pi - cornerAngle(marchIter)) then ! Flag this node as having to be averaged: averageNode = .True. !print *,xx0 @@ -840,7 +786,7 @@ subroutine calcResidual ! ------------------------------------ ! Final explict dissipation (Equation 6.1 and 6.2) ! ------------------------------------ - De = epsE * (Rksi * N_ksi * r_ksi_ksi + Reta * N_eta * r_eta_eta) + De = epsE(marchIter) * (Rksi * N_ksi * r_ksi_ksi + Reta * N_eta * r_eta_eta) if (marchIter == 2) then De = zero @@ -854,32 +800,32 @@ subroutine calcResidual do m = 0, MM - 1 thetam = 2 * pi * m / MM ! For the 'fm' point in ksi - call addBlock(ii, gnPtr(2 + m, i), (one + theta) * PinvQ1 * (two / MM) * cos(thetam)) + call addBlock(ii, gnPtr(2 + m, i), (one + theta(marchIter)) * PinvQ1 * (two / MM) * cos(thetam)) ! For the 'f0' point in ksi - call addBlock(ii, ii, -(one + theta) * PInvQ1 * (two / MM) * cos(thetam)) + call addBlock(ii, ii, -(one + theta(marchIter)) * PInvQ1 * (two / MM) * cos(thetam)) ! For the 'fm' point in eta - call addBlock(ii, gnPtr(2 + m, i), (one + theta) * PinvQ2 * (two / MM) * sin(thetam)) + call addBlock(ii, gnPtr(2 + m, i), (one + theta(marchIter)) * PinvQ2 * (two / MM) * sin(thetam)) ! For the 'f0' point in ksi - call addBlock(ii, ii, -(one + theta) * PInvQ2 * (two / MM) * sin(thetam)) + call addBlock(ii, ii, -(one + theta(marchIter)) * PInvQ2 * (two / MM) * sin(thetam)) ! Now we have the second derivative values to do in ksi-ksi ! For the 'fm' point in ksi-ksi coef = (two / MM) * (four * cos(thetam)**2 - one) - call addBlock(ii, gnPtr(2 + m, i), -eye * epsI * coef) + call addBlock(ii, gnPtr(2 + m, i), -eye * epsI(marchIter) * coef) ! For the 'f0' point in ksi-ksi - call addBlock(ii, ii, eye * epsI * coef) + call addBlock(ii, ii, eye * epsI(marchIter) * coef) ! For the 'fm' point in eta-eta coef = (two / MM) * (four * sin(thetam)**2 - one) - call addBlock(ii, gnPtr(2 + m, i), -eye * epsI * coef) + call addBlock(ii, gnPtr(2 + m, i), -eye * epsI(marchIter) * coef) ! For the 'f0' point in eta-eta - call addBlock(ii, ii, eye * epsI * coef) + call addBlock(ii, ii, eye * epsI(marchIter) * coef) end do ! Finally we need an eye on the diagonal @@ -893,11 +839,11 @@ subroutine calcResidual if (.not. averageNode) then ! First generate the 5 blocks that we will eventually have to set: - blk0 = (eye + four * epsI * eye) ! Center - blk1 = ((one + theta) * half * PinvQ1 - epsI * eye) ! Right (jp1) - blk2 = ((one + theta) * half * PinvQ2 - epsI * eye) ! Top (kp1) - blk3 = (-(one + theta) * half * PinvQ1 - epsI * eye) ! Left (jm1) - blk4 = (-(one + theta) * half * PinvQ2 - epsI * eye) ! Bottom (km1) + blk0 = (eye + four * epsI(marchIter) * eye) ! Center + blk1 = ((one + theta(marchIter)) * half * PinvQ1 - epsI(marchIter) * eye) ! Right (jp1) + blk2 = ((one + theta(marchIter)) * half * PinvQ2 - epsI(marchIter) * eye) ! Top (kp1) + blk3 = (-(one + theta(marchIter)) * half * PinvQ1 - epsI(marchIter) * eye) ! Left (jm1) + blk4 = (-(one + theta(marchIter)) * half * PinvQ2 - epsI(marchIter) * eye) ! Bottom (km1) else blk0 = eye blk1 = -fourth * eye @@ -1063,7 +1009,7 @@ subroutine updateBCs xjm1 = xx(3 * jm1 - 2:3 * jm1) ! Get the 4th point based on the BC Type: - call getBC(BCType(1, i), bcVal(1, :, i), .True., splayEdgeOrthogonality, & + call getBC(BCType(1, i), bcVal(1, :, i), .True., splayEdgeOrthogonality(marchIter), & xx0, xjp1, xkp1, xjm1, xkm1) xx(3 * i - 2:3 * i) = xx0 @@ -1078,10 +1024,10 @@ subroutine updateBCs ! Get the 3rd and 4th points based on the BCTypes call getBC(BCType(1, i), bcVal(1, :, i), .False., & - splayCornerOrthogonality, xx0, xkp1, xjp1, xkm1, xjm1) + splayCornerOrthogonality(marchIter), xx0, xkp1, xjp1, xkm1, xjm1) call getBC(BCType(2, i), bcVal(2, :, i), .False., & - splayCornerOrthogonality, xx0, xjp1, xkp1, xjm1, xkm1) + splayCornerOrthogonality(marchIter), xx0, xjp1, xkp1, xjm1, xkm1) ! BC may update our node xx(3 * i - 2:3 * i) = xx0 @@ -1093,7 +1039,7 @@ subroutine updateBCs ! Other nodes are not significant. call getBC(BCType(1, i), bcVal(1, :, i), .False., & - splayCornerOrthogonality, xx0, xkp1, xjp1, xkm1, xjm1) + splayCornerOrthogonality(marchIter), xx0, xkp1, xjp1, xkm1, xjm1) ! BC may update our node xx(3 * i - 2:3 * i) = xx0 diff --git a/src/3D/3D_utilities.F90 b/src/3D/3D_utilities.F90 index fddf75d..79c0a99 100644 --- a/src/3D/3D_utilities.F90 +++ b/src/3D/3D_utilities.F90 @@ -30,130 +30,6 @@ subroutine computeStretch(L) end subroutine computeStretch -subroutine calcGridRatio(N, nStart, nEnd, s0, S, ratio) - !***DESCRIPTION - ! - ! Written by Gaetan Kenway - ! - ! Abstract: calcGridRatio() calculates the exponential - ! distribution Turns out we need to solve a transendental - ! equation here. We will do this with a bisection search - ! - ! Parameters - ! ---------- - ! N : integer - ! The number of nodes in sequence - ! nStart : integer - ! The number of intervals with constant spacing - ! 's0' at the beginning - ! nEnd : integer - ! The number of constant sized intervals at the end. - ! The actual spacing will be determined. - ! s0 : real - ! The initial grid spacing - ! S : real - ! The total integrated length - ! - ! Returns - ! ------- - ! ratio : real - ! The computed grid ratio that satifies all the inputs. - use hypInput, only: fullDeltas - use precision - - implicit none - - ! Input Parameters - integer(kind=intType), intent(in) :: N, nStart, nEnd - real(kind=realType), intent(in) :: s0, S - - ! Output Parameters - real(kind=realType), intent(out) :: ratio - - ! Working Parameters - integer(kind=intType) :: i, j - real(kind=realType) :: r, a, b, c, f, fa, fb, curSize - - ! function 'f' is S - s0*(1-r^n)/(1-r) where S is total length, s0 is - ! initial ratio and r is the grid ratio. - - ! Do a bisection search - ! Max and min bounds...root must be in here... - a = one + 1e-8 - b = four - - fa = func(a) - fb = func(b) - do i = 1, 100 - c = half * (a + b) - f = func(c) - if (abs(f) < 1e-10) then ! Converged - exit - end if - - if (f * fa > 0) then - a = c - else - b = c - end if - end do - - ! Finally set the ratio variable to r - ratio = c - - ! And we precompute all stretches: - if (.not. allocated(fullDeltaS)) then - allocate (fullDeltaS(2:N)) - end if - - curSize = s0 - do j = 1, nStart - fullDeltaS(j + 1) = curSize - end do - - ! Next we have N - nStart - nEnd layers of exponential - do j = 1, N - 1 - nStart - nEnd - curSize = curSize * ratio - fullDeltaS(nStart + j + 1) = curSize - end do - - ! Finally we have the last nEnd constant layers - curSize = curSize * ratio - do j = 1, nEnd - fullDeltaS(N - nEnd + j) = curSize - end do - -contains - function func(r) - - ! Evaluate the function we want to solve for: - real(kind=realType) :: r, func, curSize - integer(kind=intType) :: j - - ! We will have nStart layers at the beginning. - func = nStart * s0 - curSize = s0 - - ! Next we will have M = N - nStart - nEnd layers of exponential - ! growth. - do j = 1, N - 1 - nStart - nEnd - curSize = curSize * r - func = func + curSize - end do - - ! Last stretch - curSize = curSize * r - - ! Now add the last nEnd layers of constant size - func = func + nEnd * curSize - - ! Finally the actual functio is S - func - func = S - func - - end function func - -end subroutine calcGridRatio - subroutine three_by_three_inverse(Jac, Jinv) !***DESCRIPTION ! @@ -1026,7 +902,7 @@ end subroutine computeCornerAngle ! real(kind=realType), dimension(3) :: p4_extrap, p4_splay, p3_extrap, p3_splay ! real(kind=realType) :: blend, dist -! blend = splayCornerOrthogonality +! blend = splayCornerOrthogonality(marchIter) ! p0_new = p0 diff --git a/src/3D/boundaryConditions.F90 b/src/3D/boundaryConditions.F90 index 1d8e05d..be83fca 100644 --- a/src/3D/boundaryConditions.F90 +++ b/src/3D/boundaryConditions.F90 @@ -28,6 +28,7 @@ subroutine getBC(bcType, BCVal, isEdge, blend, p0, p1, p2, p3, p4) ! use hypInput + use hypData, only: marchIter implicit none ! Input/Output @@ -45,7 +46,7 @@ subroutine getBC(bcType, BCVal, isEdge, blend, p0, p1, p2, p3, p4) case (BCSplay) ! Regular extrapolation without orthogonality - p4_extrap = (2 + splay) * p0 - (1 + splay) * p2 + p4_extrap = (2 + splay(marchIter)) * p0 - (1 + splay(marchIter)) * p2 ! Distance between p2 and p0 dist = norm2(p2 - p0) @@ -68,7 +69,7 @@ subroutine getBC(bcType, BCVal, isEdge, blend, p0, p1, p2, p3, p4) call cross_prod(v1, normal, edgeNormal) edgeNormal = edgeNormal / norm2(edgeNormal) - p4_splay = p0 + edgeNormal * (1 + splay) * dist + p4_splay = p0 + edgeNormal * (1 + splay(marchIter)) * dist p4 = (one - blend) * p4_extrap + blend * p4_splay @@ -124,6 +125,7 @@ subroutine getBCBlocks(bcType, blk4, blk0, blk2) ! Accumulate the blk4 dependence into blk0 and blk2 use hypInput + use hypData, only: marchIter implicit none ! Input @@ -132,8 +134,8 @@ subroutine getBCBlocks(bcType, blk4, blk0, blk2) select case (bcType) case (BCSplay) - blk0 = blk0 + (one + splay) * blk4 - blk2 = blk2 - splay * blk4 + blk0 = blk0 + (one + splay(marchIter)) * blk4 + blk2 = blk2 - splay(marchIter) * blk4 case (BCXSymm) blk4(:, 1) = -blk4(:, 1) diff --git a/src/modules/hypInput.f90 b/src/modules/hypInput.f90 index 2c3b4d1..b83a732 100644 --- a/src/modules/hypInput.f90 +++ b/src/modules/hypInput.f90 @@ -8,22 +8,30 @@ module hypInput ! Input parameters. See pyHyp.py for what each parameter means - real(kind=realType) :: s0, ps0, pgridratio + real(kind=realType) :: ps0, pgridratio real(kind=realType) :: slexp - real(kind=realType) :: epsE, volCoef, volBlend, epsI, theta real(kind=realType) :: kspRelTol, cmax, marchDist - real(kind=realType) :: nodeTol, symTol, cornerAngle - real(kind=realType) :: splay, splayEdgeOrthogonality, splayCornerOrthogonality + real(kind=realType) :: nodeTol, symTol integer(kind=intType) :: N, nConstantStart, nConstantEnd, nTruncate - integer(kind=intType) :: volSmoothIter, kspMaxIts, preConLag, kspSubspaceSize + integer(kind=intType) :: kspMaxIts, preConLag, kspSubspaceSize integer(kind=intType) :: coarsen - real(kind=realType), dimension(:, :), allocatable :: volSmoothSchedule + + real(kind=realType), dimension(:), allocatable, target :: fullDeltaS + + integer(kind=intType), dimension(:), allocatable, target :: volSmoothIter + real(kind=realType), dimension(:), allocatable, target :: volBlend + real(kind=realType), dimension(:), allocatable, target :: epsE + real(kind=realType), dimension(:), allocatable, target :: epsI + real(kind=realType), dimension(:), allocatable, target :: theta + real(kind=realType), dimension(:), allocatable, target :: splay + real(kind=realType), dimension(:), allocatable, target :: splayEdgeOrthogonality + real(kind=realType), dimension(:), allocatable, target :: splayCornerOrthogonality + real(kind=realType), dimension(:), allocatable, target :: cornerAngle + real(kind=realType), dimension(:), allocatable, target :: volCoef logical :: nonLinear, writeMetrics, unattachedEdgesAreSymmetry logical :: noPointReduce - real(kind=realType), dimension(:), allocatable :: fullDeltaS - ! Input boundary condition information integer(kind=intType), dimension(:, :), allocatable :: BCs diff --git a/src/python/f2py/hyp.pyf b/src/python/f2py/hyp.pyf index 14d9e34..3233f58 100644 --- a/src/python/f2py/hyp.pyf +++ b/src/python/f2py/hyp.pyf @@ -3,24 +3,25 @@ python module hyp ! in interface ! in :hyp module hypinput ! in :hyp:hypInput.f90 use precision - real(kind=realtype) :: epse - real(kind=realtype) :: epsi - real(Kind=realtype) :: theta - real(kind=realtype) :: s0 + real(kind=realtype), dimension(:), allocatable :: fulldeltas + real(kind=realtype), dimension(:), allocatable :: epse + real(kind=realtype), dimension(:), allocatable :: epsi + real(Kind=realtype), dimension(:), allocatable :: theta real(kind=realtype) :: ps0 real(kind=realtype) :: pgridratio integer(kind=inttype) :: n integer(kind=inttype) :: nconstantstart integer(kind=inttype) :: nconstantend integer(kind=inttype) :: ntruncate - integer(kind=inttype) :: volsmoothiter + integer(kind=inttype), dimension(:), allocatable :: volsmoothiter integer(kind=inttype) :: coarsen real(kind=realtype) :: slexp - real(kind=realtype) :: volcoef - real(kind=realtype) :: volblend - real(kind=realtype) :: splay - real(kind=realtype) :: splayedgeorthogonality - real(kind=realtype) :: splaycornerorthogonality + real(kind=realtype), dimension(:), allocatable :: volcoef + real(kind=realtype), dimension(:), allocatable :: volblend + real(kind=realtype), dimension(:), allocatable :: splay + real(kind=realtype), dimension(:), allocatable :: splayedgeorthogonality + real(kind=realtype), dimension(:), allocatable :: splaycornerorthogonality + real(kind=realtype), dimension(:), allocatable :: cornerangle real(kind=realtype) :: kspreltol logical :: nonlinear logical :: unattachededgesaresymmetry @@ -31,7 +32,6 @@ python module hyp ! in real(kind=realtype) :: cmax real(kind=realtype) :: nodetol real(kind=realtype) :: symtol - real(kind=realtype) :: cornerangle integer(kind=inttype), parameter :: bcdefault = -1 integer(kind=inttype), parameter :: bcsplay = 0 integer(kind=inttype), parameter :: bcxsymm = 1 @@ -61,7 +61,6 @@ python module hyp ! in integer(kind=inttype), parameter:: outerfacefarfield = 1 integer(kind=inttype), parameter:: outerfaceoverset = 2 integer(kind=inttype), dimension(:, :), allocatable :: bcs - real(kind=realtype), dimension(:, :), allocatable :: volsmoothschedule end module hypinput module hypdata diff --git a/src/utils/releaseMemory.F90 b/src/utils/releaseMemory.F90 index 5975b4b..88b8a8e 100644 --- a/src/utils/releaseMemory.F90 +++ b/src/utils/releaseMemory.F90 @@ -57,8 +57,40 @@ subroutine releaseMemory deallocate (conn) end if - if (allocated(volSmoothSchedule)) then - deallocate (volSmoothSchedule) + if (allocated(epse)) then + deallocate (epse) + end if + + if (allocated(epsi)) then + deallocate (epsi) + end if + + if (allocated(theta)) then + deallocate (theta) + end if + + if (allocated(splay)) then + deallocate (splay) + end if + + if (allocated(volSmoothIter)) then + deallocate (volSmoothIter) + end if + + if (allocated(volblend)) then + deallocate (volblend) + end if + + if (allocated(volCoef)) then + deallocate (volCoef) + end if + + if (allocated(splayEdgeOrthogonality)) then + deallocate (splayEdgeOrthogonality) + end if + + if (allocated(splayCornerOrthogonality)) then + deallocate (splayCornerOrthogonality) end if if (varsAllocated) then diff --git a/tests/test_all_examples.py b/tests/test_all_examples.py index a2b8476..0e2edd1 100644 --- a/tests/test_all_examples.py +++ b/tests/test_all_examples.py @@ -14,7 +14,7 @@ def setUp(self): # Insert the repo root in path so that testflo can import examples sys.path.insert(0, os.path.join(baseDir, "../")) - def common_test(self, testFile, marchDist, relTol=1e-14): + def commonTest(self, testFile, marchDist, relTol=1e-14): volumeName = os.path.split(testFile)[1] refFile = os.path.join(refDir, volumeName) @@ -51,55 +51,90 @@ def common_test(self, testFile, marchDist, relTol=1e-14): except AssertionError: self.assertEqual(output, "/CGNSLibraryVersion <> /CGNSLibraryVersion : data values differ\n") - def test_2D_euler(self): + def test2DEuler(self): from examples.naca0012.naca0012_euler import volumeFile, hyp - marchDist = hyp.getOption("marchDist") - self.common_test(volumeFile, marchDist) + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) - def test_2D_rans(self): - from examples.naca0012.naca0012_rans import volumeFile, hyp + def test2DRans(self): + from examples.naca0012.naca0012_rans import extrudeDefaultCase - marchDist = hyp.getOption("marchDist") - self.common_test(volumeFile, marchDist) + hyp, volumeFile = extrudeDefaultCase() - def test_717(self): + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) + + def test2DRansConstantLayers(self): + from examples.naca0012.naca0012_rans import extrudeConstantLayersCase + + hyp, volumeFile = extrudeConstantLayersCase() + + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) + + def test2DRansSchedule(self): + from examples.naca0012.naca0012_rans import extrudeScheduleCase + + hyp, volumeFile = extrudeScheduleCase() + + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) + + def test2DRansExplicit(self): + from examples.naca0012.naca0012_rans import extrudeExplicitCase + + hyp, volumeFile = extrudeExplicitCase() + + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) + + def test717(self): from examples.wing_717.run717 import volumeFile, hyp - marchDist = hyp.getOption("marchDist") - self.common_test(volumeFile, marchDist) + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) - def test_BWB(self): + def testBWB(self): from examples.BWB.runBWB import volumeFile, hyp - marchDist = hyp.getOption("marchDist") - self.common_test(volumeFile, marchDist) + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) - def test_corner(self): + def testCorner(self): from examples.corner.runCorner import volumeFile, commonOptions marchDist = commonOptions["marchDist"] - self.common_test(volumeFile, marchDist) + self.commonTest(volumeFile, marchDist) - def test_M6(self): + def testM6(self): from examples.m6.runM6 import volumeFile, hyp - marchDist = hyp.getOption("marchDist") - self.common_test(volumeFile, marchDist) + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) - def test_plate(self): + def testPlate(self): from examples.plate.runPlate import volumeFile, hyp - marchDist = hyp.getOption("marchDist") - self.common_test(volumeFile, marchDist) + marchDist = hyp.getUsedMarchDistance() + self.commonTest(volumeFile, marchDist) - def test_sphere(self): + def testSphere(self): from examples.sphere.runSphere import volumeFile, commonOptions marchDist = commonOptions["marchDist"] - self.common_test(volumeFile, marchDist) + self.commonTest(volumeFile, marchDist) + + def testSimpleOCart(self): + from examples.simpleOCart.runSimpleOCart import extrudeDefaultCase + + outFile, hExtra = extrudeDefaultCase() + + self.commonTest(outFile, hExtra) + + def testSimpleOCartNoSurfaceMesh(self): + from examples.simpleOCart.runSimpleOCart import extrudeNoSurfaceMeshCase - def test_simpleOCart(self): - from examples.simpleOCart.runSimpleOCart import outFile, hExtra + outFile, hExtra = extrudeNoSurfaceMeshCase() - self.common_test(outFile, hExtra) + self.commonTest(outFile, hExtra)