diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 2dcd241f9de..5bda4780d23 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -1126,6 +1126,8 @@ struct CMGOptions { su2double MG_Smooth_StagnationTol{0.0}; /*!< \brief Stagnation early exit: stop if current_rms >= prev_rms * tol. 0 = disabled. */ bool MG_Implicit_Lines{false}; /*!< \brief Enable implicit-lines agglomeration from walls. */ unsigned long MG_Implicit_Lines_MaxLength{20}; /*!< \brief Maximum nodes on a wall-normal implicit line (including wall seed). */ + bool MG_Implicit_Lines_Isotropic{false}; /*!< \brief Use isotropic (vs anisotropic) agglomeration along implicit lines. */ + unsigned long MG_Startup_Iter{100}; /*!< \brief Number of iterations on coarsest mesh during FMG startup phase. */ }; /*! diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index a9ca92ab145..2d68a229b27 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -2069,6 +2069,10 @@ void CConfig::SetConfig_Options() { addBoolOption("MG_IMPLICIT_LINES", MGOptions.MG_Implicit_Lines, false); /*!\brief MG_IMPLICIT_LINES_MAX_LENGTH\n DESCRIPTION: Maximum number of nodes on a wall-normal implicit agglomeration line (including the wall seed node). DEFAULT: 20 \ingroup Config*/ addUnsignedLongOption("MG_IMPLICIT_LINES_MAX_LENGTH", MGOptions.MG_Implicit_Lines_MaxLength, 20); + /*!\brief MG_IMPLICIT_LINES_ISOTROPIC\n DESCRIPTION: Use isotropic agglomeration along implicit lines (4 cells per coarse CV) instead of anisotropic (2 cells per coarse CV). DEFAULT: NO \ingroup Config*/ + addBoolOption("MG_IMPLICIT_LINES_ISOTROPIC", MGOptions.MG_Implicit_Lines_Isotropic, false); + /*!\brief MG_STARTUP_ITER\n DESCRIPTION: Number of iterations on the coarsest mesh during Full Multigrid (FMG) startup phase before advancing to finer meshes. DEFAULT: 100 \ingroup Config*/ + addUnsignedLongOption("MG_STARTUP_ITER", MGOptions.MG_Startup_Iter, 100); /*!\brief MG_CFL_SCALING\n DESCRIPTION: Per-level CFL scaling factors for coarse MG levels. Entry i is the ratio CFL(i+1)/CFL(i). If fewer values than nMGLevels are given, the last value is repeated. DEFAULT: 0.25 (i.e., 1/4 per level) \ingroup Config*/ addDoubleListOption("MG_CFL_SCALING", nMG_CflScaling_p, MG_CflScaling_p); diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 684da742b13..b97d919385b 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -659,11 +659,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un SetGlobal_nPointDomain(Global_nPointCoarse); if (iMesh != MESH_0) { - /*--- Note: CFL at the coarse levels have a large impact on convergence, - this should be rewritten to use adaptive CFL. ---*/ - const su2double Coeff = 1.5; - const su2double CFL = config->GetCFL(iMesh - 1) / Coeff; - config->SetCFL(iMesh, CFL); + /*--- Initialize coarse-level CFL from config. MG_CFL_SCALING will + apply per-level reductions during the multigrid cycle. ---*/ + config->SetCFL(iMesh, config->GetCFL(MESH_0)); } const su2double ratio = su2double(Global_nPointFine) / su2double(Global_nPointCoarse); @@ -1286,8 +1284,11 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long& Index_CoarseCV, const su2double ANGLE_THRESHOLD_DEG = 20.0; /*!< Stop line if direction deviates more than this. */ const unsigned long MAX_LINE_LENGTH = config->GetMGOptions().MG_Implicit_Lines_MaxLength; const su2double cos_threshold = cos(ANGLE_THRESHOLD_DEG * PI_NUMBER / 180.0); + const bool ISOTROPIC = config->GetMGOptions().MG_Implicit_Lines_Isotropic; const unsigned long nPointFine = fine_grid->GetnPoint(); + const unsigned long starting_Index_CoarseCV = Index_CoarseCV; /*--- Track how many CVs we create ---*/ + const bool DEBUG_OUTPUT = (rank == MASTER_NODE); /*--- Enable detailed diagnostic output ---*/ /*--- Collect implicit lines starting at viscous (no-slip) wall vertices only. * Seeding from non-wall boundaries (farfield, inlet, outlet, symmetry) would @@ -1326,7 +1327,7 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long& Index_CoarseCV, norm_prev = sqrt(norm_prev); for (unsigned short d = 0; d < nDim; ++d) prev_dir[d] /= norm_prev; - /*--- Build the implicit line by following the best-aligned interior neighbor ---*/ + /*--- Build the implicit line by following the best-aligned interior neighbor. ---*/ vector L; L.push_back(iPoint); auto current = iPoint; @@ -1336,10 +1337,10 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long& Index_CoarseCV, unsigned long best_neighbor = ULONG_MAX; for (auto jPoint : fine_grid->nodes->GetPoints(current)) { - if (jPoint == current) continue; if (!fine_grid->nodes->GetDomain(jPoint)) continue; if (fine_grid->nodes->GetBoundary(jPoint)) continue; if (fine_grid->nodes->GetAgglomerate(jPoint)) continue; + if (find(L.begin(), L.end(), jPoint) != L.end()) continue; /*--- Compute normalized direction to candidate ---*/ su2double vec[MAXNDIM] = {0.0}; @@ -1379,133 +1380,154 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long& Index_CoarseCV, if (lines.empty()) return; - if (rank == MASTER_NODE) { - cout << "Implicit line agglomeration: detected " << lines.size() << " lines." << endl; - } - - /*--- Advancing-front greedy pairing with cross-line merging. - * For each pair stage k, process interior positions (1+2k, 1+2k+1). - * When two lines share the same wall-node parent CV, merge their pairs - * into a single 4-child coarse CV. Otherwise create 2-child coarse CVs. ---*/ + /*--- Agglomeration strategy: at each "position" (distance from the wall along a + * line) every line contributes a block of nBlock consecutive nodes; two lines + * are paired through a mesh-adjacency search of their block anchors and the + * combined blocks become the children of one coarse CV. + * + * ANISOTROPIC (default, nBlock=1): pair single nodes at the SAME distance from + * the wall on two DIFFERENT lines. Each coarse CV has 2 fine children. + * Reduces the mesh by a factor ~2 normal to the wall, preserves resolution + * along the wall. + * + * ISOTROPIC (nBlock=2): pair 2-node blocks (2 positions x 2 lines) into one + * coarse CV with 4 fine children. Reduces the mesh uniformly by a factor + * ~4 in all directions. ---*/ + const unsigned long nBlock = ISOTROPIC ? 2 : 1; vector reserved(nPointFine, 0); - unsigned pair_idx = 0; + unsigned long position_idx = 0; - while (true) { - bool any_work = false; + /*--- Fine-grid nodes line `li` contributes at the current position, or empty if + the line is too short to reach that far. ---*/ + auto LineBlock = [&](unsigned long li) -> vector { + const auto& L = lines[li]; + const unsigned long first = 1 + nBlock * position_idx; + if (L.size() < first + nBlock) return {}; + return vector(L.begin() + first, L.begin() + first + nBlock); + }; - /*--- Build map: wall parent CV -> list of line indices ---*/ - unordered_map> parent_to_lines; - parent_to_lines.reserve(lines.size()); + while (true) { + /*--- Cache each line's block for this position and collect the active ones. ---*/ + vector> block(lines.size()); + vector active_lines; + active_lines.reserve(lines.size()); for (unsigned long li = 0; li < lines.size(); ++li) { - const auto& L = lines[li]; - if (L.empty()) continue; - const auto idx2 = 1 + 2 * pair_idx + 1; - if (L.size() <= idx2) continue; // no pair at this stage - const auto pW = fine_grid->nodes->GetParent_CV(L[0]); - parent_to_lines[pW].push_back(li); + block[li] = LineBlock(li); + if (!block[li].empty()) active_lines.push_back(li); } + if (active_lines.empty()) break; vector line_processed(lines.size(), 0); + bool any_work = false; + + for (auto li1 : active_lines) { + if (line_processed[li1]) continue; + + const auto anchor = block[li1].front(); + if (fine_grid->nodes->GetAgglomerate(anchor) || reserved[anchor]) continue; + + /*--- Find an unprocessed neighboring line: one whose block anchor is a + mesh-neighbor of ours at the same position. ---*/ + unsigned long li2 = ULONG_MAX; + for (auto neighbor_point : fine_grid->nodes->GetPoints(anchor)) { + for (auto candidate : active_lines) { + if (candidate == li1 || line_processed[candidate]) continue; + if (block[candidate].front() == neighbor_point) { + li2 = candidate; + break; + } + } + if (li2 != ULONG_MAX) break; + } - /*--- A) Cross-line merges: parents with multiple lines ---*/ - for (auto& [parent, line_ids] : parent_to_lines) { - if (line_ids.size() < 2) continue; - - for (size_t k = 0; k + 1 < line_ids.size(); k += 2) { - const auto li1 = line_ids[k]; - const auto li2 = line_ids[k + 1]; - if (line_processed[li1] || line_processed[li2]) continue; - - const auto& L1 = lines[li1]; - const auto& L2 = lines[li2]; - const auto idx1 = 1 + 2 * pair_idx; - const auto idx2 = idx1 + 1; - if (L1.size() <= idx2 || L2.size() <= idx2) continue; - - const auto a = L1[idx1], b = L1[idx2]; - const auto c = L2[idx1], d = L2[idx2]; - - /*--- Skip if any node is already claimed ---*/ - if (fine_grid->nodes->GetAgglomerate(a) || fine_grid->nodes->GetAgglomerate(b) || - fine_grid->nodes->GetAgglomerate(c) || fine_grid->nodes->GetAgglomerate(d)) - continue; - if (reserved[a] || reserved[b] || reserved[c] || reserved[d]) continue; - - /*--- Geometrical quality check ---*/ - if (!GeometricalCheck(a, fine_grid, config) || !GeometricalCheck(b, fine_grid, config) || - !GeometricalCheck(c, fine_grid, config) || !GeometricalCheck(d, fine_grid, config)) - continue; - - /*--- Guard against duplicate indices ---*/ - if (a == b || a == c || a == d || b == c || b == d || c == d) { - for (auto other_li : line_ids) line_processed[other_li] = 1; - continue; + if (li2 == ULONG_MAX) { + if (DEBUG_OUTPUT && position_idx < 3) { + cout << " Line " << li1 << " at position " << position_idx << " (node " << anchor + << ") has NO neighbor line!" << endl; } + continue; + } - /*--- Create 4-child coarse CV ---*/ - fine_grid->nodes->SetParent_CV(a, Index_CoarseCV); - nodes->SetChildren_CV(Index_CoarseCV, 0, a); - fine_grid->nodes->SetParent_CV(b, Index_CoarseCV); - nodes->SetChildren_CV(Index_CoarseCV, 1, b); - fine_grid->nodes->SetParent_CV(c, Index_CoarseCV); - nodes->SetChildren_CV(Index_CoarseCV, 2, c); - fine_grid->nodes->SetParent_CV(d, Index_CoarseCV); - nodes->SetChildren_CV(Index_CoarseCV, 3, d); - nodes->SetnChildren_CV(Index_CoarseCV, 4); - - reserved[a] = reserved[b] = reserved[c] = reserved[d] = 1; - MGQueue_InnerCV.RemoveCV(a); - MGQueue_InnerCV.RemoveCV(b); - MGQueue_InnerCV.RemoveCV(c); - MGQueue_InnerCV.RemoveCV(d); + /*--- Assemble and validate the coarse CV's children together. ---*/ + auto group = block[li1]; + group.insert(group.end(), block[li2].begin(), block[li2].end()); - Index_CoarseCV++; - line_processed[li1] = line_processed[li2] = 1; - for (auto other_li : line_ids) - if (other_li != li1 && other_li != li2) line_processed[other_li] = 1; - any_work = true; + bool valid = true; + for (auto p : group) { + if (fine_grid->nodes->GetAgglomerate(p) || reserved[p] || !GeometricalCheck(p, fine_grid, config)) { + valid = false; + break; + } } - } + if (!valid) continue; // one of the nodes wasn't ready; li2 may still pair elsewhere. - /*--- B) Single-line 2-child merges for remaining lines ---*/ - for (unsigned long li = 0; li < lines.size(); ++li) { - if (line_processed[li]) continue; - const auto& L = lines[li]; - const auto idx1 = 1 + 2 * pair_idx; - const auto idx2 = idx1 + 1; - if (L.size() <= idx2) continue; - - const auto a = L[idx1], b = L[idx2]; - if (fine_grid->nodes->GetAgglomerate(a) || fine_grid->nodes->GetAgglomerate(b)) continue; - if (reserved[a] || reserved[b]) continue; - if (!GeometricalCheck(a, fine_grid, config) || !GeometricalCheck(b, fine_grid, config)) continue; - - /*--- Create 2-child coarse CV ---*/ - fine_grid->nodes->SetParent_CV(a, Index_CoarseCV); - nodes->SetChildren_CV(Index_CoarseCV, 0, a); - fine_grid->nodes->SetParent_CV(b, Index_CoarseCV); - nodes->SetChildren_CV(Index_CoarseCV, 1, b); - nodes->SetnChildren_CV(Index_CoarseCV, 2); - - reserved[a] = reserved[b] = 1; - MGQueue_InnerCV.RemoveCV(a); - MGQueue_InnerCV.RemoveCV(b); + /*--- Guard against the same fine point appearing in both blocks + (can happen if two lines' walks overlap in space). ---*/ + bool duplicate = false; + for (size_t i = 0; !duplicate && i + 1 < group.size(); ++i) + for (size_t j = i + 1; j < group.size(); ++j) + if (group[i] == group[j]) duplicate = true; + if (duplicate) { + line_processed[li1] = line_processed[li2] = 1; + continue; + } + + /*--- Create the coarse CV from the combined blocks. ---*/ + for (size_t c = 0; c < group.size(); ++c) { + fine_grid->nodes->SetParent_CV(group[c], Index_CoarseCV); + nodes->SetChildren_CV(Index_CoarseCV, c, group[c]); + reserved[group[c]] = 1; + MGQueue_InnerCV.RemoveCV(group[c]); + } + nodes->SetnChildren_CV(Index_CoarseCV, static_cast(group.size())); Index_CoarseCV++; + + line_processed[li1] = line_processed[li2] = 1; any_work = true; } - pair_idx++; if (!any_work) break; + position_idx++; + } - /*--- Check if any line still has pairs at the next stage ---*/ - bool any_more = false; - for (const auto& L : lines) { - if (L.size() > 1 + 2 * pair_idx + 1) { - any_more = true; - break; - } + /*--- Count how many CVs and nodes were created ---*/ + const auto nCVs_created = Index_CoarseCV - starting_Index_CoarseCV; + unsigned long nNodes_claimed = 0; + unsigned long nNodes_on_lines = 0; + unsigned long nNodes_unpaired = 0; + + for (const auto& L : lines) { + for (size_t i = 1; i < L.size(); ++i) { // Skip wall node at [0] + nNodes_on_lines++; + if (!reserved[L[i]]) nNodes_unpaired++; } - if (!any_more) break; + } + + for (unsigned long i = 0; i < nPointFine; ++i) { + if (reserved[i]) nNodes_claimed++; + } + + if (DEBUG_OUTPUT) { + cout << " Created " << nCVs_created << " coarse CVs from " << nNodes_claimed << " fine nodes." << endl; + cout << " Nodes on implicit lines: " << nNodes_on_lines << " (paired=" << (nNodes_on_lines - nNodes_unpaired) + << ", unpaired=" << nNodes_unpaired << ")" << endl; + + if (nNodes_unpaired > 0) { + cout << " WARNING: " << nNodes_unpaired << " nodes on implicit lines were left unpaired!" << endl; + cout << " These will be processed by domain agglomeration (may create wrong orientation)." << endl; + } + } + + /*--- Verify all claimed nodes are properly marked as agglomerated (SetParent_CV should + guarantee this; a mismatch would indicate a bookkeeping bug above). ---*/ + unsigned long mismatches = 0; + for (unsigned long i = 0; i < nPointFine; ++i) { + if (reserved[i] && !fine_grid->nodes->GetAgglomerate(i)) { + mismatches++; + } + } + if (mismatches > 0 && DEBUG_OUTPUT) { + cout << " WARNING: " << mismatches << " nodes marked as reserved but not agglomerated!" << endl; } } diff --git a/SU2_CFD/include/integration/CMultiGridIntegration.hpp b/SU2_CFD/include/integration/CMultiGridIntegration.hpp index 0672d1ae5ff..f7db01424e9 100644 --- a/SU2_CFD/include/integration/CMultiGridIntegration.hpp +++ b/SU2_CFD/include/integration/CMultiGridIntegration.hpp @@ -277,4 +277,24 @@ class CMultiGridIntegration final : public CIntegration { unsigned short lastPreSmoothWorstStep[MAX_MG_LEVELS+1] = {}; unsigned short lastPostSmoothWorstStep[MAX_MG_LEVELS+1] = {}; + /*--- FMG startup CFL ramp bookkeeping: tracks the currently active FMG level + * and the InnerIter at which it became active, so its CFL can be ramped + * linearly towards the next (finer) level's target over MG_Startup_Iter + * iterations instead of jumping discontinuously at promotion. ---*/ + unsigned short mg_ramp_last_FinestMesh = MAX_MG_LEVELS + 1; /*!< \brief FinestMesh observed on the previous call; sentinel forces a reset on the first call. */ + unsigned long mg_ramp_level_start_iter = 0; /*!< \brief InnerIter at which the currently active FMG level became active. */ + + /*--- User-configured damping factors, captured before any adaptation so they can be + * restored whenever the active FMG level changes (the cross-cycle EMA that drives + * the adaptation is only meaningful within a single level). ---*/ + bool mg_damp_initial_captured = false; /*!< \brief Whether the configured damping factors have been stored yet. */ + su2double mg_damp_restric_initial = 0.0; /*!< \brief MG_DAMP_RESTRICTION as configured. */ + su2double mg_damp_prolong_initial = 0.0; /*!< \brief MG_DAMP_PROLONGATION as configured. */ + + /*--- FMG startup convergence-based early exit: promote the active level as soon as its + * CONV_FIELD residual has dropped two orders of magnitude, instead of always waiting + * out the full MG_Startup_Iter budget. Reset whenever the active level changes. ---*/ + passivedouble mg_conv_field_start_rms = -1.0; /*!< \brief CONV_FIELD RMS at the start of the active level's window; <0 = not yet captured. */ + bool mg_conv_field_early_exit = false; /*!< \brief Set once the active level has converged two orders of magnitude; consumed at the next promotion check. */ + }; diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 942e2e25877..67b7a7be69b 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -389,6 +389,22 @@ class CSolver { */ inline su2double GetAvg_CFL_Local(void) const { return Avg_CFL_Local; } + /*! + * \brief Set the value of the average local CFL number. + * \param[in] val_cfl - Average CFL number. + */ + inline void SetAvg_CFL_Local(su2double val_cfl) { Avg_CFL_Local = val_cfl; } + + /*! + * \brief Set min/max/avg local CFL summary statistics. + * \param[in] val_cfl - Uniform CFL value to report. + */ + inline void SetCFL_Local_Stats(su2double val_cfl) { + Min_CFL_Local = val_cfl; + Max_CFL_Local = val_cfl; + Avg_CFL_Local = val_cfl; + } + /*! * \brief Get the number of variables of the problem. */ diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index 1c0b35ad53d..c8ca4fcdfae 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -28,6 +28,12 @@ #include "../../include/integration/CMultiGridIntegration.hpp" #include "../../../Common/include/parallelization/omp_structure.hpp" #include "../../../Common/include/toolboxes/printing_toolbox.hpp" +#include +#include +#include +#include + +using namespace std; namespace { @@ -61,6 +67,27 @@ inline passivedouble ComputeLinSysResRMS(const CSolver* solver) { return sqrt(result); } +/*!\cond PRIVATE + * Prolongate a coarse-grid field onto the fine grid via constant injection: every fine + * child gets its parent's value. \c getCoarse returns the coarse-grid block of a point + * and \c setFine writes it to a fine-grid point, so the same loop serves both the FAS + * correction and the Full-MG solution handoff. + * + * Only domain points are written; halo synchronization is the caller's responsibility. + \endcond */ +template +void ProlongateField(CGeometry* geo_coarse, GetCoarse getCoarse, SetFine setFine) { + + SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) + for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { + for (auto iChildren = 0u; iChildren < geo_coarse->nodes->GetnChildren_CV(Point_Coarse); iChildren++) { + auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); + setFine(Point_Fine, getCoarse(Point_Coarse)); + } + } + END_SU2_OMP_FOR +} + } // anonymous namespace void CMultiGridIntegration::adaptDampingFactors(CConfig* config, passivedouble crossCycleRatio) { @@ -153,13 +180,28 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, END_SU2_OMP_SAFE_GLOBAL_ACCESS /*--- Full MG: advance to the next finer grid after a fixed number of - * outer iterations on the current coarsest active level. - * We use 100 iterations per level (nMGLevels levels total) ---*/ + * outer iterations on the current coarsest active level, controlled by + * MG_STARTUP_ITER, or as soon as the level's CONV_FIELD residual has already + * dropped two orders of magnitude (mg_conv_field_early_exit, set below after + * the cycle runs) - whichever happens first. A level that converges quickly + * should not sit idle for the rest of its budget before being promoted. + * + * Iterations spent on the currently active level are counted relative to + * mg_ramp_level_start_iter (the InnerIter at which it became active, reset on + * every promotion below) rather than a global InnerIter modulo. A global modulo + * assumes every level consumes exactly MG_STARTUP_ITER iterations; once a level + * is promoted early via mg_conv_field_early_exit, InnerIter falls out of phase + * with that assumption and every subsequent level's fixed-iteration budget would + * be truncated to whatever remains until the next global phase boundary instead + * of getting its own full MG_STARTUP_ITER window. ---*/ + const unsigned long startup_iter = config[iZone]->GetMGOptions().MG_Startup_Iter; + const unsigned long iters_on_level = config[iZone]->GetInnerIter() - mg_ramp_level_start_iter; const bool Convergence_FullMG = FullMG && (FinestMesh != MESH_0) && - (config[iZone]->GetInnerIter() % 100 == 99); + ((iters_on_level >= startup_iter - 1) || mg_conv_field_early_exit); - if (!config[iZone]->GetRestart() && FullMG && direct && ( Convergence_FullMG && (FinestMesh != MESH_0 ))) { + if (!config[iZone]->GetRestart() && FullMG && direct && ( Convergence_FullMG && (FinestMesh != MESH_0 )) && + RunTime_EqSystem == RUNTIME_FLOW_SYS) { SetProlongated_Solution(RunTime_EqSystem, solver_container[iZone][iInst][FinestMesh-1][Solver_Position], @@ -168,6 +210,21 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, geometry[iZone][iInst][FinestMesh], config[iZone]); + /*--- Report the promotion and why it happened now, before mg_ramp_level_start_iter + * and mg_conv_field_early_exit are reset for the newly active level below. ---*/ + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + if (SU2_MPI::GetRank() == MASTER_NODE) { + cout << "Full-MG: mesh level " << FinestMesh << " -> " << FinestMesh - 1 << " after " + << (iters_on_level + 1) << " iteration(s) ("; + if (mg_conv_field_early_exit) { + const string convField = (config[iZone]->GetnConv_Field() > 0) ? config[iZone]->GetConv_Field(0) : string("RMS_DENSITY"); + cout << convField << " dropped 2 orders of magnitude"; + } else + cout << "MG_STARTUP_ITER= " << startup_iter << " reached"; + cout << ")." << endl; + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + SU2_OMP_SAFE_GLOBAL_ACCESS(config[iZone]->SubtractFinestMesh();) } @@ -175,50 +232,138 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, FinestMesh = config[iZone]->GetFinestMesh(); - /*--- Perform the Full Approximation Scheme multigrid ---*/ - - MultiGrid_Cycle(geometry, solver_container, numerics_container, config, - FinestMesh, RecursiveParam, RunTime_EqSystem, iZone, iInst); - - /*--- Adapt coarse-grid CFL once per cycle using smoothing residuals gathered during the cycle. ---*/ + /*--- Rebuild coarse-grid CFL before the cycle so the currently active FMG + * level uses the intended CFL in this iteration. ---*/ const unsigned short nMGLevels = config[iZone]->GetnMGLevels(); BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { - /*--- Use the current finest-grid CFL as the base for deterministic - * coarse-level scaling. Fall back to config scalar when local CFL - * adaptation is disabled. ---*/ + /*--- Capture the configured damping factors once, before any adaptation has + * modified them, so they can be restored at every FMG level change. ---*/ + if (!mg_damp_initial_captured) { + mg_damp_restric_initial = config[iZone]->GetDamp_Res_Restric(); + mg_damp_prolong_initial = config[iZone]->GetDamp_Correc_Prolong(); + mg_damp_initial_captured = true; + } + + /*--- Detect a change of active FMG level (promotion, restart, or a new + * outer/time-step resetting FinestMesh) and (re)start the ramp window. ---*/ + if (FinestMesh != mg_ramp_last_FinestMesh) { + mg_ramp_level_start_iter = config[iZone]->GetInnerIter(); + mg_ramp_last_FinestMesh = FinestMesh; + + /*--- The cross-cycle EMA is only meaningful while the active level is fixed: + * after a promotion the pre-smoothing RMS is measured on a different grid, + * so the ratio reflects the change of level rather than the convergence + * trend. Left alone, the lagging EMA drives both damping factors onto a + * clamp within ~20-30 cycles - towards CLAMP_MIN, crippling the correction, + * or towards CLAMP_MAX, making it maximally aggressive on a freshly + * prolongated solution. Reseed the EMA and restore the configured factors. ---*/ + mg_fine_rms_ema = 0.0; + config[iZone]->SetDamp_Res_Restric(mg_damp_restric_initial); + config[iZone]->SetDamp_Correc_Prolong(mg_damp_prolong_initial); + + /*--- Restart the convergence-based early-exit window: the baseline is (re)captured + * below, after this iteration's cycle has produced a fresh residual on the level. ---*/ + mg_conv_field_start_rms = -1.0; + mg_conv_field_early_exit = false; + } + + /*--- Use the level-0 flow CFL as the base reference and derive the steady-state + * scaled target for all coarse levels from it via + * MG_CFL_SCALING[i] = CFL(i+1)/CFL(i). Fall back to config scalar when + * local level-0 CFL is unavailable. ---*/ passivedouble cfl_base = SU2_TYPE::GetValue( - solver_container[iZone][iInst][FinestMesh][Solver_Position]->GetAvg_CFL_Local()); + solver_container[iZone][iInst][MESH_0][Solver_Position]->GetAvg_CFL_Local()); if (cfl_base < EPS) - cfl_base = SU2_TYPE::GetValue(config[iZone]->GetCFL(FinestMesh)); + cfl_base = SU2_TYPE::GetValue(config[iZone]->GetCFL(MESH_0)); const auto& cflScaling = config[iZone]->GetMGOptions().MG_CflScaling; - passivedouble CFL_local = cfl_base; - for (unsigned short iMesh = FinestMesh; iMesh < nMGLevels; ++iMesh) { - const unsigned short lvl = iMesh + 1; - /*--- Use per-level scaling factor; clamp to (0,1] to prevent coarse CFL from - * exceeding the fine CFL. Index into cflScaling is iMesh (0-based transition). ---*/ - const passivedouble scale = (iMesh < cflScaling.size()) - ? max(passivedouble{1e-6}, min(passivedouble{1.0}, SU2_TYPE::GetValue(cflScaling[iMesh]))) + passivedouble CFL_target[MAX_MG_LEVELS+1]; + passivedouble CFL_scale[MAX_MG_LEVELS+1]; + CFL_target[0] = cfl_base; + CFL_scale[0] = 1.0; + for (unsigned short lvl = 1; lvl <= nMGLevels; ++lvl) { + /*--- Index into cflScaling is (lvl-1): transition lvl-1 -> lvl. ---*/ + const unsigned short iScale = lvl - 1; + const passivedouble scale = (iScale < cflScaling.size()) + ? max(passivedouble{1e-6}, SU2_TYPE::GetValue(cflScaling[iScale])) : passivedouble{0.25}; - CFL_local *= scale; + CFL_scale[lvl] = scale; + CFL_target[lvl] = CFL_target[lvl-1] * scale; + } + + /*--- During FMG startup, linearly ramp the CFL of the currently active level up + * to its own scaled target over MG_Startup_Iter iterations, starting from the + * CFL the previously active (coarser) level was running at. The ramp is + * therefore continuous across a promotion and, crucially, never exceeds the + * level's own target: the sustainable CFL is a property of the mesh, so + * carrying a finer level's target onto a coarser mesh destabilises it. The + * reduced starting value also gives the freshly prolongated solution time to + * relax before the level runs at full CFL. The coarsest level has no coarser + * predecessor, so it extends the scaling one step further to soften the + * initial transient away from the freestream state. + * + * All non-active coarse levels keep their steady-state scaled target, and no + * ramp is applied once FMG has reached MESH_0 (the final V-cycle-equivalent + * stage behaves exactly like a plain V-cycle). ---*/ + const bool ramping = FullMG && (FinestMesh > MESH_0) && (FinestMesh <= nMGLevels); + passivedouble ramp_progress = 1.0; + if (ramping && startup_iter > 0) { + const unsigned long iter_in_level = config[iZone]->GetInnerIter() - mg_ramp_level_start_iter; + ramp_progress = min(passivedouble{1.0}, passivedouble(iter_in_level) / passivedouble(startup_iter)); + } + + for (unsigned short lvl = 1; lvl <= nMGLevels; ++lvl) { + passivedouble CFL_local = CFL_target[lvl]; + if (ramping && lvl == FinestMesh) { + const passivedouble CFL_start = (lvl < nMGLevels) ? CFL_target[lvl+1] + : CFL_target[lvl] * CFL_scale[lvl]; + CFL_local = (passivedouble(1.0) - ramp_progress) * CFL_start + ramp_progress * CFL_target[lvl]; + } config[iZone]->SetCFL(lvl, CFL_local); } } END_SU2_OMP_SAFE_GLOBAL_ACCESS - /*--- Propagate the updated coarse-grid CFL to every coarse-grid point (all threads). ---*/ - for (unsigned short iMesh = FinestMesh; iMesh < nMGLevels; ++iMesh) { - const passivedouble CFL_coarse_new = SU2_TYPE::GetValue(config[iZone]->GetCFL(iMesh+1)); - CGeometry* geo_c = geometry[iZone][iInst][iMesh+1]; - CSolver* sol_c = solver_container[iZone][iInst][iMesh+1][Solver_Position]; + /*--- Propagate updated CFL to all coarse-grid points before the cycle. ---*/ + for (unsigned short iMesh = 1; iMesh <= nMGLevels; ++iMesh) { + const passivedouble CFL_coarse_new = SU2_TYPE::GetValue(config[iZone]->GetCFL(iMesh)); + CGeometry* geo_c = geometry[iZone][iInst][iMesh]; + CSolver* sol_c = solver_container[iZone][iInst][iMesh][Solver_Position]; + SU2_OMP_SAFE_GLOBAL_ACCESS(sol_c->SetCFL_Local_Stats(CFL_coarse_new);) SU2_OMP_FOR_STAT(roundUpDiv(geo_c->GetnPoint(), omp_get_num_threads())) for (auto iPoint = 0ul; iPoint < geo_c->GetnPoint(); iPoint++) sol_c->GetNodes()->SetLocalCFL(iPoint, CFL_coarse_new); END_SU2_OMP_FOR } + /*--- Perform the Full Approximation Scheme multigrid ---*/ + + MultiGrid_Cycle(geometry, solver_container, numerics_container, config, + FinestMesh, RecursiveParam, RunTime_EqSystem, iZone, iInst); + + /*--- FMG startup convergence-based early exit: track the active level's aggregate flow + * residual (RMS across all solution variables, the same solver-agnostic metric this + * class already uses for the smoothing early-exit diagnostics) and flag promotion once + * it has dropped two orders of magnitude, so a level that converges well inside its + * MG_Startup_Iter budget is not held back. Reuses this same cycle's fresh residual, so + * the very first iteration of a window only seeds the baseline (nothing to compare + * against yet). ---*/ + if (FullMG && direct && (FinestMesh != MESH_0) && RunTime_EqSystem == RUNTIME_FLOW_SYS) { + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + { + const passivedouble conv_now = ComputeLinSysResRMS(solver_container[iZone][iInst][FinestMesh][Solver_Position]); + + if (mg_conv_field_start_rms < 0.0) { + mg_conv_field_start_rms = max(conv_now, passivedouble(EPS)); + } else if (conv_now <= 1e-2 * mg_conv_field_start_rms) { + mg_conv_field_early_exit = true; + } + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + } + /*--- Computes primitive variables and gradients in the finest mesh (useful for the next solver (turbulence) and output ---*/ solver_container[iZone][iInst][MESH_0][Solver_Position]->Preprocessing(geometry[iZone][iInst][MESH_0], @@ -255,8 +400,8 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, END_SU2_OMP_SAFE_GLOBAL_ACCESS } - /*--- Print compact smoothing summary when MG_SMOOTH_OUTPUT= YES. ---*/ - if (mgOptsZone.MG_Smooth_Output) { + /*--- Print compact smoothing summary when MG_SMOOTH_OUTPUT= YES and MGLEVEL > 0. ---*/ + if ((mgOptsZone.MG_Smooth_Output) && (nMGLevels > 0)) { BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS if (SU2_MPI::GetRank() == MASTER_NODE) { @@ -283,8 +428,13 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, return ss.str(); }; + const string eqName = (RunTime_EqSystem == RUNTIME_FLOW_SYS) ? "Flow" : + (RunTime_EqSystem == RUNTIME_TURB_SYS) ? "Turb" : + (RunTime_EqSystem == RUNTIME_SPECIES_SYS) ? "Species" : + (RunTime_EqSystem == RUNTIME_TRANS_SYS) ? "Trans" : "Other"; + PrintingToolbox::CTablePrinter table(&std::cout); - table.AddColumn("Smoother", 13); + table.AddColumn("Smoother [" + eqName + "]", 13 + 7); for (unsigned short i = 0; i <= nMGLevels; ++i) table.AddColumn("Level " + std::to_string(i), 38); table.PrintHeader(); @@ -437,7 +587,8 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, iMesh+1, nextRecurseParam, RunTime_EqSystem, iZone, iInst); } - /*--- Compute prolongated solution, and smooth the correction $u^(new)_k = u_k + Smooth(I^k_(k+1)(u_(k+1)-I^(k+1)_k u_k))$ ---*/ + /*--- Compute prolongated solution, and smooth the correction $u^(new)_k = u_k + + Smooth(I^k_(k+1)(u_(k+1)-I^(k+1)_k u_k))$ ---*/ GetProlongated_Correction(RunTime_EqSystem, solver_fine, solver_coarse, geometry_fine, geometry_coarse, config); @@ -708,14 +859,15 @@ void CMultiGridIntegration::GetProlongated_Correction(unsigned short RunTime_EqS sol_coarse->InitiateComms(geo_coarse, config, MPI_QUANTITIES::SOLUTION_OLD); sol_coarse->CompleteComms(geo_coarse, config, MPI_QUANTITIES::SOLUTION_OLD); - SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) - for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { - for (auto iChildren = 0u; iChildren < geo_coarse->nodes->GetnChildren_CV(Point_Coarse); iChildren++) { - auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); - sol_fine->LinSysRes.SetBlock(Point_Fine, sol_coarse->GetNodes()->GetSolution_Old(Point_Coarse)); - } - } - END_SU2_OMP_FOR + /*--- Interpolate the coarse-grid correction (held in Solution_Old) onto the fine + * grid and store it in LinSysRes, which SetProlongated_Correction then damps + * and adds to the fine-grid solution. ---*/ + + ProlongateField(geo_coarse, + [&](unsigned long iPoint) { return sol_coarse->GetNodes()->GetSolution_Old(iPoint); }, + [&](unsigned long Point_Fine, const su2double* value) { + sol_fine->LinSysRes.SetBlock(Point_Fine, value); + }); } @@ -833,14 +985,71 @@ void CMultiGridIntegration::SetProlongated_Solution(unsigned short RunTime_EqSys CGeometry *geo_fine, CGeometry *geo_coarse, CConfig *config) { SU2_ZONE_SCOPED - SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) - for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { - for (auto iChildren = 0u; iChildren < geo_coarse->nodes->GetnChildren_CV(Point_Coarse); iChildren++) { - auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); - sol_fine->GetNodes()->SetSolution(Point_Fine, sol_coarse->GetNodes()->GetSolution(Point_Coarse)); + const unsigned short Solver_Position = config->GetContainerPosition(RunTime_EqSystem); + const bool grid_movement = config->GetGrid_Movement(); + + /*--- Interpolate the coarse solution onto the fine grid. This is the initial + * condition the newly activated Full-MG level starts from, so it uses the same + * constant-injection operator as the FAS correction, applied directly to the + * solution rather than to LinSysRes. ---*/ + + ProlongateField(geo_coarse, + [&](unsigned long iPoint) { return sol_coarse->GetNodes()->GetSolution(iPoint); }, + [&](unsigned long Point_Fine, const su2double* value) { + sol_fine->GetNodes()->SetSolution(Point_Fine, value); + }); + + /*--- Update the solution at the no-slip walls, mirroring SetRestricted_Solution. + * The prolongated values come from coarse-grid nodes and do not satisfy the + * fine-grid wall conditions on their own. ---*/ + + for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++) { + if (config->GetViscous_Wall(iMarker)) { + + SU2_OMP_FOR_STAT(32) + for (auto iVertex = 0ul; iVertex < geo_fine->nVertex[iMarker]; iVertex++) { + + const auto Point_Fine = geo_fine->vertex[iMarker][iVertex]->GetNode(); + + if (Solver_Position == FLOW_SOL) { + + /*--- At moving walls, set the solution based on the new density and wall velocity ---*/ + + if (grid_movement) { + const auto* Grid_Vel = geo_fine->nodes->GetGridVel(Point_Fine); + sol_fine->GetNodes()->SetVelSolutionVector(Point_Fine, Grid_Vel); + } + else { + /*--- For stationary no-slip walls, set the velocity to zero. ---*/ + su2double zero[3] = {0.0}; + sol_fine->GetNodes()->SetVelSolutionVector(Point_Fine, zero); + } + + } + + if (Solver_Position == ADJFLOW_SOL) { + sol_fine->GetNodes()->SetVelSolutionDVector(Point_Fine); + } + + } + END_SU2_OMP_FOR } } - END_SU2_OMP_FOR + + /*--- Enforce Euler wall BC by projecting velocity to the tangent plane. The coarse + * velocity is tangent to the coarse wall normal, which is not the fine-grid wall + * normal on curved surfaces; without this the smallest near-wall cells start with + * a spurious normal velocity. ---*/ + + sol_fine->MultigridProjectEulerWall(geo_fine, config, false); + + /*--- MPI the new interpolated solution. The loops above only write domain points, + * while Preprocessing builds primitive variables over all points including halos, + * so the halos must be synchronized before the level is iterated. ---*/ + + sol_fine->InitiateComms(geo_fine, config, MPI_QUANTITIES::SOLUTION); + sol_fine->CompleteComms(geo_fine, config, MPI_QUANTITIES::SOLUTION); + } void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, @@ -852,21 +1061,21 @@ void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coar const unsigned short nVar = sol_coarse->GetnVar(); const su2double factor = config->GetDamp_Res_Restric(); - su2activevector Residual(nVar); + su2activevector RestrictedDefect(nVar); SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { sol_coarse->GetNodes()->SetRes_TruncErrorZero(Point_Coarse); - Residual = su2double(0); + RestrictedDefect = su2double(0); for (auto iChildren = 0u; iChildren < geo_coarse->nodes->GetnChildren_CV(Point_Coarse); iChildren++) { auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); Residual_Fine = sol_fine->LinSysRes.GetBlock(Point_Fine); for (auto iVar = 0u; iVar < nVar; iVar++) - Residual[iVar] += factor * Residual_Fine[iVar]; + RestrictedDefect[iVar] += factor * Residual_Fine[iVar]; } - sol_coarse->GetNodes()->AddRes_TruncError(Point_Coarse, Residual.data()); + sol_coarse->GetNodes()->AddRes_TruncError(Point_Coarse, RestrictedDefect.data()); } END_SU2_OMP_FOR diff --git a/SU2_CFD/src/integration/CSingleGridIntegration.cpp b/SU2_CFD/src/integration/CSingleGridIntegration.cpp index fab97842048..0f051fdc2b6 100644 --- a/SU2_CFD/src/integration/CSingleGridIntegration.cpp +++ b/SU2_CFD/src/integration/CSingleGridIntegration.cpp @@ -49,6 +49,20 @@ void CSingleGridIntegration::SingleGrid_Iteration(CGeometry ****geometry, CSolve CGeometry* geometry_fine = geometry[iZone][iInst][FinestMesh]; CSolver** solvers_fine = solver_container[iZone][iInst][FinestMesh]; + if (RunTime_EqSystem == RUNTIME_TURB_SYS) { + /*--- CFL scaling of turbulence during the warmup phase if FMG. ---*/ + const su2double turbReduction = SU2_TYPE::GetValue(config[iZone]->GetCFLRedCoeff_Turb()); + const su2double turbCFL = SU2_TYPE::GetValue(config[iZone]->GetCFL(FinestMesh)) * turbReduction; + auto* turbSolver = solvers_fine[Solver_Position]; + + SU2_OMP_SAFE_GLOBAL_ACCESS(turbSolver->SetCFL_Local_Stats(turbCFL);) + SU2_OMP_FOR_STAT(roundUpDiv(geometry_fine->GetnPoint(), omp_get_num_threads())) + for (auto iPoint = 0ul; iPoint < geometry_fine->GetnPoint(); ++iPoint) { + turbSolver->GetNodes()->SetLocalCFL(iPoint, turbCFL); + } + END_SU2_OMP_FOR + } + /*--- Preprocessing ---*/ solvers_fine[Solver_Position]->Preprocessing(geometry_fine, solvers_fine, config[iZone], diff --git a/TestCases/euler/CRM/inv_CRM_JST.cfg b/TestCases/euler/CRM/inv_CRM_JST.cfg index 63c749021f2..60080a0254b 100644 --- a/TestCases/euler/CRM/inv_CRM_JST.cfg +++ b/TestCases/euler/CRM/inv_CRM_JST.cfg @@ -43,11 +43,11 @@ MARKER_MONITORING= ( fuselage , Wing , HTP ) % ------------- COMMON PARAMETERS TO DEFINE THE NUMERICAL METHOD --------------% % NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES -CFL_NUMBER= 5.0 +CFL_NUMBER= 100.0 CFL_ADAPT= NO CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.0, 100.0 ) RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) -EXT_ITER= 99999 +ITER= 1000 LINEAR_SOLVER= FGMRES LINEAR_SOLVER_ERROR= 1E-1 LINEAR_SOLVER_ITER= 3 @@ -55,12 +55,16 @@ LINEAR_SOLVER_ITER= 3 % -------------------------- MULTIGRID PARAMETERS -----------------------------% % MGLEVEL= 3 +MG_MIN_MESHSIZE= 100 MGCYCLE= V_CYCLE -MG_PRE_SMOOTH= ( 4, 4, 4, 4 ) -MG_POST_SMOOTH= ( 4, 4, 4, 4 ) +MG_SMOOTH_OUTPUT= YES +MG_SMOOTH_EARLY_EXIT= YES +MG_PRE_SMOOTH= ( 5, 5, 5, 5 ) +MG_POST_SMOOTH= ( 5, 5, 5, 5 ) MG_CORRECTION_SMOOTH= ( 1, 1, 1, 1 ) -MG_DAMP_RESTRICTION= 0.5 -MG_DAMP_PROLONGATION= 0.5 +MG_DAMP_RESTRICTION= 0.75 +MG_DAMP_PROLONGATION= 0.75 +MG_CFL_SCALING= 0.5, 0.5, 0.5 % -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% % diff --git a/TestCases/euler/channel/inv_channel.cfg b/TestCases/euler/channel/inv_channel.cfg index 5161a04413d..8145521b147 100644 --- a/TestCases/euler/channel/inv_channel.cfg +++ b/TestCases/euler/channel/inv_channel.cfg @@ -47,11 +47,11 @@ MARKER_MONITORING= ( upper_wall, lower_wall ) % ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% % NUM_METHOD_GRAD= GREEN_GAUSS -CFL_NUMBER= 6.0 +CFL_NUMBER= 100.0 CFL_ADAPT= NO CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.0, 100.0 ) RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) -EXT_ITER= 999999 +ITER= 1000 % ------------------------ LINEAR SOLVER DEFINITION ---------------------------% % @@ -63,12 +63,15 @@ LINEAR_SOLVER_ITER= 3 % -------------------------- MULTIGRID PARAMETERS -----------------------------% % MGLEVEL= 3 -MGCYCLE= V_CYCLE +MGCYCLE= W_CYCLE +MG_SMOOTH_OUTPUT= NO +MG_SMOOTH_EARLY_EXIT= YES MG_PRE_SMOOTH= ( 4, 4, 4, 4 ) MG_POST_SMOOTH= ( 4, 4, 4, 4 ) MG_CORRECTION_SMOOTH= ( 1, 1, 1, 1 ) MG_DAMP_RESTRICTION= 0.5 MG_DAMP_PROLONGATION= 0.5 +MG_CFL_SCALING= 0.5, 0.5, 0.5 % -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% % diff --git a/TestCases/euler/naca0012/inv_NACA0012.cfg b/TestCases/euler/naca0012/inv_NACA0012.cfg index ca235bb07a3..814649dbc30 100644 --- a/TestCases/euler/naca0012/inv_NACA0012.cfg +++ b/TestCases/euler/naca0012/inv_NACA0012.cfg @@ -46,7 +46,7 @@ MARKER_DESIGNING = ( airfoil ) % NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES OBJECTIVE_FUNCTION= DRAG -CFL_NUMBER= 5.0 +CFL_NUMBER= 100.0 CFL_ADAPT= NO CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.0, 100.0 ) ITER= 250 @@ -62,11 +62,15 @@ LINEAR_SOLVER_ITER= 5 % MGLEVEL= 3 MGCYCLE= W_CYCLE -MG_PRE_SMOOTH= ( 1, 2, 3, 3 ) -MG_POST_SMOOTH= ( 0, 0, 0, 0 ) -MG_CORRECTION_SMOOTH= ( 0, 0, 0, 0 ) -MG_DAMP_RESTRICTION= 1.0 -MG_DAMP_PROLONGATION= 1.0 +MG_SMOOTH_OUTPUT= YES +MG_SMOOTH_EARLY_EXIT= YES + +MG_PRE_SMOOTH= ( 5, 5, 5, 5 ) +MG_POST_SMOOTH= ( 5, 5, 5, 5 ) +MG_CORRECTION_SMOOTH= ( 1, 1, 1, 1 ) +MG_DAMP_RESTRICTION= 0.5 +MG_DAMP_PROLONGATION= 0.5 +MG_CFL_SCALING= 0.5, 0.5, 0.5 % -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% % diff --git a/TestCases/euler/oneram6/inv_ONERAM6.cfg b/TestCases/euler/oneram6/inv_ONERAM6.cfg index 6517b957871..59c19e14a53 100644 --- a/TestCases/euler/oneram6/inv_ONERAM6.cfg +++ b/TestCases/euler/oneram6/inv_ONERAM6.cfg @@ -49,15 +49,15 @@ MARKER_DESIGNING = ( WING ) % NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES OBJECTIVE_FUNCTION= DRAG -CFL_NUMBER= 5.0 +CFL_NUMBER= 100.0 CFL_ADAPT= NO CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.0, 100.0 ) RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) ITER= 99999 LINEAR_SOLVER= FGMRES LINEAR_SOLVER_PREC= LU_SGS -LINEAR_SOLVER_ERROR= 1E-6 -LINEAR_SOLVER_ITER= 2 +LINEAR_SOLVER_ERROR= 1E-1 +LINEAR_SOLVER_ITER= 5 % ----------------------- SLOPE LIMITER DEFINITION ----------------------------% % @@ -92,7 +92,7 @@ TIME_DISCRE_ADJFLOW= EULER_IMPLICIT % --------------------------- CONVERGENCE PARAMETERS --------------------------& % CONV_RESIDUAL_MINVAL= -12 -CONV_STARTITER= 25 +CONV_STARTITER= 10 CONV_CAUCHY_ELEMS= 100 CONV_CAUCHY_EPS= 1E-10 diff --git a/config_template.cfg b/config_template.cfg index bc6be98103a..45a822755c9 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1723,6 +1723,15 @@ MG_IMPLICIT_LINES= NO % Maximum nodes on a wall-normal implicit agglomeration line, including the wall seed. % Increase to extend the line deeper into the boundary layer (default 20). MG_IMPLICIT_LINES_MAX_LENGTH= 20 +% +% Use isotropic (vs anisotropic) agglomeration for implicit lines (NO, YES) +% Anisotropic (NO): Pair cells normal to wall (2 cells per coarse CV, reduces mesh ~2x) +% Isotropic (YES): Pair cells in all directions (4 cells per coarse CV, reduces mesh ~4x) +MG_IMPLICIT_LINES_ISOTROPIC= NO +% +% Number of iterations on coarsest mesh during Full Multigrid (FMG) startup phase. +% After this many iterations, solution is prolongated to finer mesh (default 100). +MG_STARTUP_ITER= 100 % -------------------------- MESH SMOOTHING -----------------------------% %