From ea893bcc352ea627b119e28d2fd48e54ae41cae5 Mon Sep 17 00:00:00 2001 From: Paul Dechamps <paul.dechamps@uliege.be> Date: Sun, 10 Nov 2024 17:59:14 +0100 Subject: [PATCH] (feat) Update API API only works for 2D cases --- blast/api/{core.py => blaster_api.py} | 103 +++++++------- blast/tests/apiTest.py | 197 ++++++++++++++++++++++++++ 2 files changed, 250 insertions(+), 50 deletions(-) rename blast/api/{core.py => blaster_api.py} (53%) create mode 100644 blast/tests/apiTest.py diff --git a/blast/api/core.py b/blast/api/blaster_api.py similarity index 53% rename from blast/api/core.py rename to blast/api/blaster_api.py index 5811adf..3d34d6d 100644 --- a/blast/api/core.py +++ b/blast/api/blaster_api.py @@ -19,7 +19,9 @@ ## Initialize blast computation # Paul Dechamps -def initBlast(cfg, icfg, iSolverName='DART'): +AVAILABLE_SOLVERS = ['DART'] + +def init_blaster(cfg, icfg, iSolverName='DART', task='analysis'): """ Inputs ------ @@ -55,60 +57,61 @@ def initBlast(cfg, icfg, iSolverName='DART'): from blast.coupler import Coupler import blast - if iSolverName == 'DART': + if iSolverName not in AVAILABLE_SOLVERS: + raise RuntimeError('BLASTERAPI: Invalid inviscid solver name', iSolverName) + elif iSolverName == 'DART': from blast.interfaces.dart.DartInterface import DartInterface as interface - # Check viscous solver parameters. - if 'Re' in cfg and cfg['Re'] > 0: - _Re = cfg['Re'] - else: - raise RuntimeError('Missing or invalid Reynolds number') - if 'Minf' in cfg and cfg['Minf'] > 0: - _Minf = cfg['Minf'] - else: - _Minf = 0.1 - if 'CFL0' in cfg and cfg['CFL0'] > 0: - _CFL0 = cfg['CFL0'] - else: - _CFL0 = 1 - if 'Verb' in cfg and 0<= cfg['Verb'] <= 3: - _verbose = cfg['Verb'] - else: - _verbose = 1 - _xtrF = [-1, -1] - if 'xtrF' in cfg: - for i, xtr in enumerate(cfg['xtrF']): - if not(0 <= xtr <= 1) and xtr != -1: - raise RuntimeError("Incorrect forced transition location.") - _xtrF[i] = xtr - _span = 0 - _nSections = 1 - - # Check coupler parameters. - if 'couplIter' in cfg and cfg['couplIter'] > 0: - __couplIter = cfg['couplIter'] - else: - __couplIter = 150 - if 'couplTol' in cfg: - __couplTol = cfg['couplTol'] - else: - __couplTol = 1e-4 - if 'iterPrint' in cfg: - __iterPrint = cfg['iterPrint'] - else: - __iterPrint = 1 - if 'resetInv' in cfg: - __resetInv = cfg['resetInv'] - else: - __resetInv = False + # Viscous solver + if 'Re' not in cfg or cfg['Re'] <= 0.: + raise RuntimeError('BLASTERAPI: Missing or invalid Reynolds number') + _Re = cfg['Re'] + + _Minf = cfg.get('Minf', 0.1) + _cfl0 = cfg.get('CFL0', 1.) + _verbose = cfg.get('Verb', 0) + _xtrF = cfg.get('xtrF', [-1, -1]) + _span = cfg.get('span', 1.) + _nSections = cfg.get('nSections', 1) + + if _Minf <= 0: + raise RuntimeError('BLASTERAPI: Invalid Mach number', _Minf) + if _cfl0 <= 0: + raise RuntimeError('BLASTERAPI: Invalid CFL number', _cfl0) + for xtr in _xtrF: + if not(0 <= xtr <= 1) and xtr != -1: + raise RuntimeError("BLASTERAPI: Incorrect forced transition location.", xtr) + if _span < 0: + raise RuntimeError('BLASTERAPI: Invalid span', _span) + if _nSections <= 0: + raise RuntimeError('BLASTERAPI: Invalid number of sections', _nSections) + + # Coupler + _couplIter = cfg.get('couplIter', 150) + _couplTol = cfg.get('couplTol', 1e-4) + _iterPrint = cfg.get('iterPrint', 1) + _resetInv = cfg.get('resetInv', False) + + if _couplIter < 0: + raise RuntimeError('BLASTERAPI: Invalid number of coupling iterations', _couplIter) + if _couplTol <= 0: + raise RuntimeError('BLASTERAPI: Invalid coupling tolerance', _couplTol) + if _iterPrint < 0: + raise RuntimeError('BLASTERAPI: Invalid iteration print frequency', _iterPrint) # Viscous solver object. - vSolver = blast.Driver(_Re, _Minf, _CFL0, _nSections, _xtrF[0], _xtrF[1], _span, _verbose =_verbose) + vSolver = blast.Driver(_Re, _Minf, _cfl0, _nSections, _xtrF[0], _xtrF[1], _span, _verbose =_verbose) # Solvers interface. - solversAPI = interface(icfg, vSolver, cfg) + solverAPI = interface(icfg, vSolver, cfg) # Coupler - coupler = Coupler(solversAPI, vSolver, _maxCouplIter = __couplIter, _couplTol = __couplTol, _iterPrint = __iterPrint, _resetInv = __resetInv) + coupler = Coupler(solverAPI, vSolver, _maxCouplIter = _couplIter, _couplTol = _couplTol, _iterPrint = _iterPrint, _resetInv = _resetInv) + + # Adjoint objects + cAdj = None + if task == 'optimization': + cAdj = blast.CoupledAdjoint(solverAPI.adjointSolver, vSolver) return {'coupler': coupler, - 'solversAPI': solversAPI, - 'vSolver': vSolver} + 'isol': solverAPI, + 'vsol': vSolver, + 'adj': cAdj} diff --git a/blast/tests/apiTest.py b/blast/tests/apiTest.py new file mode 100644 index 0000000..3b5bb9b --- /dev/null +++ b/blast/tests/apiTest.py @@ -0,0 +1,197 @@ +#!/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 2024 +# Test the blaster adjoint implementation. + +# Imports. +import numpy as np + +from fwk.wutils import parseargs +import fwk +from fwk.testing import * +from fwk.coloring import ccolors + +import fwk +from fwk.testing import * +from fwk.coloring import ccolors +import numpy as np +import os + +from matplotlib import pyplot as plt +from blast.api.blaster_api import init_blaster +import blast.utils as viscUtils + +def cfgInviscid(nthrds, verb): + import os.path + # Parameters + return { + # Options + 'scenario' : 'aerodynamic', + 'task' : 'optimization', + 'Threads' : nthrds, # number of threads + 'Verb' : verb, # verbosity + # Model (geometry or mesh) + 'File' : os.path.dirname(os.path.abspath(__file__)) + '/../models/dart/n0012.geo', # Input file containing the model + 'Pars' : {'xLgt' : 50, 'yLgt' : 50, 'msF' : 10, 'msTe' : 0.01, 'msLe' : 0.01}, # 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' : False, + 'Upstream' : 'upstream', + # Freestream + 'M_inf' : 0.2, # freestream Mach number + 'AoA' : 2., # 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' : 'SparseLu', # 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' : 20, # solver maximum number of iterations + } + +def cfgBlast(verb): + return { + 'Re' : 1e6, # Freestream Reynolds number + 'Minf' : 0.2, # 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': 100, # Maximum number of iterations + 'couplTol' : 1e-2, # Tolerance of the VII methodology + 'iterPrint': 20, # int, number of iterations between outputs + 'resetInv' : True, # bool, flag to reset the inviscid calculation at every iteration. + 'sections' : [0], # List of sections for boundary layer calculation + 'xtrF' : [-1, -1], # Forced transition location + 'interpolator' : 'Matching', # Interpolator for the coupling + } + +def main(): + + tms = fwk.Timers() + + tms['total'].start() + + # Parse agrs + args = parseargs() + verb = args.verb + nthrds = args.k + + icfg = cfgInviscid(nthrds, verb) + cfg = cfgBlast(verb) + + obj = init_blaster(cfg, icfg, iSolverName='DART', task='optimization') + coupler, isol, vsol, adj = obj['coupler'], obj['isol'], obj['vsol'], obj['adj'] + + # Forward problem + coupler.run() + + # Adjoint problem + isol.adjointSolver.run() + adj.run() + + vSolution = viscUtils.getSolution(isol.sec, write=False)[0] + #plotSolution(vSolution) + tms['total'].stop() + + # Make the api crash + params = { + 'Re': -1, + 'Minf': -1, + 'CFL0': -1, + 'couplIter': -1, + 'couplTol': -1, + 'iterPrint': -1 + } + + # Loop over the parameters + for param, value in params.items(): + original = cfg[param] + try: + cfg[param] = value + obj = init_blaster(cfg, icfg, iSolverName='DART', task='optimization') + raise AssertionError(f'API initialized with {param} = ', cfg[param]) + except AssertionError as e: + raise RuntimeError(e) + except: + print(ccolors.ANSI_GREEN + f'API crashed with {param} = ' + str(cfg[param]) + ccolors.ANSI_RESET) + cfg[param] = original + + print('Statistics') + print(tms) + print(ccolors.ANSI_BLUE + 'PyTesting' + ccolors.ANSI_RESET) + tests = CTests() + tests.add(CTest('Cl', isol.getCl(), 0.226, 1e-2)) + tests.add(CTest('Cd', isol.getCd()+vsol.Cdf, 0.00587, 1e-3)) + tests.add(CTest('dCl_dAoA', adj.tdCl_AoA, 5.47, 1e-4)) + tests.add(CTest('dCd_dAoA', adj.tdCd_AoA, 0.09531, 1e-3)) + tests.run() + + # eof + print('') + +def plotSolution(vSolution): + plt.figure() + plt.plot(vSolution['x'], vSolution['cf'], label='$c_f$') + plt.xlim([0, 1]) + plt.xlabel('x/c') + plt.ylabel('$c_f$') + plt.legend() + plt.grid() + plt.savefig('cf.png') + plt.draw() + + plt.figure() + plt.plot(vSolution['x'], vSolution['ctEq'], label='$ctEq$') + plt.xlim([0, 1]) + plt.xlabel('x/c') + plt.ylabel('$ctEq$') + plt.legend() + plt.grid() + plt.savefig('cf.png') + plt.draw() + + plt.figure() + plt.plot(vSolution['x'], vSolution['theta'], label='$\theta$') + plt.plot(vSolution['x'], vSolution['deltaStar'], label='$\delta^*$') + plt.plot(vSolution['x'], vSolution['theta']*vSolution['H'], label='$\delta^*from$') + plt.xlim([0, 1]) + plt.xlabel('x/c') + plt.ylabel('$\delta^*$') + plt.legend() + plt.grid() + plt.savefig('cf.png') + plt.draw() + + +if __name__ == '__main__': + main() \ No newline at end of file -- GitLab