From 15605019dcc313d52b7714bad2ccdb1fba8118a9 Mon Sep 17 00:00:00 2001
From: acrovato <a.crovato@uliege.be>
Date: Wed, 27 Apr 2022 14:57:42 +0200
Subject: [PATCH] Add verbosity level. Split coefficients and writing in 2
 OpenMDAO components.

---
 dart/api/core.py        |  4 +-
 dart/api/mphys.py       | 81 ++++++++++++++++++++++++++++++++---------
 dart/benchmark/onera.py |  4 +-
 dart/cases/coyote.py    |  2 +-
 dart/cases/n0012.py     |  2 +-
 dart/cases/n64A410.py   |  2 +-
 dart/cases/rae2822.py   |  2 +-
 dart/cases/wbht.py      |  2 +-
 dart/cases/wht.py       |  2 +-
 dart/default.py         |  4 +-
 dart/src/wAdjoint.cpp   | 17 ++++++++-
 dart/src/wNewton.cpp    | 76 +++++++++++++++++++++-----------------
 dart/src/wPicard.cpp    | 52 +++++++++++++++-----------
 dart/src/wSolver.cpp    | 10 +++--
 ext/amfe                |  2 +-
 15 files changed, 171 insertions(+), 91 deletions(-)

diff --git a/dart/api/core.py b/dart/api/core.py
index 248a6a1..d7e6c9a 100644
--- a/dart/api/core.py
+++ b/dart/api/core.py
@@ -106,7 +106,7 @@ def initDart(cfg, scenario='aerodynamic', task='analysis'):
     if 'Verb' in cfg:
         verb = cfg['Verb']
     else:
-        verb = 0
+        verb = 1
     # scenario and task type
     if scenario != 'aerodynamic' and scenario != 'aerostructural':
         raise RuntimeError('Scenario should be aerodynamic or aerostructural, but "' + scenario + '" was given!\n')
@@ -146,7 +146,7 @@ def initDart(cfg, scenario='aerodynamic', task='analysis'):
 
     # Mesh morpher creation
     if scenario == 'aerostructural' or task == 'optimization':
-        _mrf = tbox.MshDeform(_msh, tbox.Gmres(1, 1e-6, 30, 1e-8), _dim, nthrds=nthrd)
+        _mrf = tbox.MshDeform(_msh, tbox.Gmres(1, 1e-6, 30, 1e-8), _dim, nthrds=nthrd, vrb=verb)
         _mrf.setField(cfg['Fluid'])
         _mrf.addFixed(cfg['Farfield'])
         if _dim == 2:
diff --git a/dart/api/mphys.py b/dart/api/mphys.py
index e59d613..9a121c5 100644
--- a/dart/api/mphys.py
+++ b/dart/api/mphys.py
@@ -335,12 +335,9 @@ class DartLoads(om.ExplicitComponent):
 # 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
@@ -349,12 +346,8 @@ class DartCoefficients(om.ExplicitComponent):
     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('iscn', default=0, desc='ID of the scenario')
-        self.options.declare('morph', default=False, desc='whether the mesh can deform or not')
 
     def setup(self):
-        self.iscn = self.options['iscn']
         self.sol = self.options['sol']
         self.adj = self.options['adj']
         # I/O
@@ -370,13 +363,6 @@ class DartCoefficients(om.ExplicitComponent):
         """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 self.options['wrtr'].type() == 1:
-            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
@@ -397,9 +383,46 @@ class DartCoefficients(om.ExplicitComponent):
         partials['cl', 'phi'] = dCfPhi[0]
         partials['cd', 'phi'] = dCfPhi[1]
 
-# Aerodynamic group
+# Aerodynamic writer
+class DartWriter(om.ExplicitComponent):
+    """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('wrtr', desc='data writer', recordable=False)
+        self.options.declare('iscn', default=0, desc='ID of the scenario')
+        self.options.declare('morph', default=False, desc='whether the mesh can deform or not')
+
+    def setup(self):
+        self.iscn = self.options['iscn']
+        self.morph = self.options['morph']
+        self.sol = self.options['sol']
+        self.wrtr = self.options['wrtr']
+
+    def compute(self, inputs, outputs):
+        """Write to disk
+        """
+        # Write mesh to disk (.msh only)
+        if self.morph and self.wrtr.type() == 1:
+            if self.iscn == 0:
+                self.wrtr.save(self.sol.pbl.msh.name)
+            else:
+                self.wrtr.save(self.sol.pbl.msh.name + '_{0:d}'.format(self.iscn))
+        # Write solution to disk
+        self.sol.save(self.wrtr, self.iscn)
+
+# Aerodynamic coupling group
 class DartGroup(om.Group):
-    """Aerodynamic group
+    """Aerodynamic coupling group
     Integrate the aerodynamic computations in an aero-structural coupling procedure
 
     Components
@@ -426,6 +449,28 @@ class DartGroup(om.Group):
         if self.options['qinf'] is not None:
             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'])
 
+# Aerodynamic post-coupling group
+class DartPostGroup(om.Group):
+    """Aerodynamic post-coupling group
+    Update the aerodynamic load coefficients and write solution to disk
+
+    Components
+    ----------
+    - Coefficients
+    - Writer
+    """
+    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('iscn', default=0, desc='ID of the scenario')
+        self.options.declare('morph', default=False, desc='whether the mesh can deform or not')
+
+    def setup(self):
+        # Components
+        self.add_subsystem('coeff', DartCoefficients(sol = self.options['sol'], adj = self.options['adj']), promotes_inputs=['aoa', 'xv', 'phi'], promotes_outputs=['cl', 'cd'])
+        self.add_subsystem('writer', DartWriter(sol = self.options['sol'], wrtr = self.options['wrtr'], iscn = self.options['iscn'], morph = self.options['morph']))
+
 # Builder
 class DartBuilder(Builder):
     """Dart builder for MPHYS
@@ -511,9 +556,9 @@ class DartBuilder(Builder):
         return DartGroup(qinf = self.__qinf, bnd = self.__bnd, sol = self.__sol, mrf = self.__mrf, adj = self.__adj, raiseError = self.__raiseError)
 
     def get_post_coupling_subsystem(self):
-        """Return openMDAO component that computes the aero coefficients and writes data to disk
+        """Return openMDAO group that computes the aero coefficients and writes data to disk
         """
-        return DartCoefficients(sol = self.__sol, adj = self.__adj, wrtr = self.__wrtr, iscn = self.__sid, morph = False if self.__mrf is None else True)
+        return DartPostGroup(sol = self.__sol, adj = self.__adj, wrtr = self.__wrtr, iscn = self.__sid, morph = False if self.__mrf is None else True)
 
     def get_number_of_nodes(self):
         """Return the number of surface nodes
diff --git a/dart/benchmark/onera.py b/dart/benchmark/onera.py
index b0f9c21..0608778 100644
--- a/dart/benchmark/onera.py
+++ b/dart/benchmark/onera.py
@@ -34,10 +34,10 @@ def newton(pbl):
     # Pardiso and GMRES should give similar perfs
     k = parseargs().k
     try:
-        newton = dart.Newton(pbl, tbox.Pardiso(), nthrds=k, vrb=2)
+        newton = dart.Newton(pbl, tbox.Pardiso(), nthrds=k, vrb=3)
     except:
         gmres = tbox.Gmres(2, 1e-6, 50, 1e-5)
-        newton = dart.Newton(pbl, gmres, nthrds=k, vrb=2)
+        newton = dart.Newton(pbl, gmres, nthrds=k, vrb=3)
     return newton
 
 def main():
diff --git a/dart/cases/coyote.py b/dart/cases/coyote.py
index fc493e3..aa91bf5 100644
--- a/dart/cases/coyote.py
+++ b/dart/cases/coyote.py
@@ -30,7 +30,7 @@ def getParam():
     # Arguments
     args = parseargs()
     p['Threads'] = args.k
-    p['Verb'] = args.verb
+    p['Verb'] = args.verb + 1
     # Specific
     scl = 2 # scaling factor for lifting surface mesh size
     wrtems = scl*0.036 # wing root trailing edge mesh size
diff --git a/dart/cases/n0012.py b/dart/cases/n0012.py
index ed9f3da..474b451 100644
--- a/dart/cases/n0012.py
+++ b/dart/cases/n0012.py
@@ -26,7 +26,7 @@ def getParam():
     # Arguments
     args = parseargs()
     p['Threads'] = args.k
-    p['Verb'] = args.verb
+    p['Verb'] = args.verb + 1
     # Input/Output
     p['File'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../models/n0012.geo') # Input file containing the model
     p['Pars'] = {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1.0, 'msTe' : 0.01, 'msLe' : 0.005} # Parameters for input file model
diff --git a/dart/cases/n64A410.py b/dart/cases/n64A410.py
index d033dcb..b6a0323 100644
--- a/dart/cases/n64A410.py
+++ b/dart/cases/n64A410.py
@@ -26,7 +26,7 @@ def getParam():
     # Arguments
     args = parseargs()
     p['Threads'] = args.k
-    p['Verb'] = args.verb
+    p['Verb'] = args.verb + 1
     # Input/Output
     p['File'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../models/n64A410.geo') # Input file containing the model
     p['Pars'] = {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1.0, 'msTe' : 0.01, 'msLe' : 0.005} # Parameters for input file model
diff --git a/dart/cases/rae2822.py b/dart/cases/rae2822.py
index 1b9c6cb..b18b12e 100644
--- a/dart/cases/rae2822.py
+++ b/dart/cases/rae2822.py
@@ -26,7 +26,7 @@ def getParam():
     # Arguments
     args = parseargs()
     p['Threads'] = args.k
-    p['Verb'] = args.verb
+    p['Verb'] = args.verb + 1
     # Input/Output
     p['File'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../models/rae2822.geo') # Input file containing the model
     p['Pars'] = {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1.0, 'msTe' : 0.01, 'msLe' : 0.005} # Parameters for input file model
diff --git a/dart/cases/wbht.py b/dart/cases/wbht.py
index 73a8f1d..0e6a5b0 100644
--- a/dart/cases/wbht.py
+++ b/dart/cases/wbht.py
@@ -44,7 +44,7 @@ def getParam():
     # Arguments
     args = parseargs()
     p['Threads'] = args.k
-    p['Verb'] = args.verb
+    p['Verb'] = args.verb + 1
     # Input/Output
     p['File'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../models/wbht.geo') # Input file containing the model
     p['Pars'] = {'msLeW0' : wrlems, 'msTeW0' : wrtems,
diff --git a/dart/cases/wht.py b/dart/cases/wht.py
index dd0fc7d..1f216c5 100644
--- a/dart/cases/wht.py
+++ b/dart/cases/wht.py
@@ -39,7 +39,7 @@ def getParam():
     # Arguments
     args = parseargs()
     p['Threads'] = args.k
-    p['Verb'] = args.verb
+    p['Verb'] = args.verb + 1
     # Input/Output
     p['File'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../models/wht.geo') # Input file containing the model
     p['Pars'] = {'msLeW0' : wrlems, 'msTeW0' : wrtems,
diff --git a/dart/default.py b/dart/default.py
index caf91dc..ba0be48 100644
--- a/dart/default.py
+++ b/dart/default.py
@@ -150,7 +150,7 @@ def picard(pbl):
         problem formulation
     """
     args = parseargs()
-    solver = dart.Picard(pbl, tbox.Gmres(1, 1e-6, 30, 1e-8), nthrds=args.k, vrb=args.verb+1)
+    solver = dart.Picard(pbl, tbox.Gmres(1, 1e-6, 30, 1e-8), nthrds=args.k, vrb=args.verb+2)
     return solver
 
 def newton(pbl):
@@ -163,7 +163,7 @@ def newton(pbl):
     """
     from tbox.solvers import LinearSolver
     args = parseargs()
-    solver = dart.Newton(pbl, LinearSolver().pardiso(), nthrds=args.k, vrb=args.verb+1)
+    solver = dart.Newton(pbl, LinearSolver().pardiso(), nthrds=args.k, vrb=args.verb+2)
     return solver
 
 def morpher(msh, dim, mov, fxd = ['upstream', 'farfield', 'downstream'], fld = 'field', wk = 'wake', sym = 'symmetry'):
diff --git a/dart/src/wAdjoint.cpp b/dart/src/wAdjoint.cpp
index 7023219..e48ce29 100644
--- a/dart/src/wAdjoint.cpp
+++ b/dart/src/wAdjoint.cpp
@@ -101,6 +101,14 @@ void Adjoint::run()
     // Init
     tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads);
 
+    // Display current freestream conditions
+    if (verbose > 0)
+        std::cout << std::fixed << std::setprecision(2)
+                  << "Computing gradients for Mach " << sol->pbl->M_inf << ", "
+                  << sol->pbl->alpha * 180 / 3.14159 << "deg AoA, "
+                  << sol->pbl->beta * 180 / 3.14159 << "deg AoS"
+                  << std::endl;
+
     // Compute partial gradients of flow residuals and solve flow adjoint
     tms["0-AdjFlo"].start();
     this->linearizeFlow();                                                            // dRu/dU, dRu/dA, dRu/dX
@@ -180,7 +188,7 @@ void Adjoint::run()
               << std::setw(15) << std::right << Eigen::Map<Eigen::VectorXd>(lamCdMsh.data(), lamCdMsh.size()).norm() << std::endl;
 
     // Display timers
-    if (verbose > 1)
+    if (verbose > 2)
         std::cout << "Adjoint solver CPU" << std::endl
                   << tms;
     std::cout << std::endl;
@@ -719,7 +727,12 @@ void Adjoint::buildGradientCoefficientsMesh(Eigen::RowVectorXd &dCl, Eigen::RowV
 void Adjoint::save(MshExport *mshWriter, int n)
 {
     // Write files
-    std::cout << "Saving files... " << std::endl;
+    std::cout << "Saving files "
+              << std::setprecision(2)
+              << "(Mach " << sol->pbl->M_inf << ", "
+              << sol->pbl->alpha * 180 / 3.14159 << " deg AoA, "
+              << sol->pbl->beta * 180 / 3.14159 << " deg AoS)"
+              << std::endl;
     // setup results
     Results results;
     results.scalars_at_nodes["lambdaClPhi"] = &lamClFlo;
diff --git a/dart/src/wNewton.cpp b/dart/src/wNewton.cpp
index bd6560e..1d53b14 100644
--- a/dart/src/wNewton.cpp
+++ b/dart/src/wNewton.cpp
@@ -88,11 +88,12 @@ STATUS Newton::run()
     tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads);
 
     // Display current freestream conditions
-    std::cout << std::setprecision(2)
-              << "- Mach " << pbl->M_inf << ", "
-              << pbl->alpha * 180 / 3.14159 << " deg AoA, "
-              << pbl->beta * 180 / 3.14159 << " deg AoS"
-              << std::endl;
+    if (verbose > 0)
+        std::cout << std::fixed << std::setprecision(2)
+                  << "Solving flow for Mach " << pbl->M_inf << ", "
+                  << pbl->alpha * 180 / 3.14159 << "deg AoA, "
+                  << pbl->beta * 180 / 3.14159 << "deg AoS"
+                  << std::endl;
 
     // Initialize solver loop
     nIt = 0;           // iteration counter
@@ -118,21 +119,24 @@ STATUS Newton::run()
     ls.set(maxLsIt, lsTol, verbose);
 
     // Display residual
-    std::cout << std::setw(8) << "N_Iter"
-              << std::setw(8) << "L_Iter"
-              << std::setw(8) << "f_eval"
-              << std::setw(12) << "Cl"
-              << std::setw(12) << "Cd"
-              << std::setw(15) << "Rel_Res[phi]"
-              << std::setw(15) << "Abs_Res[phi]" << std::endl;
-    std::cout << std::fixed << std::setprecision(5);
-    std::cout << std::setw(8) << nIt
-              << std::setw(8) << 0
-              << std::setw(8) << 1
-              << std::setw(12) << "-"
-              << std::setw(12) << "-"
-              << std::setw(15) << log10(relRes)
-              << std::setw(15) << log10(absRes) << "\n";
+    if (verbose > 0)
+    {
+        std::cout << std::setw(8) << "N_Iter"
+                  << std::setw(8) << "L_Iter"
+                  << std::setw(8) << "f_eval"
+                  << std::setw(12) << "Cl"
+                  << std::setw(12) << "Cd"
+                  << std::setw(15) << "Rel_Res[phi]"
+                  << std::setw(15) << "Abs_Res[phi]" << std::endl;
+        std::cout << std::fixed << std::setprecision(5);
+        std::cout << std::setw(8) << nIt
+                  << std::setw(8) << 0
+                  << std::setw(8) << 1
+                  << std::setw(12) << "-"
+                  << std::setw(12) << "-"
+                  << std::setw(15) << log10(relRes)
+                  << std::setw(15) << log10(absRes) << "\n";
+    }
 
     do
     {
@@ -198,7 +202,7 @@ STATUS Newton::run()
         Solver::computeLoad();
 
         // Display residual (at each iteration)
-        if (verbose > 0)
+        if (verbose > 1)
         {
             std::cout << std::fixed << std::setprecision(5);
             std::cout << std::setw(8) << nIt
@@ -220,7 +224,7 @@ STATUS Newton::run()
     } while (nIt < maxIt);
 
     // Display residual (only last iteration)
-    if (verbose == 0)
+    if (verbose == 1)
     {
         std::cout << std::fixed << std::setprecision(5);
         std::cout << std::setw(8) << nIt
@@ -238,29 +242,35 @@ STATUS Newton::run()
     // Check the solution
     if (relRes < relTol || absRes < absTol)
     {
-        std::cout << ANSI_COLOR_GREEN << "Newton solver converged!" << ANSI_COLOR_RESET << std::endl;
-        if (verbose > 1)
+        if (verbose > 0)
+            std::cout << ANSI_COLOR_GREEN << "Newton solver converged!" << ANSI_COLOR_RESET << std::endl;
+        if (verbose > 2)
             std::cout << "Newton solver CPU" << std::endl
                       << tms;
-        std::cout << std::endl;
+        if (verbose > 0)
+            std::cout << std::endl;
         return STATUS::CONVERGED;
     }
     else if (std::isnan(relRes))
     {
-        std::cout << ANSI_COLOR_RED << "Newton solver diverged!" << ANSI_COLOR_RESET << std::endl;
-        if (verbose > 1)
+        if (verbose > 0)
+            std::cout << ANSI_COLOR_RED << "Newton solver diverged!" << ANSI_COLOR_RESET << std::endl;
+        if (verbose > 2)
             std::cout << "Newton solver CPU" << std::endl
                       << tms;
-        std::cout << std::endl;
+        if (verbose > 0)
+            std::cout << std::endl;
         return STATUS::FAILED;
     }
     else
     {
-        std::cout << ANSI_COLOR_YELLOW << "Newton solver not fully converged!" << ANSI_COLOR_RESET << std::endl;
-        if (verbose > 1)
+        if (verbose > 0)
+            std::cout << ANSI_COLOR_YELLOW << "Newton solver not fully converged!" << ANSI_COLOR_RESET << std::endl;
+        if (verbose > 2)
             std::cout << "Newton solver CPU" << std::endl
                       << tms;
-        std::cout << std::endl;
+        if (verbose > 0)
+            std::cout << std::endl;
         return STATUS::MAXIT;
     }
 }
@@ -394,7 +404,7 @@ void Newton::buildJac(Eigen::SparseMatrix<double, Eigen::RowMajor> &J)
     J.prune(0.);
     J.makeCompressed();
 
-    if (verbose > 2)
+    if (verbose > 3)
         std::cout << "J (" << J.rows() << "," << J.cols() << ") nnz=" << J.nonZeros() << "\n";
 }
 
@@ -504,7 +514,7 @@ void Newton::buildRes(Eigen::Map<Eigen::VectorXd> &R)
         for (auto nod : dBC->nodes)
             R(nod->row) = 0.;
 
-    if (verbose > 2)
+    if (verbose > 3)
         std::cout << "R (" << R.size() << ")\n";
 }
 
diff --git a/dart/src/wPicard.cpp b/dart/src/wPicard.cpp
index 5d56751..15177eb 100644
--- a/dart/src/wPicard.cpp
+++ b/dart/src/wPicard.cpp
@@ -81,11 +81,12 @@ STATUS Picard::run()
     tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads);
 
     // Display current freestream conditions
-    std::cout << std::setprecision(2)
-              << "- Mach " << pbl->M_inf << ", "
-              << pbl->alpha * 180 / 3.14159 << " deg AoA, "
-              << pbl->beta * 180 / 3.14159 << " deg AoS"
-              << std::endl;
+    if (verbose > 0)
+        std::cout << std::fixed << std::setprecision(2)
+                  << "Solving flow for Mach " << pbl->M_inf << ", "
+                  << pbl->alpha * 180 / 3.14159 << "deg AoA, "
+                  << pbl->beta * 180 / 3.14159 << "deg AoS"
+                  << std::endl;
 
     // Initialize solver loop
     nIt = 0;
@@ -95,11 +96,12 @@ STATUS Picard::run()
     Eigen::Map<Eigen::VectorXd> phi_(phi.data(), phi.size()), rPhi_(rPhi.data(), rPhi.size());
     Eigen::VectorXd phiOld(phi.size());
 
-    std::cout << std::setw(8) << "N_Iter"
-              << std::setw(12) << "Cl"
-              << std::setw(12) << "Cd"
-              << std::setw(15) << "Rel_Res[phi]"
-              << std::setw(15) << "Abs_Res[phi]" << std::endl;
+    if (verbose > 0)
+        std::cout << std::setw(8) << "N_Iter"
+                  << std::setw(12) << "Cl"
+                  << std::setw(12) << "Cd"
+                  << std::setw(15) << "Rel_Res[phi]"
+                  << std::setw(15) << "Abs_Res[phi]" << std::endl;
 
     do
     {
@@ -122,7 +124,7 @@ STATUS Picard::run()
         Solver::computeLoad();
 
         // Display residual (at each iteration)
-        if (verbose > 0 || nIt == 0)
+        if (verbose > 1 || (verbose == 1 && nIt == 0))
         {
             std::cout << std::fixed << std::setprecision(5);
             std::cout << std::setw(8) << nIt
@@ -152,7 +154,7 @@ STATUS Picard::run()
     } while (nIt < maxIt);
 
     // Display residual (only last iteration)
-    if (verbose == 0)
+    if (verbose == 1)
     {
         std::cout << std::fixed << std::setprecision(5);
         std::cout << std::setw(8) << nIt
@@ -168,29 +170,35 @@ STATUS Picard::run()
     // Check the solution
     if (relRes < relTol || absRes < absTol)
     {
-        std::cout << ANSI_COLOR_GREEN << "Picard solver converged!" << ANSI_COLOR_RESET << std::endl;
-        if (verbose > 1)
+        if (verbose > 0)
+            std::cout << ANSI_COLOR_GREEN << "Picard solver converged!" << ANSI_COLOR_RESET << std::endl;
+        if (verbose > 2)
             std::cout << "Picard solver CPU" << std::endl
                       << tms;
-        std::cout << std::endl;
+        if (verbose > 0)
+            std::cout << std::endl;
         return STATUS::CONVERGED;
     }
     else if (std::isnan(relRes))
     {
-        std::cout << ANSI_COLOR_RED << "Picard sovler diverged!" << ANSI_COLOR_RESET << std::endl;
-        if (verbose > 1)
+        if (verbose > 0)
+            std::cout << ANSI_COLOR_RED << "Picard sovler diverged!" << ANSI_COLOR_RESET << std::endl;
+        if (verbose > 2)
             std::cout << "Picard solver CPU" << std::endl
                       << tms;
-        std::cout << std::endl;
+        if (verbose > 0)
+            std::cout << std::endl;
         return STATUS::FAILED;
     }
     else
     {
-        std::cout << ANSI_COLOR_YELLOW << "Picard solver not fully converged!" << ANSI_COLOR_RESET << std::endl;
-        if (verbose > 1)
+        if (verbose > 0)
+            std::cout << ANSI_COLOR_YELLOW << "Picard solver not fully converged!" << ANSI_COLOR_RESET << std::endl;
+        if (verbose > 2)
             std::cout << "Picard solver CPU" << std::endl
                       << tms;
-        std::cout << std::endl;
+        if (verbose > 0)
+            std::cout << std::endl;
         return STATUS::MAXIT;
     }
 }
@@ -345,7 +353,7 @@ void Picard::build(Eigen::SparseMatrix<double, Eigen::RowMajor> &A, std::vector<
     A.prune(0.);
     A.makeCompressed();
 
-    if (verbose > 2)
+    if (verbose > 3)
     {
         std::cout << "A (" << A.rows() << "," << A.cols() << ") nnz=" << A.nonZeros() << "\n";
         std::cout << "b (" << b.size() << ")\n";
diff --git a/dart/src/wSolver.cpp b/dart/src/wSolver.cpp
index 68d2b61..e16f839 100644
--- a/dart/src/wSolver.cpp
+++ b/dart/src/wSolver.cpp
@@ -164,7 +164,12 @@ STATUS Solver::run()
 void Solver::save(MshExport *mshWriter, int n)
 {
     // Write files
-    std::cout << "Saving files... " << std::endl;
+    std::cout << "Saving files "
+              << std::fixed << std::setprecision(2)
+              << "(Mach " << pbl->M_inf << ", "
+              << pbl->alpha * 180 / 3.14159 << "deg AoA, "
+              << pbl->beta * 180 / 3.14159 << "deg AoS)"
+              << std::endl;
     // setup results
     Results results;
     results.scalars_at_nodes["phi"] = &phi;
@@ -187,7 +192,6 @@ void Solver::save(MshExport *mshWriter, int n)
         for (auto bnd : pbl->bodies)
             bnd->save(bnd->groups[0]->tag->name, results);
     }
-    std::cout << std::endl;
 }
 
 /**
@@ -279,6 +283,6 @@ void Solver::computeFlow()
     });
 
     // Check maximum Mach number
-    if (*std::max_element(M.begin(), M.end()) >= 1.25)
+    if (*std::max_element(M.begin(), M.end()) >= 1.25 && verbose > 0)
         std::cout << ANSI_COLOR_YELLOW << "Max. Mach greater than 1.25!" << ANSI_COLOR_RESET << std::endl;
 }
diff --git a/ext/amfe b/ext/amfe
index 0ec92c7..6da1452 160000
--- a/ext/amfe
+++ b/ext/amfe
@@ -1 +1 @@
-Subproject commit 0ec92c71eb96c949527e788cdbf3734dcd43aa8a
+Subproject commit 6da145263ba67c32ad3c6b8af64e83f3a9b2913e
-- 
GitLab