diff --git a/dart/CMakeLists.txt b/dart/CMakeLists.txt index b63c6eb6ba3e3bc90c0fdb2e56045d1aac07e87c..0ac4587bff42c5e93fce7ce74c10c5ead64ffe07 100644 --- a/dart/CMakeLists.txt +++ b/dart/CMakeLists.txt @@ -1,11 +1,11 @@ # Copyright 2020 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. @@ -23,6 +23,7 @@ MACRO_AddTest(${CMAKE_CURRENT_SOURCE_DIR}/tests) INSTALL(FILES ${CMAKE_CURRENT_LIST_DIR}/__init__.py ${CMAKE_CURRENT_LIST_DIR}/utils.py DESTINATION dart) -INSTALL(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/scripts +INSTALL(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/interfaces + ${CMAKE_CURRENT_LIST_DIR}/scripts ${CMAKE_CURRENT_LIST_DIR}/viscous DESTINATION dart) diff --git a/dart/benchmark/onera.py b/dart/benchmark/onera.py index 3c42b26029de2940079c66c0a8da79a0ec516caa..910b225a72cd1d677876f0e74301091afc28679c 100644 --- a/dart/benchmark/onera.py +++ b/dart/benchmark/onera.py @@ -40,21 +40,16 @@ def newton(pbl): import dart import tbox # Pardiso and GMRES should give similar perfs + k = parseargs().k try: - newton = dart.Newton(pbl, tbox.Pardiso()) + newton = dart.Newton(pbl, tbox.Pardiso(), nthrds=k, vrb=2) except: gmres = tbox.Gmres() gmres.setFillFactor(2) gmres.setDropTol(1e-6) gmres.setRestart(50) gmres.setTolerance(1e-5) - newton = dart.Newton(pbl, gmres) - newton.nthreads = parseargs().k - newton.verbose = 1 - newton.relTol = 1e-6 - newton.absTol = 1e-8 - newton.lsTol = 1e-6 - newton.avThrsh = 1e-2 + newton = dart.Newton(pbl, gmres, nthrds=k, vrb=2) return newton def main(): diff --git a/dart/default.py b/dart/default.py index 6459935a68394e1e2913942bd007810a0ecf5d4f..e86e382a3b687495a0b0ce6e013cd21b90bae105 100644 --- a/dart/default.py +++ b/dart/default.py @@ -90,13 +90,7 @@ def picard(pbl): '''Initialize Picard solver ''' args = parseargs() - solver = dart.Picard(pbl, tbox.Gmres()) - solver.nthreads = args.k - solver.verbose = args.verb - solver.relTol = 1e-6 - solver.absTol = 1e-8 - solver.maxIt = 25 - solver.relax = 0.7 + solver = dart.Picard(pbl, tbox.Gmres(), nthrds=args.k, vrb=args.verb+1) return solver def newton(pbl): @@ -104,15 +98,7 @@ def newton(pbl): ''' from tbox.solvers import LinearSolver args = parseargs() - solver = dart.Newton(pbl, LinearSolver().pardiso()) - solver.nthreads = args.k - solver.verbose = args.verb - solver.relTol = 1e-6 - solver.absTol = 1e-8 - solver.maxIt = 25 - solver.lsTol = 1e-6 - solver.maxLsIt = 10 - solver.avThrsh = 1e-2 + solver = dart.Newton(pbl, LinearSolver().pardiso(), nthrds=args.k, vrb=args.verb+1) return solver def meshMorpher(msh, dim, mov, fxd = ['upstream', 'farfield', 'downstream'], fld = 'field', wk = 'wake', sym = 'symmetry'): diff --git a/dart/interfaces/__init__.py b/dart/interfaces/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..40a96afc6ff09d58a702b76e3f7dd412fe975e26 --- /dev/null +++ b/dart/interfaces/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/dart/interfaces/mphys.py b/dart/interfaces/mphys.py new file mode 100644 index 0000000000000000000000000000000000000000..64591d5aba34e11cf88bf2e789ca5f9c1239c372 --- /dev/null +++ b/dart/interfaces/mphys.py @@ -0,0 +1,647 @@ +# -*- coding: utf-8 -*- + +# Copyright 2021 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. + +## MPHYS interface +# Adrien Crovato +# +# Structure: +# - AeroBuilder +# - AeroSurfaceMesh +# - AeroGroup +# - Morpher +# - Solver +# - Loads +# - AeroCoefficients + +import numpy as np +import openmdao.api as om +from mphys.builder import Builder + +# Surface mesh +class DartMesh(om.IndepVarComp): + """Initial surface mesh of moving boundary + For compatibility, the node coordinates of connected surfaces are always 3D + + Attributes + ---------- + dim : int + Problem dimension + bnd : dart.Boundary object + Moving boundary + """ + def initialize(self): + self.options.declare('dim', desc='problem dimension') + self.options.declare('bnd', desc='moving boundary', recordable=False) + + def setup(self): + self.bnd = self.options['bnd'] + # Store all the node coordinates and add as output + x_aero0 = np.zeros(3 * len(self.bnd.nodes)) # surface node coordinates are 3D + for i in range(len(self.bnd.nodes)): + for j in range(3): + x_aero0[3 * i + j] = self.bnd.nodes[i].pos[j] + self.add_output('x_aero0', val=x_aero0, desc='initial aerodynamic surface node coordinates', tags=['mphys_coordinates']) + + def mphys_get_triangulated_surface(self): + """Triangulate the surface + Only for 2D surfaces in 3D problems + Return a list containing a point and two vectors for each surface element + o p0 + / \ + v0 /___\ v1 + """ + if self.options['dim'] == 2: + raise RuntimeError('DartMesh - cannot triangulate 1D surface!\n') + # Create list of point coordinates and vector components + p0 = [[0.]*3 for _ in range(len(self.bnd.groups[0].tag.elems))] + v0 = [[0.]*3 for _ in range(len(self.bnd.groups[0].tag.elems))] + v1 = [[0.]*3 for _ in range(len(self.bnd.groups[0].tag.elems))] + for i in range(len(self.bnd.groups[0].tag.elems)): + e = self.bnd.groups[0].tag.elems[i] + p0i = e.nodes[0].pos + v0i = e.nodes[1].pos - e.nodes[0].pos + v1i = e.nodes[2].pos - e.nodes[0].pos + for j in range(3): + p0[i][j] = p0i[j] + v0[i][j] = v0i[j] + v1[i][j] = v1i[j] + return [p0, v0, v1] + +# Mesh morphers +class DartMorpher(om.ImplicitComponent): + """Volume mesh morpher + For compatibility, the node coordinates of connected surfaces are always 3D, while the volume mesh coordinates (only used internaly) can be 2D or 3D + + Attributes + ---------- + dim : int + Problem dimension, used to set the length of the volume node coordinates vector + bnd : dart.Boundary object + Moving boundary + mrf : tbox.MshDeform object + Mesh morpher + adj : dart.Adjoint object + Adjoint solver + """ + def initialize(self): + self.options.declare('dim', desc='problem dimension') + self.options.declare('bnd', desc='moving boundary', recordable=False) + self.options.declare('mrf', desc='mesh morpher', recordable=False) + self.options.declare('adj', desc='adjoint solver', recordable=False) + + def setup(self): + self.dim = self.options['dim'] + self.bnd = self.options['bnd'] + self.mrf = self.options['mrf'] + self.adj = self.options['adj'] + # I/O + self.add_input('x_aero', shape_by_conn=True, desc='aerodynamic surface node coordinates', tags=['mphys_coupling']) + self.add_output('xv', val=np.zeros(self.dim * len(self.mrf.msh.nodes)), desc='aerodynamic volume node coordinates', tags=['mphys_coupling']) # volume node coordinates can be 2D or 3D + # Partials + # self.declare_partials(of=['xv'], wrt=['x_aero']) + + def solve_nonlinear(self, inputs, outputs): + """Deform the volume mesh + """ + # Update inputs + self.mrf.savePos() + for i in range(len(self.bnd.nodes)): + for j in range(3): + self.bnd.nodes[i].pos[j] = inputs['x_aero'][3 * i + j] + # Compute + self.mrf.deform() + # Update outputs + for i in range(len(self.mrf.msh.nodes)): + for j in range(self.dim): + outputs['xv'][self.dim * i + j] = self.mrf.msh.nodes[i].pos[j] + + def apply_nonlinear(self, inputs, outputs, residuals): + """Compute the residuals of the mesh coordinates + """ + raise NotImplementedError('DartMorpher - cannot compute the residuals!\n') + + def linearize(self, inputs, outputs, partials): + """Compute the mesh jacobian matrix + """ + self.adj.omLinearizeMesh() + + def apply_linear(self, inputs, outputs, d_inputs, d_outputs, d_residuals, mode): + """Compute the partials derivatives and perform the matrix-vector product + """ + if mode == 'rev': + if 'xv' in d_residuals: + if 'xv' in d_outputs: + d_outputs['xv'] += self.adj.omComputeJacVecRmeshMesh(d_residuals['xv']) # dX = dRx_dX^T * dRx + if 'x_aero' in d_inputs: + for i in range(len(self.bnd.nodes)): + for j in range(self.dim): + d_inputs['x_aero'][3 * i + j] -= d_residuals['xv'][self.dim * self.bnd.nodes[i].row + j] # dXs = -dX + elif mode == 'fwd': + raise NotImplementedError('DartMorpher - forward mode not implemented!\n') + + def solve_linear(self, d_outputs, d_residuals, mode): + """Solve for the partials gradients of the mesh residuals + """ + if mode == 'rev': + d_residuals['xv'] = self.adj.omSolveMesh(d_outputs['xv']) # dRx = dRx_dX^-T * dX + elif mode == 'fwd': + raise NotImplementedError('DartMorpher - forward mode not implemented!\n') + +class DartDummyMorpher(om.ExplicitComponent): + """Dummy volume mesh morpher + Allow to define and link the surface mesh coordinates to the volume mesh coordinates, in case the mesh do not deform. + For compatibility, the node coordinates of connected surfaces are always 3D, while the volume mesh coordinates (only used internaly) can be 2D or 3D + """ + def initialize(self): + self.options.declare('dim', desc='problem dimension') + self.options.declare('msh', desc='mesh data structure', recordable=False) + + def setup(self): + dim = self.options['dim'] + msh = self.options['msh'] + # Set initial values + xv = np.zeros(dim * len(msh.nodes)) # volume node coordinates can be 2D or 3D + for i in range(len(msh.nodes)): + for j in range(dim): + xv[dim * i + j] = msh.nodes[i].pos[j] + # I/O + self.add_input('x_aero', shape_by_conn=True, desc='aerodynamic surface node coordinates', tags=['mphys_coupling']) + self.add_output('xv', val=xv, desc='aerodynamic volume node coordinates', tags=['mphys_coupling']) + + def compute(self, inputs, outputs): + """DO NOT deform the volume mesh + """ + pass + + def compute_partials(self, inputs, partials): + """DO NOT compute the partials derivatives + """ + pass + +# Solver +class DartSolver(om.ImplicitComponent): + """Aerodynamic solver + + Attributes + ---------- + sol : dart.Newton object + Direct Newton solver + adj : dart.Adjoint object + Adjoint solver + """ + def initialize(self): + self.options.declare('sol', desc='direct solver', recordable=False) + self.options.declare('adj', desc='adjoint solver', recordable=False) + + def setup(self): + self.sol = self.options['sol'] + self.adj = self.options['adj'] + # I/O + self.add_input('aoa', val=0., units='rad', desc='angle of attack', tags=['mphys_input']) + self.add_input('xv', shape_by_conn=True, desc='aerodynamic volume node coordinates', tags=['mphys_coupling']) + self.add_output('phi', val=np.zeros(len(self.sol.pbl.msh.nodes)), desc='dart variables (potential)', tags=['mphys_coupling']) + # Partials + # self.declare_partials(of=['phi'], wrt=['aoa', 'xv', 'phi']) + + def solve_nonlinear(self, inputs, outputs): + """Compute the flow variables + """ + # Update inputs + # NB: the mesh is already up-to-date mesh already up-to-date because DartMorpher has already been run + self.sol.pbl.update(inputs['aoa'][0]) + # Compute + self.sol.reset() # resets ICs (slows down convergence, but increases robustness for aero-structural) + self.sol.run() + # Update outputs + outputs['phi'] = self.sol.phi + + def apply_nonlinear(self, inputs, outputs, residuals): + """Compute the residuals of the flow variables + """ + raise NotImplementedError('DartSolver - cannot compute the residuals!\n') + + def linearize(self, inputs, outputs, partials): + """Compute the flow jacobian matrix + """ + self.adj.omLinearizeFlow() + + def apply_linear(self, inputs, outputs, d_inputs, d_outputs, d_residuals, mode): + """Compute the partials derivatives and perform the matrix-vector product + """ + if mode == 'rev': + if 'phi' in d_residuals: + if 'phi' in d_outputs: + d_outputs['phi'] += self.adj.omComputeJacVecResidualFlow(d_residuals['phi']) # dPhi = dRphi_dPhi^T * dRphi + if 'aoa' in d_inputs: + d_inputs['aoa'] += self.adj.omComputeJacVecResidualAoa(d_residuals['phi']) # dAoa = dRphi_dAoa^T * dRphi + if 'xv' in d_inputs: + d_inputs['xv'] += self.adj.omComputeJacVecResidualMesh(d_residuals['phi']) # dX = dRphi_dX^T * dRphi + elif mode == 'fwd': + raise NotImplementedError('DartSolver - forward mode not implemented!\n') + + def solve_linear(self, d_outputs, d_residuals, mode): + """Solve for the partials gradients of the flow residuals + """ + if mode == 'rev': + d_residuals['phi'] = self.adj.omSolveFlow(d_outputs['phi']) # dRphi = dRphi_dPhi^-T * dPhi + elif mode == 'fwd': + raise NotImplementedError('DartSolver - forward mode not implemented!\n') + +# Aerodynamic loads +class DartLoads(om.ExplicitComponent): + """Aerodynamic loads on moving boundary + For compatibility, the forces of connected surfaces are always 3D + + Attributes + ---------- + qinf : dart.Newton object + Freestream dynamic pressure + bnd : dart.Boundary object + Moving boundary + adj : dart.Adjoint object + Adjoint solver + """ + def initialize(self): + self.options.declare('qinf', desc='freestream dynamic pressure') + self.options.declare('bnd', desc='moving boundary', recordable=False) + self.options.declare('adj', desc='adjoint solver', recordable=False) + + def setup(self): + self.qinf = self.options['qinf'] + self.bnd = self.options['bnd'] + self.adj = self.options['adj'] + # I/O + self.add_input('xv', shape_by_conn=True, desc='aerodynamic volume node coordinates', tags=['mphys_coupling']) + self.add_input('phi', shape_by_conn=True, desc='flow variables (potential)', tags=['mphys_coupling']) + self.add_output('f_aero', val=np.zeros(3 * len(self.bnd.nodes)), desc='aerodynamic loads', tags=['mphys_coupling']) + # Partials + # self.declare_partials(of=['f_aero'], wrt=['xv', 'phi']) + + def compute(self, inputs, outputs): + """Get the forces on moving boundary + """ + # NB: inputs already up-to-date because DartSolver has already been run + for i in range(len(self.bnd.nodes)): + for j in range(3): + outputs['f_aero'][3 * i + j] = self.qinf * self.bnd.nLoads[i][j] + + def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): + """Compute the partials derivatives and perform the matrix-vector product + """ + if mode == 'rev': + if 'f_aero' in d_outputs: + if 'xv' in d_inputs: + d_inputs['xv'] += self.qinf * np.asarray(self.adj.omComputeJacVecLoadsMesh(d_outputs['f_aero'], self.bnd)) # dX = dL_dX^T * dL + if 'phi' in d_inputs: + d_inputs['phi'] += self.qinf * np.asarray(self.adj.omComputeJacVecLoadsFlow(d_outputs['f_aero'], self.bnd)) # dPhi = dL_dPhi^T * dL + elif mode == 'fwd': + raise NotImplementedError('DartLoads - forward mode not implemented!\n') + +# Aerodynamic coefficients +class DartCoefficients(om.ExplicitComponent): + """Aerodynamic load coefficients for full aircraft + Also write solution to disk + + Attributes + ---------- + iscn : int + ID of scenario (default: 0) + sol : dart.Newton object + Direct Newton sovler + adj : dart.Adjoint object + Adjoint solver + """ + def initialize(self): + self.options.declare('sol', desc='direct solver', recordable=False) + self.options.declare('adj', desc='adjoint solver', recordable=False) + self.options.declare('wrtr', desc='data writer', recordable=False) + self.options.declare('morph', default=False, desc='whether the mesh can deform or not') + + def setup(self): + self.iscn = 0 # id of scenario + self.sol = self.options['sol'] + self.adj = self.options['adj'] + # I/O + self.add_input('aoa', val=0., units='rad', desc='angle of attack', tags=['mphys_input']) + self.add_input('xv', shape_by_conn=True, desc='aerodynamic volume node coordinates', tags=['mphys_coupling']) + self.add_input('phi', shape_by_conn=True, desc='flow variables (potential)', tags=['mphys_coupling']) + self.add_output('cl', val=0., desc='lift coefficient', tags=['mphys_result']) + self.add_output('cd', val=0., desc='drag coefficient', tags=['mphys_result']) + # Partials + self.declare_partials(of=['cl', 'cd'], wrt=['aoa', 'xv', 'phi']) + + def mphys_set_iscn(self, iscn): + """Set the id of the scenario + Allow to save the mesh and results in different files + """ + self.iscn = iscn + + def compute(self, inputs, outputs): + """Get the coefficients for the full aircraft + """ + # NB: inputs already up-to-date because DartSolver has already been run + # Write to disk + if self.options['morph'] and str(type(self.options['wrtr'])) == '<class \'tboxw.GmshExport\'>': + if self.iscn == 0: + self.options['wrtr'].save(self.sol.pbl.msh.name) + else: + self.options['wrtr'].save(self.sol.pbl.msh.name + '_{0:d}'.format(self.iscn)) + self.sol.save(self.options['wrtr'], self.iscn) + # Update outputs + outputs['cl'] = self.sol.Cl + outputs['cd'] = self.sol.Cd + + def compute_partials(self, inputs, partials): + """Compute the partials derivatives + """ + # Gradients wrt to angle of attack + dCfAoa = self.adj.omComputeGradientCoefficientsAoa() + partials['cl', 'aoa'] = dCfAoa[0] + partials['cd', 'aoa'] = dCfAoa[1] + # Gradients wrt to mesh + dCfMsh = self.adj.omComputeGradientCoefficientsMesh() + partials['cl', 'xv'] = dCfMsh[0] + partials['cd', 'xv'] = dCfMsh[1] + # Gradients wrt to flow variables + dCfPhi = self.adj.omComputeGradientCoefficientsFlow() + partials['cl', 'phi'] = dCfPhi[0] + partials['cd', 'phi'] = dCfPhi[1] + +# Aerodynamic group +class DartGroup(om.Group): + """Aerodynamic group + Integrate the aerodynamic computations in an aero-structural coupling procedure + + Components + ---------- + - Mesh morpher + - Direct and adjoint solvers + - Loads computation + """ + def initialize(self): + self.options.declare('qinf', desc='freestream dynamic pressure') + self.options.declare('bnd', desc='moving boundary', recordable=False) + self.options.declare('sol', desc='direct solver', recordable=False) + self.options.declare('mrf', desc='mesh morpher', recordable=False) + self.options.declare('adj', desc='adjoint solver', recordable=False) + + def setup(self): + # Components + if self.options['mrf'] is None: + self.add_subsystem('morpher', DartDummyMorpher(dim = self.options['sol'].pbl.nDim, msh = self.options['sol'].pbl.msh), promotes_inputs=['x_aero'], promotes_outputs=['xv']) + else: + self.add_subsystem('morpher', DartMorpher(dim = self.options['sol'].pbl.nDim, bnd = self.options['bnd'], mrf = self.options['mrf'], adj = self.options['adj']), promotes_inputs=['x_aero'], promotes_outputs=['xv']) + self.add_subsystem('solver', DartSolver(sol = self.options['sol'], adj = self.options['adj']), promotes_inputs=['aoa', 'xv'], promotes_outputs=['phi']) + if self.options['qinf'] != 0: + self.add_subsystem('loads', DartLoads(qinf = self.options['qinf'], bnd = self.options['bnd'], adj = self.options['adj']), promotes_inputs=['xv', 'phi'], promotes_outputs=['f_aero']) + +# Builder +class DartBuilder(Builder): + """Dart builder for MPHYS + + Attributes + ---------- + __dim : int + Dimension of the problem + __qinf : float + Freestream dynamic pressure + __msh : tbox.MshData object + Mesh data structure + __wrtr : tbox.MshExport object + Mesh writer + __mrf : tbox.MshDeform object + Mesh morpher + __pbl : dart.Probem object + Problem definition + __sol : dart.Newton object + Direct Newton solver + __adj : dart.Adjoint object + Adjoint solver + """ + def __init__(self, params, scenario='aerodynamic', task='analysis'): + """Instantiate and initialize all the solver components. + Because we do not use MPI, we do not use the initialize method. + + Parameters + ---------- + params : dict + Dictonnary of parameters for the mesh, solver, ... + scenario : string, optional + Type of scenario (available: aerodynamic, aerostructural) (default: aerodynamic) + task : string, optional + Type of task (available: analysis, optimization) (default: analysis) + """ + # Initialize + print('*'*31 + 'mphys.DartBuilder' + '*'*31 + '\n') + + # Imports + from dartflo import tbox + from dartflo.tbox import gmsh + from dartflo import dart + + # Basic checks and config + # dimension + if params['Dim'] != 2 and params['Dim'] != 3: + raise RuntimeError('Problem dimension should be 2 or 3, but ' + params['Dim'] + ' was given!\n') + else: + self.__dim = params['Dim'] + # aoa and aos + if 'AoA' in params: + alpha = params['AoA'] * np.pi / 180 + else: + alpha = 0 + if self.__dim == 2: + if 'AoS' in params and params['AoS'] != 0: + raise RuntimeError('Angle of sideslip (AoS) should be zero for 2D problems!\n') + else: + beta = 0 + if 'Symmetry' in params: + raise RuntimeError('Symmetry boundary condition cannot be used for 2D problems!\n') + else: + if 'AoS' in params: + if params['AoS'] != 0 and 'Symmetry' in params: + raise RuntimeError('Symmetry boundary condition cannot be used with nonzero angle of sideslip (AoS)!\n') + beta = params['AoS'] * np.pi / 180 + else: + beta = 0 + # save format + if params['Format'] == 'vtk': + try: + import tboxVtk + Writer = tboxVtk.VtkExport + print('VTK libraries found! Results will be saved in VTK format.\n') + except: + Writer = tbox.GmshExport + print('VTK libraries not found! Results will be saved in gmsh format.\n') + else: + Writer = tbox.GmshExport + print('Results will be saved in gmsh format.\n') + # number of threads + if 'Threads' in params: + nthrd = params['Threads'] + else: + nthrd = 1 + # verbosity + if 'Verb' in params: + verb = params['Verb'] + else: + verb = 0 + # scenario and task type + if scenario != 'aerodynamic' and scenario != 'aerostructural': + raise RuntimeError('Scenario should be aerodynamic or aerostructural, but "' + scenario + '" was given!\n') + if task != 'analysis' and task != 'optimization': + raise RuntimeError('Task should be analysis or optimization, but "' + scenario + '" was given!\n') + # dynamic pressure + if scenario == 'aerostructural': + self.__qinf = params['Qinf'] + else: + self.__qinf = 0 + + # Mesh creation + self.__msh = gmsh.MeshLoader(params['File']).execute(**params['Pars']) + # add the wake + if self.__dim == 2: + mshCrck = tbox.MshCrack(self.__msh, self.__dim) + mshCrck.setCrack(params['Wake']) + mshCrck.addBoundaries([params['Fluid'], params['Farfield'][-1], params['Wing']]) + mshCrck.run() + else: + for i in range(len(params['Wakes'])): + mshCrck = tbox.MshCrack(self.__msh, self.__dim) + mshCrck.setCrack(params['Wakes'][i]) + mshCrck.addBoundaries([params['Fluid'], params['Farfield'][-1], params['Wings'][i]]) + if 'Fuselage' in params: + mshCrck.addBoundaries([params['Fuselage']]) + if 'Symmetry' in params: + mshCrck.addBoundaries([params['Symmetry']]) + if 'WakeTips' in params: + mshCrck.setExcluded(params['WakeTips'][i]) # 2.5D computations + mshCrck.run() + # save the updated mesh in native (gmsh) format and then set the writer to the requested format + tbox.GmshExport(self.__msh).save(self.__msh.name) + del mshCrck + self.__wrtr = Writer(self.__msh) + print('\n') + + # Mesh morpher creation + if scenario == 'aerostructural' or task == 'optimization': + self.__mrf = tbox.MshDeform(self.__msh, self.__dim) + self.__mrf.nthreads = nthrd + self.__mrf.setField(params['Fluid']) + self.__mrf.addFixed(params['Farfield']) + if self.__dim == 2: + self.__mrf.addMoving([params['Wing']]) + self.__mrf.addInternal([params['Wake'], params['Wake']+'_']) + else: + if 'Fuselage' in params: + self.__mrf.addFixed(params['Fuselage']) + self.__mrf.setSymmetry(params['Symmetry'], 1) + for i in range(len(params['Wings'])): + if i == 0: + self.__mrf.addMoving([params['Wings'][i]]) # TODO boundary of interest (FSI/OPT) is always first given boundary + else: + self.__mrf.addFixed([params['Wings'][i]]) + self.__mrf.addInternal([params['Wakes'][i], params['Wakes'][i]+'_']) + self.__mrf.initialize() + else: + self.__mrf = None + + # Problem creation + self.__pbl = dart.Problem(self.__msh, self.__dim, alpha, beta, params['M_inf'], params['S_ref'], params['c_ref'], params['x_ref'], params['y_ref'], params['z_ref']) + # add the field + self.__pbl.set(dart.Medium(self.__msh, params['Fluid'], params['M_inf'], self.__dim, alpha, beta)) + # add the initial condition + self.__pbl.set(dart.Initial(self.__msh, params['Fluid'], self.__dim, alpha, beta)) + # add farfield boundary conditions (Neumann only, a DOF will be pinned automatically) + for i in range (len(params['Farfield'])): + self.__pbl.add(dart.Freestream(self.__msh, params['Farfield'][i], self.__dim, alpha, beta)) + # add solid boundaries + if self.__dim == 2: + bnd = dart.Boundary(self.__msh, [params['Wing'], params['Fluid']]) + self.__bnd = bnd + self.__pbl.add(bnd) + else: + self.__bnd = None + for bd in params['Wings']: + bnd = dart.Boundary(self.__msh, [bd, params['Fluid']]) + if self.__bnd is None: + self.__bnd = bnd # TODO boundary of interest (FSI/OPT) is always first given boundary + self.__pbl.add(bnd) + if 'Fuselage' in params: + bnd = dart.Boundary(self.__msh, [params['Fuselage'], params['Fluid']]) + self.__pbl.add(bnd) + # add Wake/Kutta boundary conditions + if self.__dim == 2: + self.__pbl.add(dart.Wake(self.__msh, [params['Wake'], params['Wake']+'_', params['Fluid']])) + self.__pbl.add(dart.Kutta(self.__msh, [params['Te'], params['Wake']+'_', params['Wing'], params['Fluid']])) + else: + for i in range(len(params['Wakes'])): + self.__pbl.add(dart.Wake(self.__msh, [params['Wakes'][i], params['Wakes'][i]+'_', params['Fluid'], params['TeTips'][i]])) + + # Direct (forward) solver creation + # NB: more solvers/options are available but we restrict the user's choice to the most efficient ones + # initialize the linear (inner) solver + if params['LSolver'] == 'PARDISO': + linsol = tbox.Pardiso() + elif params['LSolver'] == 'MUMPS': + linsol = tbox.Mumps() + elif params['LSolver'] == 'GMRES': + linsol = tbox.Gmres() + linsol.setFillFactor(params['G_fill']) if 'G_fill' in params else linsol.setFillFactor(2) + linsol.setRestart(params['G_restart']) if 'G_restart' in params else linsol.setRestart(50) + linsol.setTolerance(params['G_tol']) if 'G_tol' in params else linsol.setTolerance(1e-5) + else: + raise RuntimeError('Available linear solvers: PARDISO, MUMPS or GMRES, but ' + params['LSolver'] + ' was given!\n') + # initialize the nonlinear (outer) solver + self.__sol = dart.Newton(self.__pbl, linsol, rTol=params['Rel_tol'], aTol=params['Abs_tol'], mIt=params['Max_it'], nthrds=nthrd, vrb=verb) + + # Adjoint (reverse) solver creation + if task == 'optimization': + if params['LSolver'] == 'PARDISO': + alinsol = tbox.Pardiso() + elif params['LSolver'] == 'MUMPS': + alinsol = tbox.Mumps() + else: + alinsol = tbox.Gmres() + alinsol.setFillFactor(params['G_fill']) if 'G_fill' in params else alinsol.setFillFactor(2) + alinsol.setRestart(params['G_restart']) if 'G_restart' in params else alinsol.setRestart(50) + alinsol.setTolerance(1e-12) + self.__adj = dart.Adjoint(self.__sol, self.__mrf, alinsol) + else: + self.__adj = None + + # Finalize + print('*'*31 + 'mphys.DartBuilder' + '*'*31 + '\n') + + def get_mesh_coordinate_subsystem(self): + """Return openMDAO component to get the initial surface mesh coordinates + """ + return DartMesh(dim = self.__dim, bnd = self.__bnd) + + def get_coupling_group_subsystem(self): + """Return openMDAO group containing the morpher, solver and loads + """ + return DartGroup(qinf = self.__qinf, bnd = self.__bnd, sol = self.__sol, mrf = self.__mrf, adj = self.__adj) + + def get_post_coupling_subsystem(self): + """Return openMDAO component that computes the aero coefficients and writes data to disk + """ + return DartCoefficients(sol = self.__sol, adj = self.__adj, wrtr = self.__wrtr, morph = False if self.__mrf is None else True) + + def get_number_of_nodes(self): + """Return the number of surface nodes + """ + return len(self.__bnd.nodes) diff --git a/dart/scripts/config.py b/dart/scripts/config.py index 00b934863d7c815482e0e3ec56d4a4d84df01915..4ba055dbb1ebeff0350d2e9b53adcd5f9bf0ef7e 100644 --- a/dart/scripts/config.py +++ b/dart/scripts/config.py @@ -134,19 +134,14 @@ class Config: else: raise RuntimeError('Available linear solvers: Pardiso, GMRES, MUMPS or SparseLU, but ' + p['LSolver'] + ' was given!\n') # initialize the nonlinear (outer) solver + args = parseargs() if p['NSolver'] == 'Picard': - self.solver = dart.Picard(self.pbl, linsol) + self.solver = dart.Picard(self.pbl, linsol, rTol=p['Rel_tol'], aTol=p['Abs_tol'], mIt=p['Max_it'], nthrds=args.k, vrb=args.verb+1) self.solver.relax = p['Relaxation'] elif p['NSolver'] == 'Newton': - self.solver = dart.Newton(self.pbl, linsol) + self.solver = dart.Newton(self.pbl, linsol, rTol=p['Rel_tol'], aTol=p['Abs_tol'], mIt=p['Max_it'], nthrds=args.k, vrb=args.verb+1) self.solver.lsTol = p['LS_tol'] self.solver.maxLsIt = p['Max_it_LS'] self.solver.avThrsh = p['AV_thrsh'] else: raise RuntimeError('Available nonlinear solvers: Picard or Newton, but ' + p['NSolver'] + ' was given!\n') - args = parseargs() - self.solver.nthreads = args.k - self.solver.verbose = args.verb - self.solver.relTol = p['Rel_tol'] - self.solver.absTol = p['Abs_tol'] - self.solver.maxIt = p['Max_it'] diff --git a/dart/src/wNewton.cpp b/dart/src/wNewton.cpp index caa9e016a152a93b1b612fcbd1530c1c24ee79a6..ce523f85741754783d2a699dfb2e68b70a3b1e13 100644 --- a/dart/src/wNewton.cpp +++ b/dart/src/wNewton.cpp @@ -57,8 +57,18 @@ using namespace dart; * @brief Initialize the solver and set default parameters * @authors Adrien Crovato */ -Newton::Newton(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol) : Solver(_pbl, _linsol) +Newton::Newton(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol, + double rTol, double aTol, int mIt, + int nthrds, int vrb) : Solver(_pbl, _linsol, rTol, aTol, mIt, nthrds, vrb) { + // Additional display + std::cout << "--- Newton Solver ---\n" + << "Inner solver: " << *linsol + << "Number of threads: " << nthreads << "\n" + << "Relative tolerance: " << log10(relTol) << "\n" + << "Absolute tolerance: " << log10(absTol) << "\n" + << "Maximum iterations: " << maxIt << "\n" + << std::endl; // Additional default parameters lsTol = 1e-6; maxLsIt = 10; @@ -78,23 +88,12 @@ bool Newton::run() // Multithread tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads); - // Display solver and problem parameters - std::cout << "--- Newton solver ---\n" - << "Inner solver: " << *linsol - << std::setprecision(5) - << "Number of threads: " << nthreads << "\n" - << "Relative tolerance: " << log10(relTol) << "\n" - << "Absolute tolerance: " << log10(absTol) << "\n" - << "Maximum iterations: " << maxIt << "\n"; - std::cout << "--- Problem definition ---\n" - << std::setprecision(5) - << "Angle of attack: " << pbl->alpha * 180 / 3.14159 << " deg\n" - << "Angle of sideslip: " << pbl->beta * 180 / 3.14159 << " deg\n" - << "Mach number: " << pbl->M_inf << "\n" - << "Reference area: " << pbl->S_ref << "\n" - << "Reference length: " << pbl->c_ref << "\n" - << "Reference origin: (" << pbl->x_ref(0) << ", " << pbl->x_ref(1) << ", " << pbl->x_ref(2) << ")\n" - << std::endl; + // Display current freestream conditions + std:: cout << std::setprecision(2) + << "- Mach " << pbl->M_inf << ", " + << pbl->alpha * 180 / 3.14159 << "° AoA, " + << pbl->beta * 180 / 3.14159 << "° AoS" + << std::endl; // Initialize solver loop nIt = 0; // iteration counter @@ -199,15 +198,18 @@ bool Newton::run() // Compute aerodynamic coefficient on boundaries Solver::computeLoad(); - // Display residual - std::cout << std::fixed << std::setprecision(5); - std::cout << std::setw(8) << nIt - << std::setw(8) << linsol->getIterations() - << std::setw(8) << ls.fevalIt - << std::setw(12) << Cl - << std::setw(12) << Cd - << std::setw(15) << log10(relRes) - << std::setw(15) << log10(absRes) << "\n"; + // Display residual (at each iteration) + if (verbose > 0) + { + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(8) << nIt + << std::setw(8) << linsol->getIterations() + << std::setw(8) << ls.fevalIt + << std::setw(12) << Cl + << std::setw(12) << Cd + << std::setw(15) << log10(relRes) + << std::setw(15) << log10(absRes) << "\n"; + } // Check convergence if ((relRes < relTol || absRes < absTol) && nIt != 0) @@ -218,6 +220,19 @@ bool Newton::run() continue; } while (nIt < maxIt); + // Display residual (only last iteration) + if (verbose == 0) + { + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(8) << nIt + << std::setw(8) << linsol->getIterations() + << std::setw(8) << ls.fevalIt + << std::setw(12) << Cl + << std::setw(12) << Cd + << std::setw(15) << log10(relRes) + << std::setw(15) << log10(absRes) << "\n"; + } + // Compute field variables Solver::computeFlow(); @@ -225,7 +240,7 @@ bool Newton::run() if (relRes < relTol || absRes < absTol) { std::cout << ANSI_COLOR_GREEN << "Newton solver converged!" << ANSI_COLOR_RESET << std::endl; - if (verbose > 0) + if (verbose > 1) std::cout << "Newton solver CPU" << std::endl << tms; std::cout << std::endl; @@ -234,7 +249,7 @@ bool Newton::run() else if (std::isnan(relRes)) { std::cout << ANSI_COLOR_RED << "Newton solver diverged!" << ANSI_COLOR_RESET << std::endl; - if (verbose > 0) + if (verbose > 1) std::cout << "Newton solver CPU" << std::endl << tms; std::cout << std::endl; @@ -243,7 +258,7 @@ bool Newton::run() else { std::cout << ANSI_COLOR_YELLOW << "Newton solver not fully converged!" << ANSI_COLOR_RESET << std::endl; - if (verbose > 0) + if (verbose > 1) std::cout << "Newton solver CPU" << std::endl << tms; std::cout << std::endl; @@ -378,7 +393,7 @@ void Newton::buildJac(Eigen::SparseMatrix<double, Eigen::RowMajor> &J) J.prune(0.); J.makeCompressed(); - if (verbose > 1) + if (verbose > 2) std::cout << "J (" << J.rows() << "," << J.cols() << ") nnz=" << J.nonZeros() << "\n"; } @@ -487,7 +502,7 @@ void Newton::buildRes(Eigen::Map<Eigen::VectorXd> &R) for (auto nod : dBC->nodes) R(nod->row) = 0.; - if (verbose > 1) + if (verbose > 2) std::cout << "R (" << R.size() << ")\n"; } diff --git a/dart/src/wNewton.h b/dart/src/wNewton.h index 1a04e0cc1a07dcba7a8f616defb7d4000c4115b5..2499fac4102da25609ba2a3f2d1c67da20c83040 100644 --- a/dart/src/wNewton.h +++ b/dart/src/wNewton.h @@ -47,7 +47,7 @@ private: double muCv; ///< variable artificial viscosity scaling factor public: - Newton(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol); + Newton(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol, double rTol = 1e-6, double aTol = 1e-8, int mIt = 25, int nthrds = 1, int vrb = 1); virtual bool run() override; diff --git a/dart/src/wPicard.cpp b/dart/src/wPicard.cpp index 9d38815fc5edd291d2f6509701eed1c8c3b0e93b..d503cec5529dd7c11e7099f940475c849ed9870a 100644 --- a/dart/src/wPicard.cpp +++ b/dart/src/wPicard.cpp @@ -55,10 +55,20 @@ using namespace dart; * @brief Initialize Picard solver * @authors Adrien Crovato */ -Picard::Picard(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol) : Solver(_pbl, _linsol) +Picard::Picard(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol, + double rTol, double aTol, int mIt, + int nthrds, int vrb) : Solver(_pbl, _linsol, rTol, aTol, mIt, nthrds, vrb) { + // Additional display + std::cout << "--- Picard Solver ---\n" + << "Inner solver: " << *linsol + << "Number of threads: " << nthreads << "\n" + << "Relative tolerance: " << log10(relTol) << "\n" + << "Absolute tolerance: " << log10(absTol) << "\n" + << "Maximum iterations: " << maxIt << "\n" + << std::endl; // Additional default parameters - relax = 1.; + relax = 0.7; } /** @@ -72,23 +82,12 @@ bool Picard::run() // Multithread tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads); - // Display solver and problem parameters - std::cout << "--- Picard solver ---\n" - << "Inner solver: " << *linsol - << std::setprecision(5) - << "Number of threads: " << nthreads << "\n" - << "Relative tolerance: " << log10(relTol) << "\n" - << "Absolute tolerance: " << log10(absTol) << "\n" - << "Maximum iterations: " << maxIt << "\n"; - std::cout << "--- Problem definition ---\n" - << std::setprecision(5) - << "Angle of attack: " << pbl->alpha * 180 / 3.14159 << " deg\n" - << "Angle of sideslip: " << pbl->beta * 180 / 3.14159 << " deg\n" - << "Mach number: " << pbl->M_inf << "\n" - << "Reference area: " << pbl->S_ref << "\n" - << "Reference length: " << pbl->c_ref << "\n" - << "Reference origin: (" << pbl->x_ref(0) << ", " << pbl->x_ref(1) << ", " << pbl->x_ref(2) << ")\n" - << std::endl; + // Display current freestream conditions + std:: cout << std::setprecision(2) + << "- Mach " << pbl->M_inf << ", " + << pbl->alpha * 180 / 3.14159 << "° AoA, " + << pbl->beta * 180 / 3.14159 << "° AoS" + << std::endl; // Initialize solver loop nIt = 0; @@ -124,13 +123,16 @@ bool Picard::run() // Compute aerodynamic coefficient on boundaries Solver::computeLoad(); - // Display residual - std::cout << std::fixed << std::setprecision(5); - std::cout << std::setw(8) << nIt - << std::setw(12) << Cl - << std::setw(12) << Cd - << std::setw(15) << log10(relRes) - << std::setw(15) << log10(absRes) << "\n"; + // Display residual (at each iteration) + if (verbose > 0 || nIt == 0) + { + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(8) << nIt + << std::setw(12) << Cl + << std::setw(12) << Cd + << std::setw(15) << log10(relRes) + << std::setw(15) << log10(absRes) << "\n"; + } // Check convergence if ((relRes < relTol || absRes < absTol) && nIt != 0) @@ -151,6 +153,17 @@ bool Picard::run() phi_ = (1 - relax) * phiOld + relax * phi_; } while (nIt < maxIt); + // Display residual (only last iteration) + if (verbose == 0) + { + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(8) << nIt + << std::setw(12) << Cl + << std::setw(12) << Cd + << std::setw(15) << log10(relRes) + << std::setw(15) << log10(absRes) << "\n"; + } + // Compute field variables Solver::computeFlow(); @@ -158,7 +171,7 @@ bool Picard::run() if (relRes < relTol || absRes < absTol) { std::cout << ANSI_COLOR_GREEN << "Picard solver converged!" << ANSI_COLOR_RESET << std::endl; - if (verbose > 0) + if (verbose > 1) std::cout << "Picard solver CPU" << std::endl << tms; std::cout << std::endl; @@ -167,7 +180,7 @@ bool Picard::run() else if (std::isnan(relRes)) { std::cout << ANSI_COLOR_RED << "Picard sovler diverged!" << ANSI_COLOR_RESET << std::endl; - if (verbose > 0) + if (verbose > 1) std::cout << "Picard solver CPU" << std::endl << tms; std::cout << std::endl; @@ -176,7 +189,7 @@ bool Picard::run() else { std::cout << ANSI_COLOR_YELLOW << "Picard solver not fully converged!" << ANSI_COLOR_RESET << std::endl; - if (verbose > 0) + if (verbose > 1) std::cout << "Picard solver CPU" << std::endl << tms; std::cout << std::endl; @@ -332,7 +345,7 @@ void Picard::build(Eigen::SparseMatrix<double, Eigen::RowMajor> &A, std::vector< A.prune(0.); A.makeCompressed(); - if (verbose > 1) + if (verbose > 2) { std::cout << "A (" << A.rows() << "," << A.cols() << ") nnz=" << A.nonZeros() << "\n"; std::cout << "b (" << b.size() << ")\n"; diff --git a/dart/src/wPicard.h b/dart/src/wPicard.h index 7ff30338692e3c10bee9afb0e122578d75f4b8f9..3ebd9b88699479131a3ec4f2659adce9645bd116 100644 --- a/dart/src/wPicard.h +++ b/dart/src/wPicard.h @@ -38,7 +38,7 @@ public: double relax; ///< relaxation public: - Picard(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol); + Picard(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol, double rTol = 1e-6, double aTol = 1e-8, int mIt = 25, int nthrds = 1, int vrb = 1); virtual bool run() override; diff --git a/dart/src/wSolver.cpp b/dart/src/wSolver.cpp index 1fc5bb3ed1bd1809a6b3b4a071233046875bafa8..78c87a44d3e414d21643711fe97550fbec864d5b 100644 --- a/dart/src/wSolver.cpp +++ b/dart/src/wSolver.cpp @@ -17,6 +17,7 @@ #include "wSolver.h" #include "wProblem.h" #include "wMedium.h" +#include "wFreestream.h" #include "wAssign.h" #include "wBoundary.h" #include "wWake.h" @@ -26,12 +27,15 @@ #include "wNode.h" #include "wElement.h" #include "wTag.h" +#include "wLinearSolver.h" #include "wResults.h" #include "wMshExport.h" #include <tbb/parallel_for_each.h> #include <tbb/spin_mutex.h> +#include <iomanip> + using namespace tbox; using namespace dart; @@ -42,32 +46,30 @@ using namespace dart; * @brief Initialize the solver and perform sanity checks * @authors Adrien Crovato */ -Solver::Solver(std::shared_ptr<Problem> _pbl, std::shared_ptr<LinearSolver> _linsol) : pbl(_pbl), linsol(_linsol), nIt(0), Cl(0), Cd(0), Cs(0), Cm(0) +Solver::Solver(std::shared_ptr<Problem> _pbl, + std::shared_ptr<LinearSolver> _linsol, + double rTol, double aTol, int mIt, + int nthrds, int vrb) : pbl(_pbl), linsol(_linsol), + relTol(rTol), absTol(aTol), maxIt(mIt), + nthreads(nthrds), verbose(vrb), + nIt(0), Cl(0), Cd(0), Cs(0), Cm(0) { // Say hi - std::cout << std::endl; - std::cout << "*******************************************************************************" << std::endl; - std::cout << "** \\_/ **" << std::endl; - std::cout << "** \\_______O(_)O_______/ **" << std::endl; - std::cout << "** _______________________________ **" << std::endl; - std::cout << "** ___ __ \\__ |__ __ \\__ __/ **" << std::endl; - std::cout << "** __ / / /_ /| |_ /_/ /_ / **" << std::endl; - std::cout << "** _ /_/ /_ ___ | _, _/_ / **" << std::endl; - std::cout << "** /_____/ /_/ |_/_/ |_| /_/ **" << std::endl; - std::cout << "*******************************************************************************" << std::endl; - std::cout << "** Hi! My name is DART v1.0.0-21.09 **" << std::endl; - std::cout << "** Adrien Crovato **" << std::endl; - std::cout << "** ULiege 2018-2021 **" << std::endl; - std::cout << "*******************************************************************************" << std::endl + std::cout << "*******************************************************************************\n" + << "** \\_/ **\n" + << "** \\_______O(_)O_______/ **\n" + << "** _______________________________ **\n" + << "** ___ __ \\__ |__ __ \\__ __/ **\n" + << "** __ / / /_ /| |_ /_/ /_ / **\n" + << "** _ /_/ /_ ___ | __ _/_ / **\n" + << "** /_____/ /_/ |_/_/ |_| /_/ **\n" + << "*******************************************************************************\n" + << "** Hi! My name is DART v1.1.0-21.09 **\n" + << "** Adrien Crovato **\n" + << "** ULiege 2018-2021 **\n" + << "*******************************************************************************\n" << std::endl; - // Default parameters - nthreads = 1; - verbose = 0; - maxIt = 50; - relTol = 1e-6; - absTol = 1e-8; - // Check the problem, pin a degree of freedom if necessary, and update element memory pbl->check(); pbl->pin(); @@ -88,9 +90,7 @@ Solver::Solver(std::shared_ptr<Problem> _pbl, std::shared_ptr<LinearSolver> _lin Cp.resize(pbl->msh->nodes.size(), 0.); // Apply Initial and Dirichlet boundary conditions - pbl->iIC->apply(phi); - for (auto dBC : pbl->dBCs) - dBC->apply(phi); + this->reset(); // Kutta condition step 1: equality of normal flux through the wake // -> directly assemble upper wake contributions on lower wake rows, and leave the upper wake rows to zero @@ -98,17 +98,54 @@ Solver::Solver(std::shared_ptr<Problem> _pbl, std::shared_ptr<LinearSolver> _lin for (auto wake : pbl->wBCs) for (auto p : wake->nodMap) rows[p.first->row] = p.second->row; + + // Display configuration + std::cout << "--- Mesh data ---\n" + << "Number of nodes: " << pbl->msh->nodes.size() << "\n" + << "Number of elements: " << pbl->msh->elems.size() << "\n"; + std::cout << "--- Physical groups ---\n" + << "Fluid on: " << *pbl->medium->tag << "\n"; + for (auto bnd : pbl->fBCs) + std::cout << "Freestream on " << *bnd->tag << "\n"; + for (auto bnd : pbl->dBCs) + std::cout << "Dirichlet on " << *bnd->tag << "\n"; + for (auto bnd : pbl->wBCs) + std::cout << "Wake on " << *bnd->groups[0]->tag << "\n"; + for (auto bnd : pbl->bnds) + std::cout << "Boundary on " << *bnd->groups[0]->tag << "\n"; + std::cout << "--- Freestream conditions ---\n" + << std::setprecision(5) + << "Angle of attack: " << pbl->alpha * 180 / 3.14159 << " deg\n" + << "Angle of sideslip: " << pbl->beta * 180 / 3.14159 << " deg\n" + << "Mach number: " << pbl->M_inf << "\n"; + std::cout << "--- Reference data ---\n" + << "Reference area: " << pbl->S_ref << "\n" + << "Reference length: " << pbl->c_ref << "\n" + << "Reference origin: (" << pbl->x_ref(0) << ", " << pbl->x_ref(1) << ", " << pbl->x_ref(2) << ")" + << std::endl; } Solver::~Solver() { // Say bye - std::cout << std::endl; - std::cout << "*******************************************************************************" << std::endl; - std::cout << "** \\_/ **" << std::endl; - std::cout << "** \\_______O(_)O_______/ **" << std::endl; - std::cout << "** ! ! **" << std::endl; - std::cout << "*******************************************************************************" << std::endl; - std::cout << std::endl; + std::cout << "\n" + << "*******************************************************************************\n" + << "** \\_/ **\n" + << "** \\_______O(_)O_______/ **\n" + << "** ! ! **\n" + << "*******************************************************************************\n" + << std::endl; +} + +/** + * @brief Set or reset initial conditions + */ +void Solver::reset() +{ + // ICs + pbl->iIC->apply(phi); + // BCs + for (auto dBC : pbl->dBCs) + dBC->apply(phi); } /** @@ -150,6 +187,7 @@ void Solver::save(MshExport *mshWriter, int n) for (auto sur : pbl->bnds) sur->save(sur->groups[0]->tag->name, results); } + std::cout << std::endl; } /** diff --git a/dart/src/wSolver.h b/dart/src/wSolver.h index 0ccac4a701bf13f3b6352cb6287b3dc2d2af349f..f04d1bd5cfd74399e6058ec7614cb727897386d0 100644 --- a/dart/src/wSolver.h +++ b/dart/src/wSolver.h @@ -39,10 +39,10 @@ public: std::shared_ptr<Problem> pbl; ///< problem definition std::shared_ptr<tbox::LinearSolver> linsol; ///< linear solver - int nthreads; ///< number of threads for the execution double relTol; ///< relative tolerance on the residual double absTol; ///< absolute tolerance on the residual int maxIt; ///< max number of iterations + int nthreads; ///< number of threads for the execution int verbose; ///< display more info int nIt; ///< number of iterations @@ -63,9 +63,10 @@ protected: std::vector<int> rows; ///< unknown nodal index public: - Solver(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol); + Solver(std::shared_ptr<Problem> _pbl, std::shared_ptr<tbox::LinearSolver> _linsol, double rTol, double aTol, int mIt, int nthrds, int vrb); virtual ~Solver(); + void reset(); virtual bool run(); void save(tbox::MshExport *mshWriter, int n = 0);