diff --git a/blast/coupler.py b/blast/coupler.py index 397f8434e6af9d579951116eedca27f05975e951..b94e05e2079d2005595ffd889c2c30f37ffcf569 100644 --- a/blast/coupler.py +++ b/blast/coupler.py @@ -87,7 +87,7 @@ class Coupler: print('{:>5s}| {:>7s} {:>7s} {:>7s} | {:>6s} {:>6s} | {:>6s}'.format('It', 'Cl', 'Cd', 'Cdwake', 'xtrT', 'xtrB', 'Error')) print(' ----------------------------------------------------------') if couplIter % self.iterPrint == 0: - print(' {:>4.0f}| {:>7.4f} {:>7.4f} {:>7.4f} | {:>6.4f} {:>6.4f} | {:>6.3f}'.format(couplIter, self.iSolverAPI.getCl(), self.iSolverAPI.getCd()+self.vSolver.Cdf, self.vSolver.Cdt, self.vSolver.getxoctr(0, 0), self.vSolver.getxoctr(0, 1), np.log10(error))) + print(' {:>4.0f}| {:>7.4f} {:>7.4f} {:>7.4f} | {:>6.4f} {:>6.4f} | {:>6.3f}'.format(couplIter, self.iSolverAPI.getCl(), self.iSolverAPI.getCd()+self.vSolver.Cdf, self.vSolver.Cdt, self.vSolver.getAvrgxoctr(0), self.vSolver.getAvrgxoctr(1), np.log10(error))) if self.iSolverAPI.getVerbose() != 0 or self.vSolver.verbose != 0: print('') couplIter += 1 @@ -97,6 +97,6 @@ class Coupler: print('') print('{:>5s}| {:>7s} {:>7s} {:>7s} | {:>6s} {:>8s} | {:>6s}'.format('It', 'Cl', 'Cd', 'Cdwake', 'xtrT', 'xtrB', 'Error')) print(' ----------------------------------------------------------') - print(ccolors.ANSI_RED, '{:>4.0f}| {:>7.5f} {:>7.5f} {:>7.5f} | {:>6.4f} {:>7.4f} | {:>6.3f}\n'.format(couplIter, self.iSolverAPI.getCl(), self.iSolverAPI.getCd()+self.vSolver.Cdf, self.vSolver.Cdt, self.vSolver.getxoctr(0, 0), self.vSolver.getxoctr(0, 1), np.log10(error)), ccolors.ANSI_RESET) + print(ccolors.ANSI_RED, '{:>4.0f}| {:>7.5f} {:>7.5f} {:>7.5f} | {:>6.4f} {:>7.4f} | {:>6.3f}\n'.format(couplIter, self.iSolverAPI.getCl(), self.iSolverAPI.getCd()+self.vSolver.Cdf, self.vSolver.Cdt, self.vSolver.getAvrgxoctr(0), self.vSolver.getAvrgxoctr(1), np.log10(error)), ccolors.ANSI_RESET) self.iSolverAPI.writeCp(sfx='_viscous'+self.filesfx) return aeroCoeffs diff --git a/blast/models/dart/agard445_visc.geo b/blast/models/dart/agard445_visc.geo new file mode 100644 index 0000000000000000000000000000000000000000..c657f6c6d731e5268a415c04c5bf3853bb057505 --- /dev/null +++ b/blast/models/dart/agard445_visc.geo @@ -0,0 +1,229 @@ +/* AGARD 445 wing */ +// Initially generated by unsRgridWingGen.m + +// Parameters +// domain and mesh +DefineConstant[ nSpan = { 20, Name "Spanwise number of elements" } ]; +DefineConstant[ nLe = { 5, Name "LE chordwise number of elements" } ]; +DefineConstant[ nTe = { 5, Name "TE chordwise number of elements" } ]; +DefineConstant[ nMid = { 10, Name "Mid chordwise number of elements" } ]; +DefineConstant[ nWake = { 15, Name "Wake number of elements" } ]; +DefineConstant[ progSpan = { 1, Name "Spanwise progression" } ]; +DefineConstant[ progLe = { 1, Name "LE progression" } ]; +DefineConstant[ progTe = { 1, Name "TE progression" } ]; +DefineConstant[ progMid = { 1, Name "Mid progression" } ]; +DefineConstant[ progWake = { 1.0, Name "Wake progression" } ]; +DefineConstant[ xO = { -3, Name "Box origin (x)" } ]; +DefineConstant[ zO = { -3, Name "Box origin (z)" } ]; +DefineConstant[ xL = { 7, Name "Box length" } ]; +DefineConstant[ yL = { 3, Name "Box width" } ]; +DefineConstant[ zL = { 6, Name "Box height" } ]; + +//// GEOMETRY + + +/// Points + +// Airfoil 1: agard445, 51 points +Point(1) = {0.559000,0.000000,0.000000}; +Point(2) = {0.531050,0.000000,0.001398}; +Point(3) = {0.503100,0.000000,0.002739}; +Point(4) = {0.475150,0.000000,0.004075}; +Point(5) = {0.447200,0.000000,0.005406}; +Point(6) = {0.419250,0.000000,0.006680}; +Point(7) = {0.391300,0.000000,0.007837}; +Point(8) = {0.363350,0.000000,0.008866}; +Point(9) = {0.335400,0.000000,0.009743}; +Point(10) = {0.307450,0.000000,0.010442}; +Point(11) = {0.279500,0.000000,0.010923}; +Point(12) = {0.251550,0.000000,0.011158}; +Point(13) = {0.223600,0.000000,0.011163}; +Point(14) = {0.195650,0.000000,0.010968}; +Point(15) = {0.167700,0.000000,0.010576}; +Point(16) = {0.139750,0.000000,0.009995}; +Point(17) = {0.111800,0.000000,0.009196}; +Point(18) = {0.083850,0.000000,0.008156}; +Point(19) = {0.055900,0.000000,0.006781}; +Point(20) = {0.041925,0.000000,0.005920}; +Point(21) = {0.027950,0.000000,0.004891}; +Point(22) = {0.013975,0.000000,0.003617}; +Point(23) = {0.006988,0.000000,0.002622}; +Point(24) = {0.004193,0.000000,0.002057}; +Point(25) = {0.002795,0.000000,0.001699}; +Point(26) = {0.000000,0.000000,0.000000}; +Point(27) = {0.002795,0.000000,-0.001699}; +Point(28) = {0.004193,0.000000,-0.002057}; +Point(29) = {0.006988,0.000000,-0.002622}; +Point(30) = {0.013975,0.000000,-0.003617}; +Point(31) = {0.027950,0.000000,-0.004891}; +Point(32) = {0.041925,0.000000,-0.005920}; +Point(33) = {0.055900,0.000000,-0.006781}; +Point(34) = {0.083850,0.000000,-0.008156}; +Point(35) = {0.111800,0.000000,-0.009196}; +Point(36) = {0.139750,0.000000,-0.009995}; +Point(37) = {0.167700,0.000000,-0.010576}; +Point(38) = {0.195650,0.000000,-0.010968}; +Point(39) = {0.223600,0.000000,-0.011163}; +Point(40) = {0.251550,0.000000,-0.011158}; +Point(41) = {0.279500,0.000000,-0.010923}; +Point(42) = {0.307450,0.000000,-0.010442}; +Point(43) = {0.335400,0.000000,-0.009743}; +Point(44) = {0.363350,0.000000,-0.008866}; +Point(45) = {0.391300,0.000000,-0.007837}; +Point(46) = {0.419250,0.000000,-0.006680}; +Point(47) = {0.447200,0.000000,-0.005406}; +Point(48) = {0.475150,0.000000,-0.004075}; +Point(49) = {0.503100,0.000000,-0.002739}; +Point(50) = {0.531050,0.000000,-0.001398}; + +// Airfoil 2: agard445, 51 points +Point(52) = {1.178128,0.762000,0.000000}; +Point(53) = {1.159709,0.762000,0.000921}; +Point(54) = {1.141290,0.762000,0.001805}; +Point(55) = {1.122870,0.762000,0.002685}; +Point(56) = {1.104451,0.762000,0.003562}; +Point(57) = {1.086032,0.762000,0.004402}; +Point(58) = {1.067613,0.762000,0.005165}; +Point(59) = {1.049194,0.762000,0.005843}; +Point(60) = {1.030775,0.762000,0.006421}; +Point(61) = {1.012356,0.762000,0.006881}; +Point(62) = {0.993937,0.762000,0.007198}; +Point(63) = {0.975518,0.762000,0.007353}; +Point(64) = {0.957099,0.762000,0.007357}; +Point(65) = {0.938680,0.762000,0.007228}; +Point(66) = {0.920261,0.762000,0.006970}; +Point(67) = {0.901842,0.762000,0.006587}; +Point(68) = {0.883423,0.762000,0.006060}; +Point(69) = {0.865004,0.762000,0.005375}; +Point(70) = {0.846585,0.762000,0.004468}; +Point(71) = {0.837375,0.762000,0.003901}; +Point(72) = {0.828166,0.762000,0.003223}; +Point(73) = {0.818956,0.762000,0.002383}; +Point(74) = {0.814351,0.762000,0.001728}; +Point(75) = {0.812509,0.762000,0.001356}; +Point(76) = {0.811589,0.762000,0.001120}; +Point(77) = {0.809747,0.762000,0.000000}; +Point(78) = {0.811589,0.762000,-0.001120}; +Point(79) = {0.812509,0.762000,-0.001356}; +Point(80) = {0.814351,0.762000,-0.001728}; +Point(81) = {0.818956,0.762000,-0.002383}; +Point(82) = {0.828166,0.762000,-0.003223}; +Point(83) = {0.837375,0.762000,-0.003901}; +Point(84) = {0.846585,0.762000,-0.004468}; +Point(85) = {0.865004,0.762000,-0.005375}; +Point(86) = {0.883423,0.762000,-0.006060}; +Point(87) = {0.901842,0.762000,-0.006587}; +Point(88) = {0.920261,0.762000,-0.006970}; +Point(89) = {0.938680,0.762000,-0.007228}; +Point(90) = {0.957099,0.762000,-0.007357}; +Point(91) = {0.975518,0.762000,-0.007353}; +Point(92) = {0.993937,0.762000,-0.007198}; +Point(93) = {1.012356,0.762000,-0.006881}; +Point(94) = {1.030775,0.762000,-0.006421}; +Point(95) = {1.049194,0.762000,-0.005843}; +Point(96) = {1.067613,0.762000,-0.005165}; +Point(97) = {1.086032,0.762000,-0.004402}; +Point(98) = {1.104451,0.762000,-0.003562}; +Point(99) = {1.122870,0.762000,-0.002685}; +Point(100) = {1.141290,0.762000,-0.001805}; +Point(101) = {1.159709,0.762000,-0.000921}; + + +// Midplane: +Point(5351) = {xO+xL,0.000000,0.000000}; +Point(5352) = {xO+xL,0.762000,0.000000}; + +/// Lines + +// Airfoil 1: +Spline(1) = {1,2,3}; +Spline(2) = {3,4,5,6,7,8,9,10,11,12,13,14,15}; +Spline(3) = {15,16,17,18,19,20,21,22,23,24,25,26}; +Spline(4) = {26,27,28,29,30,31,32,33,34,35,36,37}; +Spline(5) = {37,38,39,40,41,42,43,44,45,46,47,48,49}; +Spline(6) = {49,50,1}; + +// Airfoil 2: +Spline(7) = {52,53,54}; +Spline(8) = {54,55,56,57,58,59,60,61,62,63,64,65,66}; +Spline(9) = {66,67,68,69,70,71,72,73,74,75,76,77}; +Spline(10) = {77,78,79,80,81,82,83,84,85,86,87,88}; +Spline(11) = {88,89,90,91,92,93,94,95,96,97,98,99,100}; +Spline(12) = {100,101,52}; + + +// Airfoil 1 to airfoil 2: +Line(61) = {1,52}; +Line(62) = {3,54}; +Line(63) = {15,66}; +Line(64) = {26,77}; +Line(65) = {37,88}; +Line(66) = {49,100}; + +// Midplane: +Line(131) = {5351,5352}; + +// Wing to midplane: +Line(161) = {1,5351}; +Line(162) = {52,5352}; +/// Line loops & Surfaces + +// Wing 1: +Line Loop(11) = {1,62,-7,-61}; +Line Loop(12) = {2,63,-8,-62}; +Line Loop(13) = {3,64,-9,-63}; +Line Loop(14) = {4,65,-10,-64}; +Line Loop(15) = {5,66,-11,-65}; +Line Loop(16) = {6,61,-12,-66}; +Surface(11) = {-11}; +Surface(12) = {-12}; +Surface(13) = {-13}; +Surface(14) = {-14}; +Surface(15) = {-15}; +Surface(16) = {-16}; + + +// Wake: +Line Loop(81) = {161,131,-162,-61}; +Surface(81) = {81}; + +/// Surface loops & Volumes + + +//// MESHING ALGORITHM + + +//// PHYSICAL GROUPS + +// Trailing edge and wake tip +Physical Line("wakeTip") = {162}; +Physical Line("te") = {61}; + +// Wing: +Physical Surface("wing") = {11,12,13}; +Physical Surface("wing_") = {14,15,16}; + +// Wake: +Physical Surface("wake") = {81}; + +Coherence; +Transfinite Curve {161, 162} = nWake Using Progression progWake; +Transfinite Curve {131, 61, 62, 66, 63, 65, 64} = nSpan Using Progression progSpan; +Transfinite Curve {-3, 4, -9, 10} = nLe Using Progression progLe; +Transfinite Curve {7, -12, 1, -6} = nTe Using Progression progTe; +Transfinite Curve {2, 5, 8, 11} = nMid Using Progression progMid; +Transfinite Surface {81}; +Transfinite Surface {12}; +Transfinite Surface {15}; +Transfinite Surface {13}; +Transfinite Surface {14}; +Transfinite Surface {11}; +Transfinite Surface {16}; + +Recombine Surface {81}; +Recombine Surface {12}; +Recombine Surface {15}; +Recombine Surface {13}; +Recombine Surface {14}; +Recombine Surface {11}; +Recombine Surface {16}; diff --git a/blast/src/DDiscretization.cpp b/blast/src/DDiscretization.cpp index 032e2d843f47d3b6e7cbee2602fd124dd2206d30..9a6796b9d7c502e039873550c9c0cefbbabb3848 100644 --- a/blast/src/DDiscretization.cpp +++ b/blast/src/DDiscretization.cpp @@ -17,6 +17,8 @@ void Discretization::setGlob(std::vector<double> &_x, std::vector<double> &_y, s { if (_x.size() != _y.size() || _y.size() != _z.size() || _x.size() != _z.size()) throw std::runtime_error("blast::Discretization Wrong mesh sizes.\n"); + if (_x.size() < 2 || _y.size() < 2 || _z.size() < 2) + throw std::runtime_error("blast::Discretization Mesh too small.\n"); nMarkers = _x.size(); x.resize(nMarkers, 0.); diff --git a/blast/src/DDriver.cpp b/blast/src/DDriver.cpp index 08a2e328651df0cee65513e5d7fc91a7a3061421..a1fcba0bbb46a638a8ea896cfa1f40ab71460ec5 100644 --- a/blast/src/DDriver.cpp +++ b/blast/src/DDriver.cpp @@ -393,6 +393,15 @@ void Driver::computeSectionalDrag(std::vector<BoundaryLayer *> const &bl, size_t Cdp_sec[i] = Cdt_sec[i] - Cdf_sec[i]; } +double Driver::getAvrgxoctr(size_t const iRegion) const +{ + double xtr = 0.0; + for (size_t iSec = 0; iSec < sections.size(); ++iSec) + xtr += sections[iSec][iRegion]->xoctr; + xtr /= sections.size(); + return xtr; +} + /** * @brief Compute total drag coefficient on the airfoil/wing. * Performs the sectional drag integration for 3D computations. diff --git a/blast/src/DDriver.h b/blast/src/DDriver.h index a95458659929bf0bce46f86246f5f2acfa516de1..2937d3dc529e53ed3da7127fc151dd100732626b 100644 --- a/blast/src/DDriver.h +++ b/blast/src/DDriver.h @@ -45,6 +45,7 @@ public: // Getters. double getxtr(size_t iSec, size_t iRegion) const { return sections[iSec][iRegion]->xtr; }; double getxoctr(size_t iSec, size_t iRegion) const { return sections[iSec][iRegion]->xoctr; }; + double getAvrgxoctr(size_t const iRegion) const; double getRe() const { return Re; }; std::vector<std::vector<double>> getSolution(size_t iSec); diff --git a/blast/utils.py b/blast/utils.py index 80ac5bb7c607e81879be93f73c72d6c1fee3166a..91a0aa4699a2016cd0ef2f666f7e5b5c7befa25c 100644 --- a/blast/utils.py +++ b/blast/utils.py @@ -67,6 +67,8 @@ def initBlast(iconfig, vconfig, iSolver='DART'): if 'nSections' not in vconfig: vconfig['nSections'] = len(vconfig['sections']) + if 'sfx' not in vconfig: + vconfig['sfx'] = '' # Viscous solver vSolver = initBL(vconfig['Re'], vconfig['Minf'], vconfig['CFL0'], vconfig['nSections'], xtrF=vconfig['xtrF']) @@ -80,7 +82,7 @@ def initBlast(iconfig, vconfig, iSolver='DART'): # Coupler import blast.coupler as blastCoupler - coupler = blastCoupler.Coupler(iSolverAPI, vSolver, _maxCouplIter=vconfig['couplIter'], _couplTol=vconfig['couplTol'], _iterPrint=vconfig['iterPrint'], _resetInv=vconfig['resetInv']) + coupler = blastCoupler.Coupler(iSolverAPI, vSolver, _maxCouplIter=vconfig['couplIter'], _couplTol=vconfig['couplTol'], _iterPrint=vconfig['iterPrint'], _resetInv=vconfig['resetInv'], sfx=vconfig['sfx']) return coupler, iSolverAPI, vSolver def mesh(file, pars): @@ -131,8 +133,8 @@ def getSolution(vSolver, iSec=0): 'y' : solverOutput[17], 'z' : solverOutput[18], 'ueInv' : solverOutput[19], - 'xtrT' : vSolver.getxtr(iSec, 0), - 'xtrB' : vSolver.getxtr(iSec, 1), + 'xtrT' : vSolver.getAvrgxoctr(0), + 'xtrB' : vSolver.getAvrgxoctr(1), 'Cdt_w' : vSolver.Cdt_sec[iSec], 'Cdf' : vSolver.Cdf_sec[iSec], 'Cdp' : vSolver.Cdp_sec[iSec] diff --git a/blast/validation/agardValidation.py b/blast/validation/agardValidation.py new file mode 100644 index 0000000000000000000000000000000000000000..a97d4d484f4f25922c0175f5ec2f3d518152aa88 --- /dev/null +++ b/blast/validation/agardValidation.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright 2022 University of Liège +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# @author Paul Dechamps +# @date 2022 +# Test the blast implementation on the 3D AGARD wing + +# Imports. + +import blast.utils as viscUtils +import numpy as np +import os.path + +from fwk.wutils import parseargs +import fwk +from fwk.testing import * +from fwk.coloring import ccolors + +import math + +def cfgInviscid(nthrds, verb): + # Parameters + return { + # Options + 'scenario' : 'aerodynamic', + 'task' : 'analysis', + 'Threads' : nthrds, # number of threads + 'Verb' : verb, # verbosity + # Model (geometry or mesh) + 'File' : os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + '/modules/dartflo/dart/models/agard445.geo', # Input file containing the model + 'Pars' : {'xL': 7., 'yL': 3., 'zL': 6., 'xO': -3, 'zO': -3, 'msLeRt': 0.0028, 'msTeRt': 0.0056, 'msLeTp': 0.0018, 'msTeTp': 0.0036, 'msF': 1.0}, # parameters for input file model + 'Dim' : 3, # problem dimension + 'Format' : 'vtk', # save format (vtk or gmsh) + # Markers + 'Fluid' : 'field', # name of physical group containing the fluid + 'Farfield' : ['upstream', 'farfield', 'downstream'], # LIST of names of physical groups containing the farfield boundaries (upstream/downstream should be first/last element) + 'Wings' : ['wing'], # LIST of names of physical groups containing the lifting surface boundary + 'Wakes' : ['wake'], # LIST of names of physical group containing the wake + 'WakeTips' : ['wakeTip'], # LIST of names of physical group containing the edge of the wake + 'Tes' : ['te'], # LIST of names of physical group containing the trailing edge + 'dbc' : True, + 'Upstream' : 'upstream', + # Freestream + 'M_inf' : 0.96, # freestream Mach number + 'AoA' : 1, # freestream angle of attack + # Geometry + 'S_ref' : 0.35, # reference surface length + 'c_ref' : 0.47, # reference chord length + 'x_ref' : 0.0, # reference point for moment computation (x) + 'y_ref' : 0.0, # reference point for moment computation (y) + 'z_ref' : 0.0, # reference point for moment computation (z) + # Numerical + 'LSolver' : 'GMRES', # inner solver (Pardiso, MUMPS or GMRES) + 'G_fill' : 2, # fill-in factor for GMRES preconditioner + 'G_tol' : 1e-5, # tolerance for GMRES + 'G_restart' : 50, # restart for GMRES + 'Rel_tol' : 1e-6, # relative tolerance on solver residual + 'Abs_tol' : 1e-8, # absolute tolerance on solver residual + 'Max_it' : 50 # solver maximum number of iterations + } + +def cfgBlast(verb): + return { + 'Re' : 6.7e6, # Freestream Reynolds number + 'Minf' : 0.96, # Freestream Mach number (used for the computation of the time step only) + 'CFL0' : 1, # Inital CFL number of the calculation + + 'sections' : np.linspace(0.026, 0.7, 15), + 'writeSections': np.linspace(0.01, 0.76, 15), + 'Sym':[0.], + 'span': 0.762, + 'interpolator': 'Rbf', + 'rbftype': 'linear', + 'smoothing': 1e-8, + 'degree': 0, + 'neighbors': 10, + 'saveTag': 5, + + 'Verb': verb, # Verbosity level of the solver + 'couplIter': 100, # Maximum number of iterations + 'couplTol' : 1e-3, # Tolerance of the VII methodology + 'iterPrint': 1, # int, number of iterations between outputs + 'resetInv' : True, # bool, flag to reset the inviscid calculation at every iteration. + 'xtrF' : [0., 0.], # Forced transition location + 'nDim' : 3 + } + +def main(): + # Timer. + tms = fwk.Timers() + tms['total'].start() + + args = parseargs() + icfg = cfgInviscid(args.k, args.verb) + vcfg = cfgBlast(args.verb) + + AoAVec = [-1, 0., 1] + sfxVec = ['_AoAminus1deg', '_AoA0deg', '_AoA1deg'] + for i in range(3): + vcfg['sfx'] = sfxVec[i] + icfg['AoA'] = AoAVec[i] + + parsViscous = {'nLe': 15, 'nMid': 30, 'nTe': 7, 'nSpan': 60, 'nWake': 30, + 'progLe': 1.07, 'progMid': 1.0, 'progTe': 1.0, 'progSpan': 1.0, 'progWake': 1.15} + vMsh = viscUtils.mesh(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/models/dart/agard445_visc.geo', parsViscous) + vcfg['vMsh'] = vMsh + + tms['pre'].start() + coupler, iSolverAPI, vSolver = viscUtils.initBlast(icfg, vcfg) + tms['pre'].stop() + + print(ccolors.ANSI_BLUE + 'PySolving...' + ccolors.ANSI_RESET) + tms['solver'].start() + aeroCoeffs = coupler.run() + tms['solver'].stop() + + # Display results. + print(ccolors.ANSI_BLUE + 'PyRes...' + ccolors.ANSI_RESET) + print(' Re M alpha Cl Cd Cdp Cdf Cm') + print('{0:6.1f}e6 {1:8.2f} {2:8.1f} {3:8.4f} {4:8.4f} {5:8.4f} {6:8.4f} {7:8.4f}'.format(vcfg['Re']/1e6, iSolverAPI.getMinf(), iSolverAPI.getAoA()*180/math.pi, iSolverAPI.getCl(), vSolver.Cdt, vSolver.Cdp, vSolver.Cdf, iSolverAPI.getCm())) + + # Write results to file. + vSolution = viscUtils.getSolution(vSolver) + + # Write results to file. + for iSec in range(len(iSolverAPI.cfg['EffSections'])): + vSolution = viscUtils.getSolution(vSolver, iSec) + viscUtils.write(vSolution, vSolver.getRe(), sfx=sfxVec[i]+'slice'+str(iSec)) + vSolution['Cdt_int'] = vSolver.Cdf + iSolverAPI.getCd() + tms['total'].stop() + + print(ccolors.ANSI_BLUE + 'PyTiming...' + ccolors.ANSI_RESET) + print('CPU statistics') + print(tms) + print('SOLVERS statistics') + print(coupler.tms) + + cps = [] + for s in sfxVec: + cps_s = [] + for i in range(len(vcfg['writeSections'])): + cps_s.append(np.loadtxt('/Users/pauldechamps/lab/softwares/blaster/workspace/blast_validation_agardValidation/agard445_viscous'+s+'_slice_'+str(i)+'.dat', delimiter=',', skiprows=1)) + cps.append(cps_s) + # Plotting + from matplotlib import pyplot as plt + for i in range(len(vcfg['writeSections'])): + plt.figure(i) + for j in range(len(sfxVec)): + plt.plot(cps[j][i][:,3], cps[j][i][:,4], label=sfxVec[j].replace('_', ' ')) + plt.legend() + plt.title('Section ' + str(i)) + plt.xlabel('x') + plt.ylabel('cp') + plt.show() + # Test solution + print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET) + tests = CTests() + tests.add(CTest('Cl', iSolverAPI.getCl(), 0.069, 5e-2)) + tests.add(CTest('Cd', vSolution['Cdt_int'], 0.00498, 1e-3, forceabs=True)) + tests.add(CTest('Iterations', len(aeroCoeffs), 8, 0, forceabs=True)) + tests.run() + + # eof + print('') + +if __name__ == "__main__": + main() diff --git a/blast/validation/lannValidation.py b/blast/validation/lannValidation.py index 870d67fb74d662404e41d12036708eb9e868712d..f5e865925ca0f310cfdada7ec3eb750ce2877f0e 100644 --- a/blast/validation/lannValidation.py +++ b/blast/validation/lannValidation.py @@ -18,19 +18,7 @@ # @author Paul Dechamps # @date 2022 -# Test the blast implementation. The test case is a compressible attached transitional flow. -# Tested functionalities; -# - Time integration. -# - Two time-marching techniques (agressive and safe). -# - Transition routines. -# - Forced transition. -# - Compressible flow routines. -# - Laminar and turbulent flow. -# -# Untested functionalities. -# - Separation routines. -# - Multiple failure case at one iteration. -# - Response to unconverged inviscid solver. +# Test the blast implementation on the 3D LANN wing. # Imports. diff --git a/blast/validation/oneraValidation.py b/blast/validation/oneraValidation.py index 3c0d239551906db5425f858a1753f52adf60b6e9..cb9af04aba1ac3f44ca2572ff00b376f3e510fdd 100644 --- a/blast/validation/oneraValidation.py +++ b/blast/validation/oneraValidation.py @@ -18,19 +18,7 @@ # @author Paul Dechamps # @date 2022 -# Test the blast implementation. The test case is a compressible attached transitional flow. -# Tested functionalities; -# - Time integration. -# - Two time-marching techniques (agressive and safe). -# - Transition routines. -# - Forced transition. -# - Compressible flow routines. -# - Laminar and turbulent flow. -# -# Untested functionalities. -# - Separation routines. -# - Multiple failure case at one iteration. -# - Response to unconverged inviscid solver. +# Test the blast implementation on the 3D ONERA M6 wing. # Imports. diff --git a/blast/validation/raeValidation.py b/blast/validation/raeValidation.py new file mode 100644 index 0000000000000000000000000000000000000000..9a99c222fbda711fb0e6d53f8cf5b33bb0c0f426 --- /dev/null +++ b/blast/validation/raeValidation.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright 2022 University of Liège +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# @author Paul Dechamps +# @date 2022 + +# Imports. + +import blast.utils as viscUtils +import numpy as np + +from fwk.wutils import parseargs +import fwk +from fwk.testing import * +from fwk.coloring import ccolors + +import math + +def cfgInviscid(nthrds, verb): + import os.path + # Parameters + return { + # Options + 'scenario' : 'aerodynamic', + 'task' : 'analysis', + 'Threads' : nthrds, # number of threads + 'Verb' : verb, # verbosity + # Model (geometry or mesh) + 'File' : os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/models/dart/rae_2.geo', # Input file containing the model + 'Pars' : {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1, 'msTe' : 0.01, 'msLe' : 0.002}, # parameters for input file model + 'Dim' : 2, # problem dimension + 'Format' : 'gmsh', # save format (vtk or gmsh) + # Markers + 'Fluid' : 'field', # name of physical group containing the fluid + 'Farfield' : ['upstream', 'farfield', 'downstream'], # LIST of names of physical groups containing the farfield boundaries (upstream/downstream should be first/last element) + 'Wing' : 'airfoil', # LIST of names of physical groups containing the lifting surface boundary + 'Wake' : 'wake', # LIST of names of physical group containing the wake + 'WakeTip' : 'wakeTip', # LIST of names of physical group containing the edge of the wake + 'Te' : 'te', # LIST of names of physical group containing the trailing edge + 'dbc' : True, + 'Upstream' : 'upstream', + # Freestream + 'M_inf' : 0.729, # freestream Mach number + 'AoA' : 2.31, # freestream angle of attack + # Geometry + 'S_ref' : 1., # reference surface length + 'c_ref' : 1., # reference chord length + 'x_ref' : .25, # reference point for moment computation (x) + 'y_ref' : 0.0, # reference point for moment computation (y) + 'z_ref' : 0.0, # reference point for moment computation (z) + # Numerical + 'LSolver' : 'GMRES', # inner solver (Pardiso, MUMPS or GMRES) + 'G_fill' : 2, # fill-in factor for GMRES preconditioner + 'G_tol' : 1e-5, # tolerance for GMRES + 'G_restart' : 50, # restart for GMRES + 'Rel_tol' : 1e-6, # relative tolerance on solver residual + 'Abs_tol' : 1e-8, # absolute tolerance on solver residual + 'Max_it' : 50 # solver maximum number of iterations + } + +def cfgBlast(verb): + return { + 'Re' : 6.5e6, # Freestream Reynolds number + 'Minf' : 0.73, # Freestream Mach number (used for the computation of the time step only) + 'CFL0' : 1, # Inital CFL number of the calculation + 'Verb': verb, # Verbosity level of the solver + 'couplIter': 50, # Maximum number of iterations + 'couplTol' : 1e-4, # Tolerance of the VII methodology + 'iterPrint': 5, # int, number of iterations between outputs + 'resetInv' : True, # bool, flag to reset the inviscid calculation at every iteration. + 'sections' : [0], + 'xtrF' : [0., 0.],# Forced transition location + 'interpolator' : 'Matching', + 'nDim' : 2 + } + +def main(): + # Timer. + tms = fwk.Timers() + tms['total'].start() + + args = parseargs() + icfg = cfgInviscid(args.k, args.verb) + vcfg = cfgBlast(args.verb) + + tms['pre'].start() + coupler, iSolverAPI, vSolver = viscUtils.initBlast(icfg, vcfg) + tms['pre'].stop() + + print(ccolors.ANSI_BLUE + 'PySolving...' + ccolors.ANSI_RESET) + tms['solver'].start() + aeroCoeffs = coupler.run() + tms['solver'].stop() + + # Display results. + print(ccolors.ANSI_BLUE + 'PyRes...' + ccolors.ANSI_RESET) + print(' Re M alpha Cl Cd Cdp Cdf Cm') + print('{0:6.1f}e6 {1:8.2f} {2:8.1f} {3:8.4f} {4:8.4f} {5:8.4f} {6:8.4f} {7:8.4f}'.format(vcfg['Re']/1e6, iSolverAPI.getMinf(), iSolverAPI.getAoA()*180/math.pi, iSolverAPI.getCl(), vSolver.Cdt, vSolver.Cdp, vSolver.Cdf, iSolverAPI.getCm())) + + # Write results to file. + vSolution = viscUtils.getSolution(vSolver) + vSolution['Cdt_int'] = vSolution['Cdf'] + iSolverAPI.getCd() + tms['total'].stop() + + print(ccolors.ANSI_BLUE + 'PyTiming...' + ccolors.ANSI_RESET) + print('CPU statistics') + print(tms) + print('SOLVERS statistics') + print(coupler.tms) + + # Test solution + print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET) + tests = CTests() + tests.add(CTest('Cl', iSolverAPI.getCl(), 0.752, 5e-2)) # XFOIL 0.2325 + tests.add(CTest('Cd wake', vSolution['Cdt_w'], 0.0094, 1e-3, forceabs=True)) # XFOIL 0.00531 + tests.add(CTest('Cd integral', vSolution['Cdt_int'], 0.0145, 1e-3, forceabs=True)) # XFOIL 0.00531 + tests.add(CTest('Cdf', vSolution['Cdf'], 0.0069, 1e-3, forceabs=True)) # XFOIL 0.00084, Cdf = 0.00447 + tests.add(CTest('Iterations', len(aeroCoeffs), 35, 0, forceabs=True)) + tests.run() + + # Show results + if not args.nogui: + iCp = viscUtils.read('Cp_inviscid.dat') + vCp = viscUtils.read('Cp_viscous.dat') + plotcp = {'curves': [np.column_stack((vCp[:,0], vCp[:,3])), + np.column_stack((iCp[:,0], iCp[:,3]))], + 'labels': ['Blast (VII)', 'DART (inviscid)'], + 'lw': [3, 3, 2, 2], + 'color': ['darkblue', 'darkblue'], + 'ls': ['-', '--'], + 'reverse': True, + 'xlim':[0, 1], + 'yreverse': True, + 'legend': True, + 'xlabel': '$x/c$', + 'ylabel': '$c_p$' + } + viscUtils.plot(plotcp) + + plotcf = {'curves': [np.column_stack((vSolution['x'], vSolution['Cf']))], + 'labels': ['Blast'], + 'lw': [3, 3], + 'color': ['darkblue'], + 'ls': ['-'], + 'reverse': True, + 'xlim':[0, 1], + 'legend': True, + 'xlabel': '$x/c$', + 'ylabel': '$c_f$' + } + viscUtils.plot(plotcf) + + # eof + print('') + +if __name__ == "__main__": + main()