From 23ac38b3e3ab20a46cbf0a771bd77f3d9ce7c40a Mon Sep 17 00:00:00 2001 From: bigfooted Date: Fri, 8 Nov 2024 10:28:40 +0100 Subject: [PATCH 01/41] initial commit --- .../include/geometry/CMultiGridGeometry.hpp | 2 +- Common/src/geometry/CMultiGridGeometry.cpp | 132 ++++++++++++------ 2 files changed, 94 insertions(+), 40 deletions(-) diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index c3e897f7f311..7a2d65bb3629 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -45,7 +45,7 @@ class CMultiGridGeometry final : public CGeometry { * \param[in] config - Definition of the particular problem. * \return TRUE or FALSE depending if the control volume can be agglomerated. */ - bool SetBoundAgglomeration(unsigned long CVPoint, short marker_seed, const CGeometry* fine_grid, + bool SetBoundAgglomeration(unsigned long CVPoint, vector marker_seed, const CGeometry* fine_grid, const CConfig* config) const; /*! diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 8c5bfddf39ec..4182d7ea62cc 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -31,6 +31,10 @@ #include "../../../Common/include/toolboxes/geometry_toolbox.hpp" CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, unsigned short iMesh) : CGeometry() { + + vector marker_seed; + + nDim = fine_grid->GetnDim(); // Write the number of dimensions of the coarse grid. /*--- Create a queue system to do the agglomeration @@ -92,43 +96,58 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un bool agglomerate_seed = true; auto counter = 0; unsigned short copy_marker[3] = {}; - const auto marker_seed = iMarker; + marker_seed.push_back(iMarker); /*--- For a particular point in the fine grid we save all the markers that are in that point ---*/ - + //short counter2 = 1; for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker() && counter < 3; jMarker++) { if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { copy_marker[counter] = jMarker; counter++; + + if (jMarker != iMarker) { + marker_seed.push_back(jMarker); + //counter2++; + } + } } - /*--- To aglomerate a vertex it must have only one physical bc!! + /*--- To agglomerate a vertex it must have only one physical bc!! This can be improved. If there is only a marker, it is a good candidate for agglomeration ---*/ - + /*--- Valley -> Interior : never + * Valley -> Valley : conditional + * Valley -> Ridge : never ---*/ if (counter == 1) { agglomerate_seed = true; /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ - if (config->GetMarker_All_KindBC(marker_seed) == EULER_WALL) agglomerate_seed = false; + //if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; } + /*--- If there are two markers, we will agglomerate if any of the markers is SEND_RECEIVE ---*/ - + /*--- Ridge -> Interior : never + * Ridge -> Valley : never + * Ridge -> Ridge : conditional. Conditional means should be on the same marker. ---*/ if (counter == 2) { - agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || - (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); + agglomerate_seed = ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || + (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE) + || + (config->GetMarker_All_KindBC(copy_marker[0]) == + config->GetMarker_All_KindBC(copy_marker[1])) ); /* --- Euler walls can also not be agglomerated when the point has 2 markers ---*/ - if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || - (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { - agglomerate_seed = false; - } + //if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || + // (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { + // agglomerate_seed = false; + //} } - /*--- If there are more than 2 markers, the aglomeration will be discarded ---*/ + /*--- Corner -> Any: never agglomerate ---*/ + /*--- If there are more than 2 markers, the agglomeration will be discarded ---*/ if (counter > 2) agglomerate_seed = false; @@ -138,6 +157,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Now we do a sweep over all the nodes that surround the seed point ---*/ for (auto CVPoint : fine_grid->nodes->GetPoints(iPoint)) { + /*--- The new point can be agglomerated ---*/ if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { @@ -506,7 +526,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un edgeColorGroupSize = config->GetEdgeColoringGroupSize(); } -bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, short marker_seed, const CGeometry* fine_grid, +bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector marker_seed, const CGeometry* fine_grid, const CConfig* config) const { bool agglomerate_CV = false; @@ -515,9 +535,11 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, short mark if ((!fine_grid->nodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint)) && (GeometricalCheck(CVPoint, fine_grid, config))) { + /*--- If the point belongs to a boundary, its type must be compatible with the seed marker. ---*/ if (fine_grid->nodes->GetBoundary(CVPoint)) { + /*--- Identify the markers of the vertex that we want to agglomerate ---*/ // count number of markers on the agglomeration candidate @@ -530,55 +552,87 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, short mark } } - /*--- The basic condition is that the aglomerated vertex must have the same physical marker, - but eventually a send-receive condition ---*/ + //count again the markers on the seed + // for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker() && counter < 3; jMarker++) { + // if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { + // copy_marker[counter] = jMarker; + // counter++; + // } + // } - /*--- Only one marker in the vertex that is going to be aglomerated ---*/ + /*--- The basic condition is that the agglomerated vertex must have the same physical marker, + but eventually a send-receive condition ---*/ - if (counter == 1) { + /*--- Only one marker in the vertex that is going to be agglomerated ---*/ + /*--- Ridge(2) -> Valley(1) : never + * Valley -> Valley(1) : conditional (should be same marker) ---*/ + if ((counter == 1) && (marker_seed.size() == 1)) { /*--- We agglomerate if there is only one marker and it is the same marker as the seed marker ---*/ // note that this should be the same marker id, not just the same marker type - if (copy_marker[0] == marker_seed) agglomerate_CV = true; + // !!!! note that when the seed has 2 markers, we have a problem. + if (copy_marker[0] == marker_seed[0]) agglomerate_CV = true; - /*--- If there is only one marker, but the marker is the SEND_RECEIVE ---*/ - if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - agglomerate_CV = true; + /*--- If there is only one marker, but the marker is the SEND_RECEIVE ---*/ + // Nijso: that means the child is an mpi interface in the interior -> do NOT agglomerate unless the + // marker of the seed is also SEND_RECEIVE + else if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { + //agglomerate_CV = true; + agglomerate_CV = false; } - if ((config->GetMarker_All_KindBC(marker_seed) == SYMMETRY_PLANE) || - (config->GetMarker_All_KindBC(marker_seed) == EULER_WALL)) { - if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - agglomerate_CV = false; - } - } + //if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || + // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { + // if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { + // agglomerate_CV = false; + // } + //} } - /*--- If there are two markers in the vertex that is going to be aglomerated ---*/ - - if (counter == 2) { + /*--- If there are two markers in the vertex that is going to be agglomerated ---*/ + /*--- ridge(2) -> ridge(2): conditionally allow (same marker) + * ridge -> valley : never (seed has 1 marker) + * ridge -> corner : never (seed has >2 marker ) + *---*/ + if ((counter == 2) && (marker_seed.size() == 2)) { /*--- First we verify that the seed is a physical boundary ---*/ - if (config->GetMarker_All_KindBC(marker_seed) != SEND_RECEIVE) { + if (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE) { + /*--- Then we check that one of the markers is equal to the seed marker, and the other is send/receive ---*/ - if (((copy_marker[0] == marker_seed) && (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || - ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed))) { + if (((copy_marker[0] == marker_seed[0]) && (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || + ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed[0]))) { + agglomerate_CV = true; + } + else + /*--- check that ridges are equal on seed and on child ---*/ + if ((config->GetMarker_All_KindBC(copy_marker[0]) == config->GetMarker_All_KindBC(copy_marker[1])) + && + (config->GetMarker_All_KindBC(marker_seed[0]) == config->GetMarker_All_KindBC(marker_seed[1])) + ) { + cout << "ridges can be agglomerated" << endl; agglomerate_CV = true; } } } } /*--- If the element belongs to the domain, it is always agglomerated. ---*/ + /*--- any marker -> interior : never agglomerate ---*/ else { - agglomerate_CV = true; + + // only agglomerate internal nodes with mpi nodes + //if (config->GetMarker_All_KindBC(marker_seed[0]) == SEND_RECEIVE) + // agglomerate_CV = true; + //else + agglomerate_CV = false; // actually, for symmetry (and possibly other cells) we only agglomerate cells that are on the marker // at this point, the seed was on the boundary and the CV was not. so we check if the seed is a symmetry - if ((config->GetMarker_All_KindBC(marker_seed) == SYMMETRY_PLANE) || - (config->GetMarker_All_KindBC(marker_seed) == EULER_WALL)) { - agglomerate_CV = false; - } + //if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || + // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { + // agglomerate_CV = false; + //} } } From a47c0f4dbc00b791ecea3c3b7d7f9982a5425253 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Fri, 8 Nov 2024 14:02:20 +0100 Subject: [PATCH 02/41] fix vector --- Common/include/geometry/CMultiGridGeometry.hpp | 2 +- Common/src/geometry/CMultiGridGeometry.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 7a2d65bb3629..4e98c3a938c4 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -57,7 +57,7 @@ class CMultiGridGeometry final : public CGeometry { bool GeometricalCheck(unsigned long iPoint, const CGeometry* fine_grid, const CConfig* config) const; /*! - * \brief Determine if a CVPoint van be agglomerated, if it have the same marker point as the seed. + * \brief Determine if a CVPoint can be agglomerated, if it has the same marker point as the seed. * \param[out] Suitable_Indirect_Neighbors - List of Indirect Neighbours that can be agglomerated. * \param[in] iPoint - Seed point. * \param[in] Index_CoarseCV - Index of agglomerated point. diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 4182d7ea62cc..e1ded738c7d4 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -32,7 +32,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, unsigned short iMesh) : CGeometry() { - vector marker_seed; nDim = fine_grid->GetnDim(); // Write the number of dimensions of the coarse grid. @@ -80,6 +79,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- If the element has not been previously agglomerated and it belongs to this physical domain, and it meets the geometrical criteria, the agglomeration is studied. ---*/ + vector marker_seed; if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint)) && (GeometricalCheck(iPoint, fine_grid, config))) { From f846cbbc5fac657631d85d704ceacfc8929e54fb Mon Sep 17 00:00:00 2001 From: bigfooted Date: Fri, 15 Nov 2024 15:06:22 +0100 Subject: [PATCH 03/41] update agglomeration --- .../include/geometry/CMultiGridGeometry.hpp | 6 +- Common/include/geometry/CMultiGridQueue.hpp | 2 +- Common/src/geometry/CMultiGridGeometry.cpp | 136 +++++++----------- SU2_CFD/src/solvers/CSolverFactory.cpp | 1 + 4 files changed, 55 insertions(+), 90 deletions(-) diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 4e98c3a938c4..c0421ea23bd6 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -31,14 +31,14 @@ /*! * \class CMultiGridGeometry - * \brief Class for defining the multigrid geometry, the main delicated part is the + * \brief Class for defining the multigrid geometry, the main delegated part is the * agglomeration stage, which is done in the declaration. * \author F. Palacios */ class CMultiGridGeometry final : public CGeometry { private: /*! - * \brief Determine if a CVPoint van be agglomerated, if it have the same marker point as the seed. + * \brief Determine if a CVPoint can be agglomerated, if it has the same marker point as the seed. * \param[in] CVPoint - Control volume to be agglomerated. * \param[in] marker_seed - Marker of the seed. * \param[in] fine_grid - Geometrical definition of the problem. @@ -49,7 +49,7 @@ class CMultiGridGeometry final : public CGeometry { const CConfig* config) const; /*! - * \brief Determine if a can be agglomerated using geometrical criteria. + * \brief Determine if a Point can be agglomerated using geometrical criteria. * \param[in] iPoint - Seed point. * \param[in] fine_grid - Geometrical definition of the problem. * \param[in] config - Definition of the particular problem. diff --git a/Common/include/geometry/CMultiGridQueue.hpp b/Common/include/geometry/CMultiGridQueue.hpp index 0e48f9385179..3ac15acdaf0f 100644 --- a/Common/include/geometry/CMultiGridQueue.hpp +++ b/Common/include/geometry/CMultiGridQueue.hpp @@ -93,7 +93,7 @@ class CMultiGridQueue { void IncrPriorityCV(unsigned long incrPoint); /*! - * \brief Increase the priority of the CV. + * \brief Reduce the priority of the CV. * \param[in] redPoint - Index of the control volume. */ void RedPriorityCV(unsigned long redPoint); diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index e1ded738c7d4..e020c4481d8a 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -31,16 +31,13 @@ #include "../../../Common/include/toolboxes/geometry_toolbox.hpp" CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, unsigned short iMesh) : CGeometry() { - - - nDim = fine_grid->GetnDim(); // Write the number of dimensions of the coarse grid. /*--- Create a queue system to do the agglomeration 1st) More than two markers ---> Vertices (never agglomerate) 2nd) Two markers ---> Edges (agglomerate if same BC, never agglomerate if different BC) - 3rd) One marker ---> Surface (always agglomarate) - 4th) No marker ---> Internal Volume (always agglomarate) ---*/ + 3rd) One marker ---> Surface (always agglomerate) + 4th) No marker ---> Internal Volume (always agglomerate) ---*/ /*--- Set a marker to indicate indirect agglomeration, for quads and hexs, i.e. consider up to neighbors of neighbors of neighbors. @@ -100,7 +97,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- For a particular point in the fine grid we save all the markers that are in that point ---*/ - //short counter2 = 1; + for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker() && counter < 3; jMarker++) { if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { copy_marker[counter] = jMarker; @@ -108,45 +105,38 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if (jMarker != iMarker) { marker_seed.push_back(jMarker); - //counter2++; } - } } /*--- To agglomerate a vertex it must have only one physical bc!! This can be improved. If there is only a marker, it is a good candidate for agglomeration ---*/ - /*--- Valley -> Interior : never - * Valley -> Valley : conditional - * Valley -> Ridge : never ---*/ + + /*--- Valley -> Valley : conditionally allowed when both points are on the same marker. ---*/ + /*--- ! Note that in the case of MPI SEND_RECEIVE markers, we might need other conditions ---*/ if (counter == 1) { agglomerate_seed = true; /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ - //if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; + //if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) + // agglomerate_seed = false; } /*--- If there are two markers, we will agglomerate if any of the markers is SEND_RECEIVE ---*/ - /*--- Ridge -> Interior : never - * Ridge -> Valley : never - * Ridge -> Ridge : conditional. Conditional means should be on the same marker. ---*/ + if (counter == 2) { - agglomerate_seed = ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || - (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE) - || - (config->GetMarker_All_KindBC(copy_marker[0]) == - config->GetMarker_All_KindBC(copy_marker[1])) ); + //agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || + // (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); /* --- Euler walls can also not be agglomerated when the point has 2 markers ---*/ //if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || // (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { - // agglomerate_seed = false; + agglomerate_seed = true; //} } - /*--- Corner -> Any: never agglomerate ---*/ /*--- If there are more than 2 markers, the agglomeration will be discarded ---*/ if (counter > 2) agglomerate_seed = false; @@ -157,7 +147,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Now we do a sweep over all the nodes that surround the seed point ---*/ for (auto CVPoint : fine_grid->nodes->GetPoints(iPoint)) { - /*--- The new point can be agglomerated ---*/ if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { @@ -493,7 +482,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un SetGlobal_nPointDomain(Global_nPointCoarse); if (iMesh != MESH_0) { - const su2double factor = 1.5; + //const su2double factor = 1.5; //nijso: too high + const su2double factor = 1.0; const su2double Coeff = pow(su2double(Global_nPointFine) / Global_nPointCoarse, 1.0 / nDim); const su2double CFL = factor * config->GetCFL(iMesh - 1) / Coeff; config->SetCFL(iMesh, CFL); @@ -501,7 +491,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un const su2double ratio = su2double(Global_nPointFine) / su2double(Global_nPointCoarse); - if (((nDim == 2) && (ratio < 2.5)) || ((nDim == 3) && (ratio < 2.5))) { + //if (((nDim == 2) && (ratio < 2.5)) || ((nDim == 3) && (ratio < 2.5))) { + // nijso: too high for very small test meshes. + if (((nDim == 2) && (ratio < 2.0)) || ((nDim == 3) && (ratio < 2.0))) { config->SetMGLevels(iMesh - 1); } else if (rank == MASTER_NODE) { PrintingToolbox::CTablePrinter MGTable(&std::cout); @@ -535,11 +527,9 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectornodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint)) && (GeometricalCheck(CVPoint, fine_grid, config))) { - /*--- If the point belongs to a boundary, its type must be compatible with the seed marker. ---*/ if (fine_grid->nodes->GetBoundary(CVPoint)) { - /*--- Identify the markers of the vertex that we want to agglomerate ---*/ // count number of markers on the agglomeration candidate @@ -552,87 +542,61 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectorGetnMarker() && counter < 3; jMarker++) { - // if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { - // copy_marker[counter] = jMarker; - // counter++; - // } - // } - /*--- The basic condition is that the agglomerated vertex must have the same physical marker, but eventually a send-receive condition ---*/ /*--- Only one marker in the vertex that is going to be agglomerated ---*/ - /*--- Ridge(2) -> Valley(1) : never - * Valley -> Valley(1) : conditional (should be same marker) ---*/ - if ((counter == 1) && (marker_seed.size() == 1)) { + + /*--- Valley -> Valley: only if of the same type---*/ + if (counter == 1) { /*--- We agglomerate if there is only one marker and it is the same marker as the seed marker ---*/ // note that this should be the same marker id, not just the same marker type - // !!!! note that when the seed has 2 markers, we have a problem. - if (copy_marker[0] == marker_seed[0]) agglomerate_CV = true; - + if ((marker_seed.size()==1) && (copy_marker[0] == marker_seed[0])) agglomerate_CV = true; /*--- If there is only one marker, but the marker is the SEND_RECEIVE ---*/ - // Nijso: that means the child is an mpi interface in the interior -> do NOT agglomerate unless the - // marker of the seed is also SEND_RECEIVE - else if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - //agglomerate_CV = true; - agglomerate_CV = false; - } + + //if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { + // agglomerate_CV = true; + //} //if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { - // if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - // agglomerate_CV = false; - // } + // if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { + // agglomerate_CV = false; + // } //} } /*--- If there are two markers in the vertex that is going to be agglomerated ---*/ - /*--- ridge(2) -> ridge(2): conditionally allow (same marker) - * ridge -> valley : never (seed has 1 marker) - * ridge -> corner : never (seed has >2 marker ) - *---*/ - if ((counter == 2) && (marker_seed.size() == 2)) { + /*--- Ridge -> Ridge: only if of the same type (same marker ID) ---*/ + if (counter == 2) { + + // check if the seed also has 2 markers + if (marker_seed.size() == 2) { + // now check if the seed is on the same 2 marker ID's (note that if we allow that they are of the same + // marker type, we need to check that the alignement of the markers is correct as well. better not go there.) + if ( ((marker_seed[0] = copy_marker[0]) && (marker_seed[1] = copy_marker[1])) || + ((marker_seed[0] = copy_marker[1]) && (marker_seed[1] = copy_marker[0])) ) { + agglomerate_CV = true; + } + } /*--- First we verify that the seed is a physical boundary ---*/ - if (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE) { + // if (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE) { + // /*--- Then we check that one of the markers is equal to the seed marker, and the other is send/receive ---*/ - /*--- Then we check that one of the markers is equal to the seed marker, and the other is send/receive ---*/ - - if (((copy_marker[0] == marker_seed[0]) && (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || - ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed[0]))) { - agglomerate_CV = true; - } - else - /*--- check that ridges are equal on seed and on child ---*/ - if ((config->GetMarker_All_KindBC(copy_marker[0]) == config->GetMarker_All_KindBC(copy_marker[1])) - && - (config->GetMarker_All_KindBC(marker_seed[0]) == config->GetMarker_All_KindBC(marker_seed[1])) - ) { - cout << "ridges can be agglomerated" << endl; - agglomerate_CV = true; - } - } + // if (((copy_marker[0] == marker_seed[0]) && (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || + // ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed[0]))) { + agglomerate_CV = false; + // agglomerate_CV = true; + // } + // } } } - /*--- If the element belongs to the domain, it is always agglomerated. ---*/ - /*--- any marker -> interior : never agglomerate ---*/ + /*--- If the element belongs to the domain, it is never agglomerated. ---*/ + /*--- Any -> Interior : disallowed ---*/ else { - - // only agglomerate internal nodes with mpi nodes - //if (config->GetMarker_All_KindBC(marker_seed[0]) == SEND_RECEIVE) - // agglomerate_CV = true; - //else agglomerate_CV = false; - - // actually, for symmetry (and possibly other cells) we only agglomerate cells that are on the marker - // at this point, the seed was on the boundary and the CV was not. so we check if the seed is a symmetry - //if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || - // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { - // agglomerate_CV = false; - //} } } diff --git a/SU2_CFD/src/solvers/CSolverFactory.cpp b/SU2_CFD/src/solvers/CSolverFactory.cpp index 743775ad139b..ba7ae481484e 100644 --- a/SU2_CFD/src/solvers/CSolverFactory.cpp +++ b/SU2_CFD/src/solvers/CSolverFactory.cpp @@ -307,6 +307,7 @@ CSolver* CSolverFactory::CreateSubSolver(SUB_SOLVER_TYPE kindSolver, CSolver **s case SUB_SOLVER_TYPE::TURB_SST: genericSolver = CreateTurbSolver(kindTurbModel, solver, geometry, config, iMGLevel, false); metaData.integrationType = INTEGRATION_TYPE::SINGLEGRID; + //metaData.integrationType = INTEGRATION_TYPE::MULTIGRID; break; case SUB_SOLVER_TYPE::TEMPLATE: genericSolver = new CTemplateSolver(geometry, config); From 6fbef927f55d3fd10c41f5470b5b5ffc6afbdb0a Mon Sep 17 00:00:00 2001 From: bigfooted Date: Thu, 30 Oct 2025 20:32:09 +0100 Subject: [PATCH 04/41] testing agglomeration --- Common/include/CConfig.hpp | 27 +- Common/src/CConfig.cpp | 2 +- Common/src/geometry/CGeometry.cpp | 9 +- Common/src/geometry/CMultiGridGeometry.cpp | 290 ++++++++++++------ .../integration/CMultiGridIntegration.hpp | 23 ++ .../include/solvers/CFVMFlowSolverBase.hpp | 2 +- .../include/solvers/CFVMFlowSolverBase.inl | 4 +- SU2_CFD/include/solvers/CSolver.hpp | 4 +- .../src/integration/CFEM_DG_Integration.cpp | 12 +- SU2_CFD/src/integration/CIntegration.cpp | 2 +- .../src/integration/CMultiGridIntegration.cpp | 172 ++++++----- SU2_CFD/src/solvers/CIncNSSolver.cpp | 1 + SU2_CFD/src/solvers/CNSSolver.cpp | 6 +- .../compressible_SA/turb_SA_flatplate.cfg | 2 +- .../compressible_SST/turb_SST_flatplate.cfg | 2 +- 15 files changed, 361 insertions(+), 197 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index c996c643e1f1..24985fcecfd7 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -483,7 +483,7 @@ class CConfig { unsigned short **DegreeFFDBox; /*!< \brief Degree of the FFD boxes. */ string *FFDTag; /*!< \brief Parameters of the design variable. */ string *TagFFDBox; /*!< \brief Tag of the FFD box. */ - unsigned short GeometryMode; /*!< \brief Gemoetry mode (analysis or gradient computation). */ + unsigned short GeometryMode; /*!< \brief Geometry mode (analysis or gradient computation). */ unsigned short MGCycle; /*!< \brief Kind of multigrid cycle. */ unsigned short FinestMesh; /*!< \brief Finest mesh for the full multigrid approach. */ unsigned short nFFD_Fix_IDir, @@ -2881,7 +2881,7 @@ class CConfig { unsigned short GetFinestMesh(void) const { return FinestMesh; } /*! - * \brief Get the kind of multigrid (V or W). + * \brief Get the kind of multigrid (V, W or FULLMG). * \note This variable is used in a recursive way to perform the different kind of cycles * \return 0 or 1 depending of we are dealing with a V or W cycle. */ @@ -3024,7 +3024,28 @@ class CConfig { * \brief Get the number of Runge-Kutta steps. * \return Number of Runge-Kutta steps. */ - unsigned short GetnRKStep(void) const { return nRKStep; } + unsigned short GetnRKStep(void) const { + + unsigned short iRKLimit = 1; + + switch (GetKind_TimeIntScheme()) { + case RUNGE_KUTTA_EXPLICIT: + iRKLimit = GetnRKStep(); + break; + case CLASSICAL_RK4_EXPLICIT: + iRKLimit = 4; + break; + case EULER_EXPLICIT: + case EULER_IMPLICIT: + iRKLimit = 1; + break; + default: + iRKLimit = 1; + break; + } + //return nRKStep; + return iRKLimit; +} /*! * \brief Get the number of time levels for time accurate local time stepping. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 542f79b4b815..b920f422aebf 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -3528,7 +3528,7 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i } /*--- Check if MULTIGRID is requested in VOLUME_OUTPUT and set the config boolean accordingly. ---*/ - Wrt_MultiGrid = false; + Wrt_MultiGrid = true; for (unsigned short iField = 0; iField < nVolumeOutput; iField++) { if(VolumeOutput[iField].find("MULTIGRID") != string::npos) { Wrt_MultiGrid = true; diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index 4af47d310fd5..d2b79520f8f0 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -3902,11 +3902,14 @@ void CGeometry::ColorMGLevels(unsigned short nMGLevels, const CGeometry* const* for (auto step = 0u; step < iMesh; ++step) { auto coarseMesh = geometry[iMesh - 1 - step]; if (step) - for (auto iPoint = 0ul; iPoint < coarseMesh->GetnPoint(); ++iPoint) + for (auto iPoint = 0ul; iPoint < coarseMesh->GetnPoint(); ++iPoint) { CoarseGridColor_(iPoint, step) = CoarseGridColor_(coarseMesh->nodes->GetParent_CV(iPoint), step - 1); + } else - for (auto iPoint = 0ul; iPoint < coarseMesh->GetnPoint(); ++iPoint) - CoarseGridColor_(iPoint, step) = color[coarseMesh->nodes->GetParent_CV(iPoint)]; + for (auto iPoint = 0ul; iPoint < coarseMesh->GetnPoint(); ++iPoint){ + //CoarseGridColor_(iPoint, step) = color[coarseMesh->nodes->GetParent_CV(iPoint)]; + CoarseGridColor_(iPoint, step) = coarseMesh->nodes->GetParent_CV(iPoint); + } } } } diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 489016d18953..e1d697116975 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -30,10 +30,15 @@ #include "../../include/toolboxes/printing_toolbox.hpp" #include "../../../Common/include/toolboxes/geometry_toolbox.hpp" + +/*--- Nijso says: this could perhaps be replaced by metis partitioning? ---*/ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, unsigned short iMesh) : CGeometry() { nDim = fine_grid->GetnDim(); // Write the number of dimensions of the coarse grid. + /*--- Maximum agglomeration size in 2D is 4 nodes, in 3D is 8 nodes. ---*/ + const short int maxAgglomSize=4; - /*--- Create a queue system to do the agglomeration + /*--- Agglomeration Scheme II (Nishikawa, Diskin, Thomas) + Create a queue system to do the agglomeration 1st) More than two markers ---> Vertices (never agglomerate) 2nd) Two markers ---> Edges (agglomerate if same BC, never agglomerate if different BC) 3rd) One marker ---> Surface (always agglomerate) @@ -67,12 +72,19 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un unsigned long Index_CoarseCV = 0; - /*--- The first step is the boundary agglomeration. ---*/ - + /*--- STEP 1: The first step is the boundary agglomeration. ---*/ + cout << "loop over Markers" << endl; for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { + cout << "*** loop:marker = " << iMarker << " " << config->GetMarker_All_TagBound(iMarker) << endl; for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { + const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); + cout << "*** seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " << fine_grid->nodes->GetCoord(iPoint, 1) << endl; + cout << "*** seed point is on the marker: " << fine_grid->nodes->GetVertex(iPoint, iMarker) + << " , agglomerated=" <nodes->GetAgglomerate(iPoint) << " , domain = " <nodes->GetDomain(iPoint) + << " , geo =" << GeometricalCheck(iPoint, fine_grid, config) + <SetChildren_CV(Index_CoarseCV, 0, iPoint); - bool agglomerate_seed = true; + bool agglomerate_seed = false; auto counter = 0; unsigned short copy_marker[3] = {}; marker_seed.push_back(iMarker); @@ -98,8 +112,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- For a particular point in the fine grid we save all the markers that are in that point ---*/ - for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker() && counter < 3; jMarker++) { + for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker(); jMarker++) { + const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); //fine_grid->GetMarker_Tag(jMarker); + cout << " marker = " << jMarker<<" " << Marker_Tag << endl; if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { + cout << " point is on marker" << endl; copy_marker[counter] = jMarker; counter++; @@ -109,13 +126,19 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } + + cout << " counter for this seed point = " << counter << endl; + /*--- To agglomerate a vertex it must have only one physical bc!! - This can be improved. If there is only a marker, it is a good + This can be improved. If there is only one marker, it is a good candidate for agglomeration ---*/ + /*--- 1 BC, so either an edge in 2D or the interior of a plane in 3D ---*/ /*--- Valley -> Valley : conditionally allowed when both points are on the same marker. ---*/ /*--- ! Note that in the case of MPI SEND_RECEIVE markers, we might need other conditions ---*/ if (counter == 1) { + // The seed/parent is one valley, so we set this part to true + // if the child is only on this same valley, we set it to true as well. agglomerate_seed = true; /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ @@ -126,70 +149,95 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- If there are two markers, we will agglomerate if any of the markers is SEND_RECEIVE ---*/ + /*--- Note that in 2D, this is a corner and we do not agglomerate. ---*/ + /*--- In 3D, we agglomerate if the 2 markers are the same. ---*/ if (counter == 2) { - //agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || - // (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); + /*--- Only agglomerate if the 2 markers are MPI markers. ---*/ + // ? not possible to have 2 send-receive markers on the same point? + agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || + (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); + + /*--- Note: We could also allow 2 markers that are of the same type, except when they are symmetry markers. ---*/ /* --- Euler walls can also not be agglomerated when the point has 2 markers ---*/ //if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || // (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { - agglomerate_seed = true; + // agglomerate_seed = true; //} } - /*--- If there are more than 2 markers, the agglomeration will be discarded ---*/ - + /*--- If there are more than 2 markers (corner node), the agglomeration will be discarded ---*/ if (counter > 2) agglomerate_seed = false; - /*--- If the seed can be agglomerated, we try to agglomerate more points ---*/ + + /*--- If the seed (parent) can be agglomerated, we try to agglomerate connected childs to the parent ---*/ + /*--- Note that in 2D we allow a maximum of 4 nodes to be agglomerated ---*/ if (agglomerate_seed) { + cout << " seed can be agglomerated to more points." << endl; /*--- Now we do a sweep over all the nodes that surround the seed point ---*/ - for (auto CVPoint : fine_grid->nodes->GetPoints(iPoint)) { - /*--- The new point can be agglomerated ---*/ + cout << " checking child CVPoint = " + << CVPoint + << ", coord = " + << fine_grid->nodes->GetCoord(CVPoint, 0) + << " " + << fine_grid->nodes->GetCoord(CVPoint, 1) + << endl; + /*--- The new point can be agglomerated ---*/ if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { + cout << " agglomerate " << CVPoint << " with seed point "<< iPoint << endl; /*--- We set the value of the parent ---*/ - fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); /*--- We set the value of the child ---*/ - nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; + /*--- In 2D, we only agglomerate 2 nodes. ---*/ + if (nDim==2) break; + } } - Suitable_Indirect_Neighbors.clear(); - - if (fine_grid->nodes->GetAgglomerate_Indirect(iPoint)) - SetSuitableNeighbors(Suitable_Indirect_Neighbors, iPoint, Index_CoarseCV, fine_grid); - /*--- Now we do a sweep over all the indirect nodes that can be added ---*/ - - for (auto CVPoint : Suitable_Indirect_Neighbors) { - /*--- The new point can be agglomerated ---*/ - - if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { - /*--- We set the value of the parent ---*/ - - fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); - - /*--- We set the indirect agglomeration information of the corse point - based on its children in the fine grid. ---*/ - - if (fine_grid->nodes->GetAgglomerate_Indirect(CVPoint)) - nodes->SetAgglomerate_Indirect(Index_CoarseCV, true); - - /*--- We set the value of the child ---*/ - - nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); - nChildren++; + /*--- Only take into account indirect neighbors for 3D faces, not 2D. ---*/ + if (nDim == 3) { + Suitable_Indirect_Neighbors.clear(); + + if (fine_grid->nodes->GetAgglomerate_Indirect(iPoint)) + SetSuitableNeighbors(Suitable_Indirect_Neighbors, iPoint, Index_CoarseCV, fine_grid); + + /*--- Now we do a sweep over all the indirect nodes that can be added ---*/ + for (auto CVPoint : Suitable_Indirect_Neighbors) { + cout << " Boundary: checking indirect neighbors " << CVPoint + << ", coord = " + << fine_grid->nodes->GetCoord(CVPoint, 0) + << " " + << fine_grid->nodes->GetCoord(CVPoint, 1) + << endl; + /*--- The new point can be agglomerated ---*/ + if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { + cout << " Boundary: indirect neighbor " << CVPoint << " can be agglomerated." << endl; + /*--- We set the value of the parent ---*/ + fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); + + /*--- We set the indirect agglomeration information of the corse point + based on its children in the fine grid. ---*/ + if (fine_grid->nodes->GetAgglomerate_Indirect(CVPoint)) + nodes->SetAgglomerate_Indirect(Index_CoarseCV, true); + + /*--- We set the value of the child ---*/ + nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); + nChildren++; + } } } } + + /*--- At this stage we can check if the node is an isolated node. ---*/ + /*--- Update the number of children of the coarse control volume. ---*/ nodes->SetnChildren_CV(Index_CoarseCV, nChildren); @@ -200,12 +248,13 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Do not agglomerate any leftover node with more than one physical boundary condition, i.e. make one coarse CV with a single child. ---*/ - for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - + cout << "point " << iPoint << ", parent = " << fine_grid->nodes->GetParent_CV(iPoint) + << " " << fine_grid->nodes->GetAgglomerate(iPoint) << endl; if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint))) { + cout << " Boundary:mark left-over nodes " << endl; fine_grid->nodes->SetParent_CV(iPoint, Index_CoarseCV); nodes->SetChildren_CV(Index_CoarseCV, 0, iPoint); nodes->SetnChildren_CV(Index_CoarseCV, 1); @@ -214,6 +263,20 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } + + /* --- Note that we can check index_coarse_cv for nr of children ---*/ + for (auto icoarse_cv = 0u; icoarse_cv < Index_CoarseCV; icoarse_cv++) { + cout << "coarse node " << icoarse_cv << ", children = " << nodes->GetnChildren_CV(icoarse_cv) << " " << endl; + } + + /*--- Check all agglomerations and see if there is a 1-node agglomeration ---*/ + for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { + for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { + const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); + cout << "ipoint = " << iPoint << " , " << nodes->GetnChildren_CV(iPoint) << " " << fine_grid->nodes->GetParent_CV(iPoint) << endl; + } + } + /*--- Update the queue with the results from the boundary agglomeration ---*/ for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) { @@ -232,8 +295,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } - /*--- Agglomerate the domain points. ---*/ - + /*--- STEP 2: Agglomerate the domain points. ---*/ + cout << "*********** STEP 2 ***" << endl; auto iteration = 0ul; while (!MGQueue_InnerCV.EmptyQueue() && (iteration < fine_grid->GetnPoint())) { const auto iPoint = MGQueue_InnerCV.NextCV(); @@ -245,6 +308,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint)) && (GeometricalCheck(iPoint, fine_grid, config))) { unsigned short nChildren = 1; + cout << "***** internal seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " << fine_grid->nodes->GetCoord(iPoint, 1) << endl; /*--- We set an index for the parent control volume ---*/ @@ -267,7 +331,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if ((!fine_grid->nodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint)) && (GeometricalCheck(CVPoint, fine_grid, config))) { /*--- We set the value of the parent ---*/ - + cout << "agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); /*--- We set the value of the child ---*/ @@ -280,36 +344,35 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un MGQueue_InnerCV.Update(CVPoint, fine_grid); } + if (nChildren==maxAgglomSize) break; } /*--- Identify the indirect neighbors ---*/ - Suitable_Indirect_Neighbors.clear(); if (fine_grid->nodes->GetAgglomerate_Indirect(iPoint)) SetSuitableNeighbors(Suitable_Indirect_Neighbors, iPoint, Index_CoarseCV, fine_grid); /*--- Now we do a sweep over all the indirect nodes that can be added ---*/ - for (auto CVPoint : Suitable_Indirect_Neighbors) { - /*--- The new point can be agglomerated ---*/ + // if we have reached the maximum, get out. + if (nChildren==maxAgglomSize) break; + /*--- The new point can be agglomerated ---*/ if ((!fine_grid->nodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint))) { - /*--- We set the value of the parent ---*/ + cout << "indirect agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; + /*--- We set the value of the parent ---*/ fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); /*--- We set the indirect agglomeration information ---*/ - if (fine_grid->nodes->GetAgglomerate_Indirect(CVPoint)) nodes->SetAgglomerate_Indirect(Index_CoarseCV, true); /*--- We set the value of the child ---*/ - nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; /*--- Update the queue with the new control volume (remove the CV and increase the priority of the neighbors) ---*/ - MGQueue_InnerCV.Update(CVPoint, fine_grid); } } @@ -330,6 +393,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) { if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint))) { + cout << "!!! agglomerate isolated point " << iPoint << endl; fine_grid->nodes->SetParent_CV(iPoint, Index_CoarseCV); if (fine_grid->nodes->GetAgglomerate_Indirect(iPoint)) nodes->SetAgglomerate_Indirect(Index_CoarseCV, true); nodes->SetChildren_CV(Index_CoarseCV, 0, iPoint); @@ -349,7 +413,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un for (auto iCoarsePoint = 0ul; iCoarsePoint < nPointDomain; iCoarsePoint++) { if (nodes->GetnPoint(iCoarsePoint) == 1) { /*--- Find the neighbor of the isolated point. This neighbor is the right control volume ---*/ - + cout << "isolated point " << iCoarsePoint << endl; const auto iCoarsePoint_Complete = nodes->GetPoint(iCoarsePoint, 0); /*--- Add the children to the connected control volume (and modify its parent indexing). @@ -515,9 +579,26 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } + // print out all point connections + for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) { + cout << iPoint << ", parent=" << fine_grid->nodes->GetParent_CV(iPoint) << endl; + } + + // loop over coarse points + for (auto iCoarsePoint = 0ul; iCoarsePoint < Index_CoarseCV; iCoarsePoint++) { + cout << iCoarsePoint << endl; + // print all fine-point children of the coarse point + for (auto iChildren = 0u; iChildren < nodes->GetnChildren_CV(iCoarsePoint); iChildren++) { + //const auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint, iChildren); + cout << " " <GetChildren_CV(iCoarsePoint, iChildren) << endl; + } + } + + edgeColorGroupSize = config->GetEdgeColoringGroupSize(); } + bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector marker_seed, const CGeometry* fine_grid, const CConfig* config) const { bool agglomerate_CV = false; @@ -550,11 +631,21 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector Valley: only if of the same type---*/ if (counter == 1) { /*--- We agglomerate if there is only one marker and it is the same marker as the seed marker ---*/ - // note that this should be the same marker id, not just the same marker type + // note that this should be the same marker id, not just the same marker type. + // also note that the seed point can have 2 markers, one of them may be a send-receive. if ((marker_seed.size()==1) && (copy_marker[0] == marker_seed[0])) agglomerate_CV = true; + // if ((marker_seed.size() == 2) && (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE)) { + // if (copy_marker[0] == marker_seed[1]) { + // agglomerate_CV = true; + // } + // } + // if ((marker_seed.size() == 2) && (config->GetMarker_All_KindBC(marker_seed[1]) != SEND_RECEIVE)) { + // if (copy_marker[0] == marker_seed[0]) { + // agglomerate_CV = true; + // } + // } /*--- If there is only one marker, but the marker is the SEND_RECEIVE ---*/ - //if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { // agglomerate_CV = true; //} @@ -571,15 +662,18 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector Ridge: only if of the same type (same marker ID) ---*/ if (counter == 2) { + // the child has 2 markers, it cannot be agglomerated with the seed. + agglomerate_CV = false; + // check if the seed also has 2 markers - if (marker_seed.size() == 2) { - // now check if the seed is on the same 2 marker ID's (note that if we allow that they are of the same - // marker type, we need to check that the alignement of the markers is correct as well. better not go there.) - if ( ((marker_seed[0] = copy_marker[0]) && (marker_seed[1] = copy_marker[1])) || - ((marker_seed[0] = copy_marker[1]) && (marker_seed[1] = copy_marker[0])) ) { - agglomerate_CV = true; - } - } + //if (marker_seed.size() == 2) { + // // now check if the seed is on the same 2 marker ID's (note that if we allow that they are of the same + // // marker type, we need to check that the alignement of the markers is correct as well. better not go there.) + // if ( ((marker_seed[0] = copy_marker[0]) && (marker_seed[1] = copy_marker[1])) || + // ((marker_seed[0] = copy_marker[1]) && (marker_seed[1] = copy_marker[0])) ) { + // agglomerate_CV = true; + // } + //} /*--- First we verify that the seed is a physical boundary ---*/ // if (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE) { @@ -587,22 +681,28 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectorGetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || // ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed[0]))) { - agglomerate_CV = false; + //agglomerate_CV = false; // agglomerate_CV = true; // } // } } } - /*--- If the element belongs to the domain, it is never agglomerated. ---*/ + /*--- If the element belongs to the domain, it is never agglomerated with the boundary. ---*/ /*--- Any -> Interior : disallowed ---*/ else { agglomerate_CV = false; } } + + + + return agglomerate_CV; } + +/*--- ---*/ bool CMultiGridGeometry::GeometricalCheck(unsigned long iPoint, const CGeometry* fine_grid, const CConfig* config) const { su2double max_dimension = 1.2; @@ -612,8 +712,10 @@ bool CMultiGridGeometry::GeometricalCheck(unsigned long iPoint, const CGeometry* bool Volume = true; su2double ratio = pow(fine_grid->nodes->GetVolume(iPoint), 1.0 / su2double(nDim)) * max_dimension; su2double limit = pow(config->GetDomainVolume(), 1.0 / su2double(nDim)); - if (ratio > limit) Volume = false; - + if (ratio > limit) { + Volume = false; + cout << "Volume limit reached!" << endl; + } /*--- Evaluate the stretching of the element ---*/ bool Stretching = true; @@ -655,8 +757,8 @@ void CMultiGridGeometry::SetSuitableNeighbors(vector& Suitable_In auto end = First_Neighbor_Points.end(); if (find(First_Neighbor_Points.begin(), end, kPoint) == end) { - Second_Neighbor_Points.push_back(kPoint); - Second_Origin_Points.push_back(jPoint); + Second_Neighbor_Points.push_back(kPoint); //neighbor of a neighbor, not connected to original ipoint + Second_Origin_Points.push_back(jPoint); // the neighbor that is connected to ipoint } } } @@ -691,43 +793,43 @@ void CMultiGridGeometry::SetSuitableNeighbors(vector& Suitable_In /// TODO: This repeats the process above but I doubt it catches any more points. - vector Third_Neighbor_Points, Third_Origin_Points; + // vector Third_Neighbor_Points, Third_Origin_Points; - for (auto kPoint : Suitable_Second_Neighbors) { - for (auto lPoint : fine_grid->nodes->GetPoints(kPoint)) { - /*--- Check that the third neighbor does not belong to the first neighbors or the seed ---*/ + // for (auto kPoint : Suitable_Second_Neighbors) { + // for (auto lPoint : fine_grid->nodes->GetPoints(kPoint)) { + // /*--- Check that the third neighbor does not belong to the first neighbors or the seed ---*/ - auto end1 = First_Neighbor_Points.end(); - if (find(First_Neighbor_Points.begin(), end1, lPoint) != end1) continue; + // auto end1 = First_Neighbor_Points.end(); + // if (find(First_Neighbor_Points.begin(), end1, lPoint) != end1) continue; - /*--- Check that the third neighbor does not belong to the second neighbors ---*/ + // /*--- Check that the third neighbor does not belong to the second neighbors ---*/ - auto end2 = Suitable_Second_Neighbors.end(); - if (find(Suitable_Second_Neighbors.begin(), end2, lPoint) != end2) continue; + // auto end2 = Suitable_Second_Neighbors.end(); + // if (find(Suitable_Second_Neighbors.begin(), end2, lPoint) != end2) continue; - Third_Neighbor_Points.push_back(lPoint); - Third_Origin_Points.push_back(kPoint); - } - } + // Third_Neighbor_Points.push_back(lPoint); + // Third_Origin_Points.push_back(kPoint); + // } + // } /*--- Identify those third neighbors that are repeated (candidate to be added). ---*/ - for (auto iNeighbor = 0ul; iNeighbor < Third_Neighbor_Points.size(); iNeighbor++) { - for (auto jNeighbor = iNeighbor + 1; jNeighbor < Third_Neighbor_Points.size(); jNeighbor++) { - /*--- Repeated third neighbor with different origin ---*/ + // for (auto iNeighbor = 0ul; iNeighbor < Third_Neighbor_Points.size(); iNeighbor++) { + // for (auto jNeighbor = iNeighbor + 1; jNeighbor < Third_Neighbor_Points.size(); jNeighbor++) { + // /*--- Repeated third neighbor with different origin ---*/ - if ((Third_Neighbor_Points[iNeighbor] == Third_Neighbor_Points[jNeighbor]) && - (Third_Origin_Points[iNeighbor] != Third_Origin_Points[jNeighbor])) { - Suitable_Indirect_Neighbors.push_back(Third_Neighbor_Points[iNeighbor]); - } - } - } + // if ((Third_Neighbor_Points[iNeighbor] == Third_Neighbor_Points[jNeighbor]) && + // (Third_Origin_Points[iNeighbor] != Third_Origin_Points[jNeighbor])) { + // Suitable_Indirect_Neighbors.push_back(Third_Neighbor_Points[iNeighbor]); + // } + // } + // } /*--- Remove duplicates from the final list of Suitable Indirect Neighbors. ---*/ - sort(Suitable_Indirect_Neighbors.begin(), Suitable_Indirect_Neighbors.end()); - auto it2 = unique(Suitable_Indirect_Neighbors.begin(), Suitable_Indirect_Neighbors.end()); - Suitable_Indirect_Neighbors.resize(it2 - Suitable_Indirect_Neighbors.begin()); + // sort(Suitable_Indirect_Neighbors.begin(), Suitable_Indirect_Neighbors.end()); + // auto it2 = unique(Suitable_Indirect_Neighbors.begin(), Suitable_Indirect_Neighbors.end()); + // Suitable_Indirect_Neighbors.resize(it2 - Suitable_Indirect_Neighbors.begin()); } void CMultiGridGeometry::SetPoint_Connectivity(const CGeometry* fine_grid) { diff --git a/SU2_CFD/include/integration/CMultiGridIntegration.hpp b/SU2_CFD/include/integration/CMultiGridIntegration.hpp index d44eb4c30927..7f8d4da35e37 100644 --- a/SU2_CFD/include/integration/CMultiGridIntegration.hpp +++ b/SU2_CFD/include/integration/CMultiGridIntegration.hpp @@ -133,6 +133,29 @@ class CMultiGridIntegration final : public CIntegration { void SetProlongated_Solution(unsigned short RunTime_EqSystem, CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, CGeometry *geo_coarse, CConfig *config); + + void PostSmoothing(unsigned short RunTime_EqSystem, + CSolver* solver_fine, + CNumerics** numerics_fine, + CGeometry* geometry_fine, + CSolver** solver_container_fine, + CConfig *config, + unsigned short iMesh, + unsigned short iRKLimit); + + void PreSmoothing(unsigned short RunTime_EqSystem, + CGeometry**** geometry, + CSolver***** solver_container, + CConfig **config_container, + CSolver* solver_fine, + CNumerics** numerics_fine, + CGeometry* geometry_fine, + CSolver** solver_container_fine, + CConfig *config, + unsigned short iMesh, + unsigned short iZone, + unsigned short iRKLimit); + /*! * \brief Compute the fine grid correction from the coarse solution. * \param[out] sol_fine - Pointer to the solution on the fine grid. diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp b/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp index e6f10ff2eaf7..afef135a2a93 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp @@ -577,7 +577,7 @@ class CFVMFlowSolverBase : public CSolver { } - /*--- Recompute the unsteady time step for the dual time strategy if the unsteady CFL is diferent from 0. + /*--- Recompute the unsteady time step for the dual time strategy if the unsteady CFL is different from 0. * This is only done once because in dual time the time step cannot be variable. ---*/ if (dual_time && (Iteration == config->GetRestart_Iter()) && (config->GetUnst_CFL() != 0.0) && (iMesh == MESH_0)) { diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 2d14194587b9..3c291b47f33a 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -592,7 +592,7 @@ void CFVMFlowSolverBase::ComputeUnderRelaxationFactor(const CConfig* confi template void CFVMFlowSolverBase::ImplicitEuler_Iteration(CGeometry *geometry, CSolver**, CConfig *config) { - + cout << "calling ImplicitEuler" << endl; PrepareImplicitIteration(geometry, nullptr, config); /*--- Solve or smooth the linear system. ---*/ @@ -605,7 +605,7 @@ void CFVMFlowSolverBase::ImplicitEuler_Iteration(CGeometry *geometry, CSol END_SU2_OMP_FOR auto iter = System.Solve(Jacobian, LinSysRes, LinSysSol, geometry, config); - + cout << "iter = " << iter << " " << System.GetResidual() << endl; BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { SetIterLinSolver(iter); SetResLinSolver(System.GetResidual()); diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 7c38e054d4a1..88eac5a1d2eb 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -4322,8 +4322,8 @@ class CSolver { } const su2double scale = 1 / geoCoarse.nodes->GetVolume(iPointCoarse); - for (auto iChildren = 0ul; iChildren < geoCoarse.nodes->GetnChildren_CV(iPointCoarse); ++iChildren) { - const auto iPointFine = geoCoarse.nodes->GetChildren_CV(iPointCoarse, iChildren); + for (auto iChild = 0ul; iChild < geoCoarse.nodes->GetnChildren_CV(iPointCoarse); ++iChild) { + const auto iPointFine = geoCoarse.nodes->GetChildren_CV(iPointCoarse, iChild); const su2double w = geoFine.nodes->GetVolume(iPointFine) * scale; for (auto iVar = 0ul; iVar < varsCoarse.cols(); ++iVar) { varsCoarse(iPointCoarse, iVar) += w * varsFine(iPointFine, iVar); diff --git a/SU2_CFD/src/integration/CFEM_DG_Integration.cpp b/SU2_CFD/src/integration/CFEM_DG_Integration.cpp index 7a52df8bc60f..5946900a52cb 100644 --- a/SU2_CFD/src/integration/CFEM_DG_Integration.cpp +++ b/SU2_CFD/src/integration/CFEM_DG_Integration.cpp @@ -38,7 +38,7 @@ void CFEM_DG_Integration::SingleGrid_Iteration(CGeometry ****geometry, unsigned short iZone, unsigned short iInst) { - unsigned short iMesh, iStep, iLimit = 1; + unsigned short iMesh, iStep; unsigned short SolContainer_Position = config[iZone]->GetContainerPosition(RunTime_EqSystem); unsigned short FinestMesh = config[iZone]->GetFinestMesh(); @@ -60,12 +60,8 @@ void CFEM_DG_Integration::SingleGrid_Iteration(CGeometry ****geometry, complicated algorithm must be used to facilitate time accurate local time stepping. Note that we are currently hard-coding the classical RK4 scheme. ---*/ - bool useADER = false; - switch (config[iZone]->GetKind_TimeIntScheme()) { - case RUNGE_KUTTA_EXPLICIT: iLimit = config[iZone]->GetnRKStep(); break; - case CLASSICAL_RK4_EXPLICIT: iLimit = 4; break; - case ADER_DG: iLimit = 1; useADER = true; break; - case EULER_EXPLICIT: case EULER_IMPLICIT: iLimit = 1; break; } + unsigned short iLimit = config[iZone]->GetnRKStep(); + /*--- In case an unsteady simulation is carried out, it is possible that a synchronization time step is specified. If so, set the boolean @@ -101,7 +97,7 @@ void CFEM_DG_Integration::SingleGrid_Iteration(CGeometry ****geometry, space and time integration are tightly coupled and cannot be treated segregatedly. Therefore a different function is called for ADER to carry out the space and time integration. ---*/ - if( useADER ) { + if( config[iZone]->GetKind_TimeIntScheme() == ADER_DG ) { solver_container[iZone][iInst][iMesh][SolContainer_Position]->ADER_SpaceTimeIntegration(geometry[iZone][iInst][iMesh], solver_container[iZone][iInst][iMesh], numerics_container[iZone][iInst][iMesh][SolContainer_Position], config[iZone], iMesh, RunTime_EqSystem); diff --git a/SU2_CFD/src/integration/CIntegration.cpp b/SU2_CFD/src/integration/CIntegration.cpp index 039fad933a4c..f7c030ab5069 100644 --- a/SU2_CFD/src/integration/CIntegration.cpp +++ b/SU2_CFD/src/integration/CIntegration.cpp @@ -202,7 +202,7 @@ void CIntegration::Space_Integration(CGeometry *geometry, void CIntegration::Time_Integration(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iRKStep, unsigned short RunTime_EqSystem) { - + cout << "time integration CIntegration.cpp" << endl; unsigned short MainSolver = config->GetContainerPosition(RunTime_EqSystem); switch (config->GetKind_TimeIntScheme()) { diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index f2a391869093..87cde992e670 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -139,7 +139,6 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, CConfig* config = config_container[iZone]; const unsigned short Solver_Position = config->GetContainerPosition(RunTime_EqSystem); - const bool classical_rk4 = (config->GetKind_TimeIntScheme() == CLASSICAL_RK4_EXPLICIT); const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); /*--- Shorter names to refer to fine grid entities. ---*/ @@ -151,71 +150,18 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, /*--- Number of RK steps. ---*/ - unsigned short iRKLimit = 1; - - switch (config->GetKind_TimeIntScheme()) { - case RUNGE_KUTTA_EXPLICIT: - iRKLimit = config->GetnRKStep(); - break; - case CLASSICAL_RK4_EXPLICIT: - iRKLimit = 4; - break; - case EULER_EXPLICIT: - case EULER_IMPLICIT: - iRKLimit = 1; - break; - } - - /*--- Do a presmoothing on the grid iMesh to be restricted to the grid iMesh+1 ---*/ - - for (unsigned short iPreSmooth = 0; iPreSmooth < config->GetMG_PreSmooth(iMesh); iPreSmooth++) { - - /*--- Time and space integration ---*/ - - for (unsigned short iRKStep = 0; iRKStep < iRKLimit; iRKStep++) { - - /*--- Send-Receive boundary conditions, and preprocessing ---*/ - - solver_fine->Preprocessing(geometry_fine, solver_container_fine, config, iMesh, iRKStep, RunTime_EqSystem, false); - - - if (iRKStep == 0) { - - /*--- Set the old solution ---*/ - - solver_fine->Set_OldSolution(); - - if (classical_rk4) solver_fine->Set_NewSolution(); - - /*--- Compute time step, max eigenvalue, and integration scheme (steady and unsteady problems) ---*/ - - solver_fine->SetTime_Step(geometry_fine, solver_container_fine, config, iMesh, config->GetTimeIter()); - - /*--- Restrict the solution and gradient for the adjoint problem ---*/ - - Adjoint_Setup(geometry, solver_container, config_container, RunTime_EqSystem, config->GetTimeIter(), iZone); - - } + unsigned short iRKLimit = config->GetnRKStep(); - /*--- Space integration ---*/ - - Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); + // standard multigrid: pre-smoothing + // start with solution on fine grid h + // apply nonlinear smoother (e.g. Gauss-Seidel) to system to damp high-frequency errors. + PreSmoothing(RunTime_EqSystem, geometry, solver_container, config_container, solver_fine, numerics_fine, geometry_fine, solver_container_fine, config, iMesh, iZone,iRKLimit); - /*--- Time integration, update solution using the old solution plus the solution increment ---*/ - - Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); - - /*--- Send-Receive boundary conditions, and postprocessing ---*/ - - solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); - - } - - } /*--- Compute Forcing Term $P_(k+1) = I^(k+1)_k(P_k+F_k(u_k))-F_(k+1)(I^(k+1)_k u_k)$ and update solution for multigrid ---*/ if ( iMesh < config->GetnMGLevels() ) { + cout << "imesh =" << iMesh << " " << config->GetnMGLevels() << endl; /*--- Shorter names to refer to coarse grid entities. ---*/ @@ -239,7 +185,7 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, SetResidual_Term(geometry_fine, solver_fine); /*--- Compute $r_(k+1) = F_(k+1)(I^(k+1)_k u_k)$ ---*/ - + // restrict the fine-grid solution to the coarser grid. This must be FAS multigrid, because else we restrict the residual. SetRestricted_Solution(RunTime_EqSystem, solver_fine, solver_coarse, geometry_fine, geometry_coarse, config); solver_coarse->Preprocessing(geometry_coarse, solver_container_coarse, config, iMesh+1, NO_RK_ITER, RunTime_EqSystem, false); @@ -271,40 +217,113 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, /*--- 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); - SmoothProlongated_Correction(RunTime_EqSystem, solver_fine, geometry_fine, config->GetMG_CorrecSmooth(iMesh), 1.25, config); - SetProlongated_Correction(solver_fine, geometry_fine, config, iMesh); /*--- Solution post-smoothing in the prolongated grid. ---*/ + PostSmoothing(RunTime_EqSystem, solver_fine, numerics_fine, geometry_fine, solver_container_fine, config, iMesh, iRKLimit); - for (unsigned short iPostSmooth = 0; iPostSmooth < config->GetMG_PostSmooth(iMesh); iPostSmooth++) { + } - for (unsigned short iRKStep = 0; iRKStep < iRKLimit; iRKStep++) { +} - solver_fine->Preprocessing(geometry_fine, solver_container_fine, config, iMesh, iRKStep, RunTime_EqSystem, false); - if (iRKStep == 0) { - solver_fine->Set_OldSolution(); +void CMultiGridIntegration::PreSmoothing(unsigned short RunTime_EqSystem, +CGeometry**** geometry, +CSolver***** solver_container, +CConfig **config_container, +CSolver* solver_fine, +CNumerics** numerics_fine, +CGeometry* geometry_fine, +CSolver** solver_container_fine, +CConfig *config, +unsigned short iMesh, +unsigned short iZone, +unsigned short iRKLimit) { + const bool classical_rk4 = (config->GetKind_TimeIntScheme() == CLASSICAL_RK4_EXPLICIT); + const unsigned short nPreSmooth = config->GetMG_PreSmooth(iMesh); + const unsigned long timeIter = config->GetTimeIter(); - if (classical_rk4) solver_fine->Set_NewSolution(); + /*--- Do a presmoothing on the grid iMesh to be restricted to the grid iMesh+1 ---*/ + for (unsigned short iPreSmooth = 0; iPreSmooth < nPreSmooth; iPreSmooth++) { - solver_fine->SetTime_Step(geometry_fine, solver_container_fine, config, iMesh, config->GetTimeIter()); - } + /*--- Time and space integration ---*/ + for (unsigned short iRKStep = 0; iRKStep < iRKLimit; iRKStep++) { + + /*--- Send-Receive boundary conditions, and preprocessing ---*/ + solver_fine->Preprocessing(geometry_fine, solver_container_fine, config, iMesh, iRKStep, RunTime_EqSystem, false); - Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); + if (iRKStep == 0) { - Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); + /*--- Set the old solution ---*/ + solver_fine->Set_OldSolution(); - solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); + if (classical_rk4) solver_fine->Set_NewSolution(); + // nijso asks: only call when classical_rk4? + // this copies solution to old solution, which we already did above. + //solver_fine->Set_OldSolution(); + + /*--- Compute time step, max eigenvalue, and integration scheme (steady and unsteady problems) ---*/ + solver_fine->SetTime_Step(geometry_fine, solver_container_fine, config, iMesh, timeIter); + + /*--- Restrict the solution and gradient for the adjoint problem ---*/ + // nijso asks: why do we call this here but not in the post-smoothing? + Adjoint_Setup(geometry, solver_container, config_container, RunTime_EqSystem, timeIter, iZone); } + cout << "pre-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; + /*--- Space integration ---*/ + Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); + + /*--- Time integration, update solution using the old solution plus the solution increment ---*/ + Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); + + /*--- Send-Receive boundary conditions, and postprocessing ---*/ + solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); + } } +} + +void CMultiGridIntegration::PostSmoothing(unsigned short RunTime_EqSystem, CSolver* solver_fine,CNumerics** numerics_fine, CGeometry* geometry_fine, CSolver** solver_container_fine, CConfig *config, unsigned short iMesh,unsigned short iRKLimit) +{ + const bool classical_rk4 = (config->GetKind_TimeIntScheme() == CLASSICAL_RK4_EXPLICIT); + const unsigned short nPostSmooth = config->GetMG_PostSmooth(iMesh); + const unsigned long timeIter = config->GetTimeIter(); + + /*--- Do a postsmoothing on the grid iMesh after prolongation from the grid iMesh+1 ---*/ + for (unsigned short iPostSmooth = 0; iPostSmooth < nPostSmooth; iPostSmooth++) { + + for (unsigned short iRKStep = 0; iRKStep < iRKLimit; iRKStep++) { + + solver_fine->Preprocessing(geometry_fine, solver_container_fine, config, iMesh, iRKStep, RunTime_EqSystem, false); + + if (iRKStep == 0) { + + /*--- Set the old solution ---*/ + solver_fine->Set_OldSolution(); + + if (classical_rk4) solver_fine->Set_NewSolution(); + solver_fine->SetTime_Step(geometry_fine, solver_container_fine, config, iMesh, timeIter); + + } + + /*--- Space integration ---*/ + Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); + cout << "post-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; + /*--- Time integration, update solution using the old solution plus the solution increment ---*/ + Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); + + /*--- Send-Receive boundary conditions, and postprocessing ---*/ + solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); + + } + } } + void CMultiGridIntegration::GetProlongated_Correction(unsigned short RunTime_EqSystem, CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, CGeometry *geo_coarse, CConfig *config) { unsigned long Point_Fine, Point_Coarse, iVertex; @@ -353,8 +372,8 @@ void CMultiGridIntegration::GetProlongated_Correction(unsigned short RunTime_EqS Point_Coarse = geo_coarse->vertex[iMarker][iVertex]->GetNode(); - /*--- For dirichlet boundary condtions, set the correction to zero. - Note that Solution_Old stores the correction not the actual value ---*/ + /*--- For dirichlet boundary conditions, set the correction to zero. + Note that Solution_Old stores the correction, not the actual value ---*/ su2double zero[3] = {0.0}; sol_coarse->GetNodes()->SetVelocity_Old(Point_Coarse, zero); @@ -590,6 +609,7 @@ void CMultiGridIntegration::SetRestricted_Solution(unsigned short RunTime_EqSyst sol_coarse->GetNodes()->SetVelSolutionVector(Point_Coarse, Grid_Vel); } else { + // nijso asks: why only for the velocity? /*--- For stationary no-slip walls, set the velocity to zero. ---*/ su2double zero[3] = {0.0}; sol_coarse->GetNodes()->SetVelSolutionVector(Point_Coarse, zero); diff --git a/SU2_CFD/src/solvers/CIncNSSolver.cpp b/SU2_CFD/src/solvers/CIncNSSolver.cpp index 30f0efd46d98..4de12a4ad3cd 100644 --- a/SU2_CFD/src/solvers/CIncNSSolver.cpp +++ b/SU2_CFD/src/solvers/CIncNSSolver.cpp @@ -649,6 +649,7 @@ void CIncNSSolver::SetTau_Wall_WF(CGeometry *geometry, CSolver **solver_containe const auto iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); const auto Point_Normal = geometry->vertex[iMarker][iVertex]->GetNormal_Neighbor(); + /*--- On the finest mesh compute also on halo nodes to avoid communication of tau wall. ---*/ if ((!geometry->nodes->GetDomain(iPoint)) && !(MGLevel==MESH_0)) continue; diff --git a/SU2_CFD/src/solvers/CNSSolver.cpp b/SU2_CFD/src/solvers/CNSSolver.cpp index c2206813fe21..90370a74fef9 100644 --- a/SU2_CFD/src/solvers/CNSSolver.cpp +++ b/SU2_CFD/src/solvers/CNSSolver.cpp @@ -823,10 +823,8 @@ void CNSSolver::SetTau_Wall_WF(CGeometry *geometry, CSolver **solver_container, const auto iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); const auto Point_Normal = geometry->vertex[iMarker][iVertex]->GetNormal_Neighbor(); - /*--- Check if the node belongs to the domain (i.e, not a halo node) - * and the neighbor is not part of the physical boundary ---*/ - - if (!geometry->nodes->GetDomain(iPoint)) continue; + /*--- On the finest mesh compute also on halo nodes to avoid communication of tau wall. ---*/ + if ((!geometry->nodes->GetDomain(iPoint)) && !(MGLevel==MESH_0)) continue; /*--- Get coordinates of the current vertex and nearest normal point ---*/ diff --git a/TestCases/wallfunctions/flatplate/compressible_SA/turb_SA_flatplate.cfg b/TestCases/wallfunctions/flatplate/compressible_SA/turb_SA_flatplate.cfg index e44ebd97bdb4..20bfd1347bcf 100644 --- a/TestCases/wallfunctions/flatplate/compressible_SA/turb_SA_flatplate.cfg +++ b/TestCases/wallfunctions/flatplate/compressible_SA/turb_SA_flatplate.cfg @@ -100,5 +100,5 @@ MARKER_PLOTTING= ( wall ) MARKER_MONITORING= ( wall ) % OUTPUT_FILES= RESTART, PARAVIEW_MULTIBLOCK -VOLUME_OUTPUT= RESIDUAL, PRIMITIVE, RESIDUAL +VOLUME_OUTPUT= RESIDUAL, PRIMITIVE OUTPUT_WRT_FREQ= 1000 diff --git a/TestCases/wallfunctions/flatplate/compressible_SST/turb_SST_flatplate.cfg b/TestCases/wallfunctions/flatplate/compressible_SST/turb_SST_flatplate.cfg index b2f9c515502f..122ce950cd01 100644 --- a/TestCases/wallfunctions/flatplate/compressible_SST/turb_SST_flatplate.cfg +++ b/TestCases/wallfunctions/flatplate/compressible_SST/turb_SST_flatplate.cfg @@ -100,5 +100,5 @@ MARKER_PLOTTING= ( wall ) MARKER_MONITORING= ( wall ) % OUTPUT_FILES= RESTART, PARAVIEW_MULTIBLOCK -VOLUME_OUTPUT= RESIDUAL, PRIMITIVE, RESIDUAL +VOLUME_OUTPUT= RESIDUAL, PRIMITIVE OUTPUT_WRT_FREQ= 1000 From d55bd7277116dd6d030710f99694114756509e81 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 15 Nov 2025 11:42:50 +0100 Subject: [PATCH 05/41] update 3D agglomeration --- Common/src/geometry/CMultiGridGeometry.cpp | 31 ++++++++++++++++--- .../include/solvers/CFVMFlowSolverBase.inl | 1 - SU2_CFD/src/integration/CIntegration.cpp | 1 - 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index e1d697116975..5579cb898d85 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -157,7 +157,20 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); - /*--- Note: We could also allow 2 markers that are of the same type, except when they are symmetry markers. ---*/ + /*--- Note: in 2D, We could also allow to agglomerate markers that are of the same type, except when they are symmetry markers. ---*/ + // if (nDim==2){ + // if ((config->GetMarker_All_KindBC(copy_marker[0]) == config->GetMarker_All_KindBC(copy_marker[1])) && + // (config->GetMarker_All_KindBC(copy_marker[0]) != SYMMETRY_PLANE)) { + // agglomerate_seed = true; + // } + // } + + /* in 3D, this is the edge of a surface, so we can always try to agglomerate if the other marker is of the same type.*/ + if (nDim==3){ + agglomerate_seed = true; + } + + /* --- Euler walls can also not be agglomerated when the point has 2 markers ---*/ //if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || @@ -194,8 +207,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- We set the value of the child ---*/ nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; - /*--- In 2D, we only agglomerate 2 nodes. ---*/ - if (nDim==2) break; + /*--- In 2D, we only agglomerate 2 nodes if the nodes are on the line edge. ---*/ + if ((nDim==2) && (counter==1)) break; + /*--- In 3D, we only agglomerate 2 nodes if the nodes are on the surface edge. ---*/ + if ((nDim==3) && (counter==2)) break; } } @@ -631,6 +646,7 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector Valley: only if of the same type---*/ if (counter == 1) { /*--- We agglomerate if there is only one marker and it is the same marker as the seed marker ---*/ + // So this is the case when in 2D we are on an edge, and in 3D we are in the interior of a surface. // note that this should be the same marker id, not just the same marker type. // also note that the seed point can have 2 markers, one of them may be a send-receive. if ((marker_seed.size()==1) && (copy_marker[0] == marker_seed[0])) agglomerate_CV = true; @@ -662,8 +678,13 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector Ridge: only if of the same type (same marker ID) ---*/ if (counter == 2) { - // the child has 2 markers, it cannot be agglomerated with the seed. - agglomerate_CV = false; + // in 2D, if the child has 2 markers it is a corner, it cannot be agglomerated with the seed. + if (nDim==2) agglomerate_CV = false; + else if (nDim==3) { + // in 3D, this is an edge of a surface, so we can agglomerate if the other marker is of the same type. + agglomerate_CV = true; + } + // check if the seed also has 2 markers //if (marker_seed.size() == 2) { diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 3c291b47f33a..c8e822c2a350 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -592,7 +592,6 @@ void CFVMFlowSolverBase::ComputeUnderRelaxationFactor(const CConfig* confi template void CFVMFlowSolverBase::ImplicitEuler_Iteration(CGeometry *geometry, CSolver**, CConfig *config) { - cout << "calling ImplicitEuler" << endl; PrepareImplicitIteration(geometry, nullptr, config); /*--- Solve or smooth the linear system. ---*/ diff --git a/SU2_CFD/src/integration/CIntegration.cpp b/SU2_CFD/src/integration/CIntegration.cpp index f7c030ab5069..65322ff04e96 100644 --- a/SU2_CFD/src/integration/CIntegration.cpp +++ b/SU2_CFD/src/integration/CIntegration.cpp @@ -202,7 +202,6 @@ void CIntegration::Space_Integration(CGeometry *geometry, void CIntegration::Time_Integration(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iRKStep, unsigned short RunTime_EqSystem) { - cout << "time integration CIntegration.cpp" << endl; unsigned short MainSolver = config->GetContainerPosition(RunTime_EqSystem); switch (config->GetKind_TimeIntScheme()) { From 768aa92a9d07bf0749779617ee94cd3f5366f873 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 15 Nov 2025 12:08:14 +0100 Subject: [PATCH 06/41] update 3D agglomeration --- SU2_CFD/include/solvers/CFVMFlowSolverBase.inl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index c52b7fb80af5..8dc0e4b63931 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -593,6 +593,7 @@ void CFVMFlowSolverBase::ComputeUnderRelaxationFactor(const CConfig* confi template void CFVMFlowSolverBase::ImplicitEuler_Iteration(CGeometry *geometry, CSolver**, CConfig *config) { + PrepareImplicitIteration(geometry, nullptr, config); /*--- Solve or smooth the linear system. ---*/ @@ -605,7 +606,7 @@ void CFVMFlowSolverBase::ImplicitEuler_Iteration(CGeometry *geometry, CSol END_SU2_OMP_FOR auto iter = System.Solve(Jacobian, LinSysRes, LinSysSol, geometry, config); - cout << "iter = " << iter << " " << System.GetResidual() << endl; + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { SetIterLinSolver(iter); SetResLinSolver(System.GetResidual()); From 35dbdae423073eda8c03b0d8a81ce6d852532908 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 15 Nov 2025 12:11:18 +0100 Subject: [PATCH 07/41] update opdi --- externals/opdi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/opdi b/externals/opdi index a5e2ac47035b..294807b0111c 160000 --- a/externals/opdi +++ b/externals/opdi @@ -1 +1 @@ -Subproject commit a5e2ac47035b6b3663f60d5f80b7a9fe62084867 +Subproject commit 294807b0111ce241cda97db62f80cdd5012d9381 From 767bd2aebddd9448bc4ec3110f6ace3f588a097a Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 15 Nov 2025 22:18:12 +0100 Subject: [PATCH 08/41] update with commented cout --- Common/src/geometry/CMultiGridGeometry.cpp | 312 +++++++++--------- .../src/integration/CMultiGridIntegration.cpp | 6 +- 2 files changed, 165 insertions(+), 153 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 5579cb898d85..7144380f302a 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -73,18 +73,17 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un unsigned long Index_CoarseCV = 0; /*--- STEP 1: The first step is the boundary agglomeration. ---*/ - cout << "loop over Markers" << endl; + //cout << "loop over Markers" << endl; for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { - cout << "*** loop:marker = " << iMarker << " " << config->GetMarker_All_TagBound(iMarker) << endl; + //cout << "*** loop:marker = " << iMarker << " " << config->GetMarker_All_TagBound(iMarker) << endl; for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { - const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - cout << "*** seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " << fine_grid->nodes->GetCoord(iPoint, 1) << endl; - cout << "*** seed point is on the marker: " << fine_grid->nodes->GetVertex(iPoint, iMarker) - << " , agglomerated=" <nodes->GetAgglomerate(iPoint) << " , domain = " <nodes->GetDomain(iPoint) - << " , geo =" << GeometricalCheck(iPoint, fine_grid, config) - <nodes->GetCoord(iPoint, 1) << endl; + //cout << "*** seed point is on the marker: " << fine_grid->nodes->GetVertex(iPoint, iMarker) + //<< " , agglomerated=" <nodes->GetAgglomerate(iPoint) << " , domain = " <nodes->GetDomain(iPoint) + //<< " , geo =" << GeometricalCheck(iPoint, fine_grid, config) + //<SetChildren_CV(Index_CoarseCV, 0, iPoint); - bool agglomerate_seed = false; + bool agglomerate_seed = true; auto counter = 0; unsigned short copy_marker[3] = {}; marker_seed.push_back(iMarker); @@ -114,9 +113,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker(); jMarker++) { const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); //fine_grid->GetMarker_Tag(jMarker); - cout << " marker = " << jMarker<<" " << Marker_Tag << endl; + //cout << " marker = " << jMarker<<" " << Marker_Tag << endl; if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { - cout << " point is on marker" << endl; + //cout << " point is on marker" << endl; copy_marker[counter] = jMarker; counter++; @@ -127,7 +126,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } - cout << " counter for this seed point = " << counter << endl; + //cout << " counter for this seed point = " << counter << endl; /*--- To agglomerate a vertex it must have only one physical bc!! This can be improved. If there is only one marker, it is a good @@ -142,8 +141,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un agglomerate_seed = true; /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ - //if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) - // agglomerate_seed = false; + if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; } /*--- If there are two markers, we will agglomerate if any of the @@ -157,29 +155,15 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); - /*--- Note: in 2D, We could also allow to agglomerate markers that are of the same type, except when they are symmetry markers. ---*/ - // if (nDim==2){ - // if ((config->GetMarker_All_KindBC(copy_marker[0]) == config->GetMarker_All_KindBC(copy_marker[1])) && - // (config->GetMarker_All_KindBC(copy_marker[0]) != SYMMETRY_PLANE)) { - // agglomerate_seed = true; - // } - // } - - /* in 3D, this is the edge of a surface, so we can always try to agglomerate if the other marker is of the same type.*/ - if (nDim==3){ - agglomerate_seed = true; - } - - - /* --- Euler walls can also not be agglomerated when the point has 2 markers ---*/ - //if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || - // (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { - // agglomerate_seed = true; - //} + if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || + (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { + agglomerate_seed = false; + } } - /*--- If there are more than 2 markers (corner node), the agglomeration will be discarded ---*/ + /*--- If there are more than 2 markers, the aglomeration will be discarded ---*/ + if (counter > 2) agglomerate_seed = false; @@ -187,34 +171,33 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- If the seed (parent) can be agglomerated, we try to agglomerate connected childs to the parent ---*/ /*--- Note that in 2D we allow a maximum of 4 nodes to be agglomerated ---*/ if (agglomerate_seed) { - cout << " seed can be agglomerated to more points." << endl; + //cout << " seed can be agglomerated to more points." << endl; /*--- Now we do a sweep over all the nodes that surround the seed point ---*/ + for (auto CVPoint : fine_grid->nodes->GetPoints(iPoint)) { - cout << " checking child CVPoint = " - << CVPoint - << ", coord = " - << fine_grid->nodes->GetCoord(CVPoint, 0) - << " " - << fine_grid->nodes->GetCoord(CVPoint, 1) - << endl; + // cout << " checking child CVPoint = " + // << CVPoint + // << ", coord = " + // << fine_grid->nodes->GetCoord(CVPoint, 0) + // << " " + // << fine_grid->nodes->GetCoord(CVPoint, 1) + // << endl; /*--- The new point can be agglomerated ---*/ if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { - cout << " agglomerate " << CVPoint << " with seed point "<< iPoint << endl; + //cout << " agglomerate " << CVPoint << " with seed point "<< iPoint << endl; /*--- We set the value of the parent ---*/ + fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); /*--- We set the value of the child ---*/ + nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; - /*--- In 2D, we only agglomerate 2 nodes if the nodes are on the line edge. ---*/ - if ((nDim==2) && (counter==1)) break; - /*--- In 3D, we only agglomerate 2 nodes if the nodes are on the surface edge. ---*/ - if ((nDim==3) && (counter==2)) break; - } } + //Suitable_Indirect_Neighbors.clear(); /*--- Only take into account indirect neighbors for 3D faces, not 2D. ---*/ if (nDim == 3) { @@ -225,15 +208,15 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Now we do a sweep over all the indirect nodes that can be added ---*/ for (auto CVPoint : Suitable_Indirect_Neighbors) { - cout << " Boundary: checking indirect neighbors " << CVPoint - << ", coord = " - << fine_grid->nodes->GetCoord(CVPoint, 0) - << " " - << fine_grid->nodes->GetCoord(CVPoint, 1) - << endl; + // cout << " Boundary: checking indirect neighbors " << CVPoint + // << ", coord = " + // << fine_grid->nodes->GetCoord(CVPoint, 0) + // << " " + // << fine_grid->nodes->GetCoord(CVPoint, 1) + // << endl; /*--- The new point can be agglomerated ---*/ if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { - cout << " Boundary: indirect neighbor " << CVPoint << " can be agglomerated." << endl; + //cout << " Boundary: indirect neighbor " << CVPoint << " can be agglomerated." << endl; /*--- We set the value of the parent ---*/ fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); @@ -263,13 +246,14 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Do not agglomerate any leftover node with more than one physical boundary condition, i.e. make one coarse CV with a single child. ---*/ + for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - cout << "point " << iPoint << ", parent = " << fine_grid->nodes->GetParent_CV(iPoint) - << " " << fine_grid->nodes->GetAgglomerate(iPoint) << endl; + //cout << "point " << iPoint << ", parent = " << fine_grid->nodes->GetParent_CV(iPoint) + // << " " << fine_grid->nodes->GetAgglomerate(iPoint) << endl; if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint))) { - cout << " Boundary:mark left-over nodes " << endl; + //cout << " Boundary:mark left-over nodes " << endl; fine_grid->nodes->SetParent_CV(iPoint, Index_CoarseCV); nodes->SetChildren_CV(Index_CoarseCV, 0, iPoint); nodes->SetnChildren_CV(Index_CoarseCV, 1); @@ -280,17 +264,17 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /* --- Note that we can check index_coarse_cv for nr of children ---*/ - for (auto icoarse_cv = 0u; icoarse_cv < Index_CoarseCV; icoarse_cv++) { - cout << "coarse node " << icoarse_cv << ", children = " << nodes->GetnChildren_CV(icoarse_cv) << " " << endl; - } + // for (auto icoarse_cv = 0u; icoarse_cv < Index_CoarseCV; icoarse_cv++) { + // cout << "coarse node " << icoarse_cv << ", children = " << nodes->GetnChildren_CV(icoarse_cv) << " " << endl; + // } /*--- Check all agglomerations and see if there is a 1-node agglomeration ---*/ - for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { - for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { - const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - cout << "ipoint = " << iPoint << " , " << nodes->GetnChildren_CV(iPoint) << " " << fine_grid->nodes->GetParent_CV(iPoint) << endl; - } - } + // for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { + // for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { + // const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); + // cout << "ipoint = " << iPoint << " , " << nodes->GetnChildren_CV(iPoint) << " " << fine_grid->nodes->GetParent_CV(iPoint) << endl; + // } + // } /*--- Update the queue with the results from the boundary agglomeration ---*/ @@ -311,7 +295,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } /*--- STEP 2: Agglomerate the domain points. ---*/ - cout << "*********** STEP 2 ***" << endl; + //cout << "*********** STEP 2 ***" << endl; auto iteration = 0ul; while (!MGQueue_InnerCV.EmptyQueue() && (iteration < fine_grid->GetnPoint())) { const auto iPoint = MGQueue_InnerCV.NextCV(); @@ -323,7 +307,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint)) && (GeometricalCheck(iPoint, fine_grid, config))) { unsigned short nChildren = 1; - cout << "***** internal seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " << fine_grid->nodes->GetCoord(iPoint, 1) << endl; + //cout << "***** internal seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " << fine_grid->nodes->GetCoord(iPoint, 1) << endl; /*--- We set an index for the parent control volume ---*/ @@ -346,7 +330,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if ((!fine_grid->nodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint)) && (GeometricalCheck(CVPoint, fine_grid, config))) { /*--- We set the value of the parent ---*/ - cout << "agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; + //cout << "agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); /*--- We set the value of the child ---*/ @@ -363,31 +347,36 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } /*--- Identify the indirect neighbors ---*/ + Suitable_Indirect_Neighbors.clear(); if (fine_grid->nodes->GetAgglomerate_Indirect(iPoint)) SetSuitableNeighbors(Suitable_Indirect_Neighbors, iPoint, Index_CoarseCV, fine_grid); /*--- Now we do a sweep over all the indirect nodes that can be added ---*/ + for (auto CVPoint : Suitable_Indirect_Neighbors) { // if we have reached the maximum, get out. if (nChildren==maxAgglomSize) break; /*--- The new point can be agglomerated ---*/ if ((!fine_grid->nodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint))) { - cout << "indirect agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; + //cout << "indirect agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; /*--- We set the value of the parent ---*/ fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); /*--- We set the indirect agglomeration information ---*/ + if (fine_grid->nodes->GetAgglomerate_Indirect(CVPoint)) nodes->SetAgglomerate_Indirect(Index_CoarseCV, true); /*--- We set the value of the child ---*/ + nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; /*--- Update the queue with the new control volume (remove the CV and increase the priority of the neighbors) ---*/ + MGQueue_InnerCV.Update(CVPoint, fine_grid); } } @@ -431,13 +420,61 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un cout << "isolated point " << iCoarsePoint << endl; const auto iCoarsePoint_Complete = nodes->GetPoint(iCoarsePoint, 0); - /*--- Add the children to the connected control volume (and modify its parent indexing). - Identify the child CV from the finest grid and add it to the correct control volume. - Set the parent CV of iFinePoint. Instead of using the original one - (iCoarsePoint), use the new one (iCoarsePoint_Complete) ---*/ - - auto nChildren = nodes->GetnChildren_CV(iCoarsePoint_Complete); + /*--- Check if merging would exceed the maximum agglomeration size ---*/ + auto nChildren_Target = nodes->GetnChildren_CV(iCoarsePoint_Complete); + auto nChildren_Isolated = nodes->GetnChildren_CV(iCoarsePoint); + auto nChildren_Total = nChildren_Target + nChildren_Isolated; + + /*--- If the total would exceed maxAgglomSize, try to redistribute children to neighbors ---*/ + if (nChildren_Total > maxAgglomSize) { + cout << " Merging isolated point " << iCoarsePoint + << " to point " << iCoarsePoint_Complete + << " would exceed limit (" << nChildren_Total << " > " << maxAgglomSize << ")" << endl; + + /*--- Find neighbors of the target coarse point that have room ---*/ + unsigned short nChildrenToRedistribute = nChildren_Total - maxAgglomSize; + + for (auto jCoarsePoint : nodes->GetPoints(iCoarsePoint_Complete)) { + if (nChildrenToRedistribute == 0) break; + + auto nChildren_Neighbor = nodes->GetnChildren_CV(jCoarsePoint); + if (nChildren_Neighbor < maxAgglomSize) { + unsigned short nCanTransfer = min(nChildrenToRedistribute, + static_cast(maxAgglomSize - nChildren_Neighbor)); + + cout << " Redistributing " << nCanTransfer << " children from point " + << iCoarsePoint_Complete << " to neighbor " << jCoarsePoint << endl; + + /*--- Transfer children from target to neighbor ---*/ + for (unsigned short iTransfer = 0; iTransfer < nCanTransfer; iTransfer++) { + /*--- Take from the end of the target's children list ---*/ + auto nChildren_Current = nodes->GetnChildren_CV(iCoarsePoint_Complete); + if (nChildren_Current > 0) { + auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint_Complete, nChildren_Current - 1); + + /*--- Add to neighbor ---*/ + auto nChildren_Neighbor_Current = nodes->GetnChildren_CV(jCoarsePoint); + nodes->SetChildren_CV(jCoarsePoint, nChildren_Neighbor_Current, iFinePoint); + nodes->SetnChildren_CV(jCoarsePoint, nChildren_Neighbor_Current + 1); + + /*--- Update parent ---*/ + fine_grid->nodes->SetParent_CV(iFinePoint, jCoarsePoint); + + /*--- Remove from target (by reducing count) ---*/ + nodes->SetnChildren_CV(iCoarsePoint_Complete, nChildren_Current - 1); + + nChildrenToRedistribute--; + } + } + } + } + + /*--- Update the target's child count after redistribution ---*/ + nChildren_Target = nodes->GetnChildren_CV(iCoarsePoint_Complete); + } + /*--- Add the isolated point's children to the target control volume ---*/ + auto nChildren = nChildren_Target; for (auto iChildren = 0u; iChildren < nodes->GetnChildren_CV(iCoarsePoint); iChildren++) { const auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint, iChildren); nodes->SetChildren_CV(iCoarsePoint_Complete, nChildren, iFinePoint); @@ -446,9 +483,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } /*--- Update the number of children control volumes ---*/ - nodes->SetnChildren_CV(iCoarsePoint_Complete, nChildren); nodes->SetnChildren_CV(iCoarsePoint, 0); + + cout << " Final: point " << iCoarsePoint_Complete + << " has " << nChildren << " children" << endl; } } @@ -562,17 +601,18 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if (iMesh != MESH_0) { //const su2double factor = 1.5; //nijso: too high - const su2double factor = 1.0; + const su2double factor = 2.0; const su2double Coeff = pow(su2double(Global_nPointFine) / Global_nPointCoarse, 1.0 / nDim); const su2double CFL = factor * config->GetCFL(iMesh - 1) / Coeff; config->SetCFL(iMesh, CFL); } const su2double ratio = su2double(Global_nPointFine) / su2double(Global_nPointCoarse); + cout << "********** ratio = " << ratio << endl; //if (((nDim == 2) && (ratio < 2.5)) || ((nDim == 3) && (ratio < 2.5))) { // nijso: too high for very small test meshes. - if (((nDim == 2) && (ratio < 2.0)) || ((nDim == 3) && (ratio < 2.0))) { + if (((nDim == 2) && (ratio < 3.0)) || ((nDim == 3) && (ratio < 3.0))) { config->SetMGLevels(iMesh - 1); } else if (rank == MASTER_NODE) { PrintingToolbox::CTablePrinter MGTable(&std::cout); @@ -595,19 +635,19 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } // print out all point connections - for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) { - cout << iPoint << ", parent=" << fine_grid->nodes->GetParent_CV(iPoint) << endl; - } + // for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) { + // cout << iPoint << ", parent=" << fine_grid->nodes->GetParent_CV(iPoint) << endl; + // } - // loop over coarse points - for (auto iCoarsePoint = 0ul; iCoarsePoint < Index_CoarseCV; iCoarsePoint++) { - cout << iCoarsePoint << endl; - // print all fine-point children of the coarse point - for (auto iChildren = 0u; iChildren < nodes->GetnChildren_CV(iCoarsePoint); iChildren++) { - //const auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint, iChildren); - cout << " " <GetChildren_CV(iCoarsePoint, iChildren) << endl; - } - } + // // loop over coarse points + // for (auto iCoarsePoint = 0ul; iCoarsePoint < Index_CoarseCV; iCoarsePoint++) { + // cout << iCoarsePoint << endl; + // // print all fine-point children of the coarse point + // for (auto iChildren = 0u; iChildren < nodes->GetnChildren_CV(iCoarsePoint); iChildren++) { + // //const auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint, iChildren); + // cout << " " <GetChildren_CV(iCoarsePoint, iChildren) << endl; + // } + // } edgeColorGroupSize = config->GetEdgeColoringGroupSize(); @@ -646,79 +686,51 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector Valley: only if of the same type---*/ if (counter == 1) { /*--- We agglomerate if there is only one marker and it is the same marker as the seed marker ---*/ - // So this is the case when in 2D we are on an edge, and in 3D we are in the interior of a surface. - // note that this should be the same marker id, not just the same marker type. - // also note that the seed point can have 2 markers, one of them may be a send-receive. - if ((marker_seed.size()==1) && (copy_marker[0] == marker_seed[0])) agglomerate_CV = true; - // if ((marker_seed.size() == 2) && (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE)) { - // if (copy_marker[0] == marker_seed[1]) { - // agglomerate_CV = true; - // } - // } - // if ((marker_seed.size() == 2) && (config->GetMarker_All_KindBC(marker_seed[1]) != SEND_RECEIVE)) { - // if (copy_marker[0] == marker_seed[0]) { - // agglomerate_CV = true; - // } - // } + // note that this should be the same marker id, not just the same marker type + if (copy_marker[0] == marker_seed[0]) agglomerate_CV = true; /*--- If there is only one marker, but the marker is the SEND_RECEIVE ---*/ - //if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - // agglomerate_CV = true; - //} - - //if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || - // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { - // if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - // agglomerate_CV = false; - // } - //} - } - - /*--- If there are two markers in the vertex that is going to be agglomerated ---*/ - /*--- Ridge -> Ridge: only if of the same type (same marker ID) ---*/ - if (counter == 2) { - // in 2D, if the child has 2 markers it is a corner, it cannot be agglomerated with the seed. - if (nDim==2) agglomerate_CV = false; - else if (nDim==3) { - // in 3D, this is an edge of a surface, so we can agglomerate if the other marker is of the same type. + if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { agglomerate_CV = true; } + if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || + (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { + if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { + agglomerate_CV = false; + } + } + } - // check if the seed also has 2 markers - //if (marker_seed.size() == 2) { - // // now check if the seed is on the same 2 marker ID's (note that if we allow that they are of the same - // // marker type, we need to check that the alignement of the markers is correct as well. better not go there.) - // if ( ((marker_seed[0] = copy_marker[0]) && (marker_seed[1] = copy_marker[1])) || - // ((marker_seed[0] = copy_marker[1]) && (marker_seed[1] = copy_marker[0])) ) { - // agglomerate_CV = true; - // } - //} + /*--- If there are two markers in the vertex that is going to be aglomerated ---*/ + + if (counter == 2) { /*--- First we verify that the seed is a physical boundary ---*/ - // if (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE) { - // /*--- Then we check that one of the markers is equal to the seed marker, and the other is send/receive ---*/ + if (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE) { + /*--- Then we check that one of the markers is equal to the seed marker, and the other is send/receive ---*/ - // if (((copy_marker[0] == marker_seed[0]) && (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || - // ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed[0]))) { - //agglomerate_CV = false; - // agglomerate_CV = true; - // } - // } + if (((copy_marker[0] == marker_seed[0]) && (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || + ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed[0]))) { + agglomerate_CV = true; + } + } } } - /*--- If the element belongs to the domain, it is never agglomerated with the boundary. ---*/ - /*--- Any -> Interior : disallowed ---*/ + /*--- If the element belongs to the domain, it is always agglomerated. ---*/ else { - agglomerate_CV = false; + agglomerate_CV = true; + + // actually, for symmetry (and possibly other cells) we only agglomerate cells that are on the marker + // at this point, the seed was on the boundary and the CV was not. so we check if the seed is a symmetry + if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || + (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { + agglomerate_CV = false; + } } } - - - - return agglomerate_CV; } diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index 87cde992e670..e4e752e16a6d 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -161,7 +161,7 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, /*--- Compute Forcing Term $P_(k+1) = I^(k+1)_k(P_k+F_k(u_k))-F_(k+1)(I^(k+1)_k u_k)$ and update solution for multigrid ---*/ if ( iMesh < config->GetnMGLevels() ) { - cout << "imesh =" << iMesh << " " << config->GetnMGLevels() << endl; + //cout << "imesh =" << iMesh << " " << config->GetnMGLevels() << endl; /*--- Shorter names to refer to coarse grid entities. ---*/ @@ -272,7 +272,7 @@ unsigned short iRKLimit) { Adjoint_Setup(geometry, solver_container, config_container, RunTime_EqSystem, timeIter, iZone); } - cout << "pre-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; + //cout << "pre-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; /*--- Space integration ---*/ Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); @@ -312,7 +312,7 @@ void CMultiGridIntegration::PostSmoothing(unsigned short RunTime_EqSystem, CSolv /*--- Space integration ---*/ Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); - cout << "post-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; + //cout << "post-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; /*--- Time integration, update solution using the old solution plus the solution increment ---*/ Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); From 9e6e7910d40da250afae83b3fc531c526437cfd8 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 16 Nov 2025 10:58:09 +0100 Subject: [PATCH 09/41] update agglomeration, aggressive settings --- .../include/geometry/CMultiGridGeometry.hpp | 10 ++ Common/src/geometry/CMultiGridGeometry.cpp | 115 +++++++++++------- SU2_CFD/src/drivers/CDriver.cpp | 2 +- SU2_CFD/src/solvers/CSolver.cpp | 21 ++++ 4 files changed, 102 insertions(+), 46 deletions(-) diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 7039c6c50c16..9cd252fd7764 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -154,4 +154,14 @@ class CMultiGridGeometry final : public CGeometry { * \param[in] val_marker - Index of the boundary marker. */ void SetMultiGridWallTemperature(const CGeometry* fine_grid, unsigned short val_marker) override; + + /*! + * \brief Write VTK file to visualize the agglomeration structure. + * \param[in] fine_grid - Geometrical definition of the fine grid. + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Multigrid level index. + * \param[in] filename - Name of the output VTK file. + */ + void WriteAgglomerationVTK(const CGeometry* fine_grid, const CConfig* config, + unsigned short iMesh, const std::string& filename) const; }; diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 7144380f302a..09776dbb9a7f 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -103,7 +103,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- We add the seed point (child) to the parent control volume ---*/ nodes->SetChildren_CV(Index_CoarseCV, 0, iPoint); - bool agglomerate_seed = true; + bool agglomerate_seed = false; auto counter = 0; unsigned short copy_marker[3] = {}; marker_seed.push_back(iMarker); @@ -142,6 +142,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; + /*--- Note that if the marker is a SEND_RECEIVE, then the node is actually an interior point. + In that case it can only be agglomerated with another interior point. ---*/ + } /*--- If there are two markers, we will agglomerate if any of the @@ -150,8 +153,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Note that in 2D, this is a corner and we do not agglomerate. ---*/ /*--- In 3D, we agglomerate if the 2 markers are the same. ---*/ if (counter == 2) { - /*--- Only agglomerate if the 2 markers are MPI markers. ---*/ - // ? not possible to have 2 send-receive markers on the same point? + /*--- Only agglomerate if one of the 2 markers are MPI markers. ---*/ agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); @@ -160,6 +162,12 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { agglomerate_seed = false; } + + /*--- In 2D, corners are not agglomerated, but in 3D counter=2 means we are on the + edge of a 2D face. In that case, agglomerate if both nodes are the same. ---*/ + //if (nDim == 2) agglomerate_seed = false; + + } /*--- If there are more than 2 markers, the aglomeration will be discarded ---*/ @@ -194,10 +202,16 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; + /*--- In 2D, we only agglomerate 2 nodes if the nodes are on the line edge. ---*/ + if ((nDim==2) && (counter==1)) break; + /*--- In 3D, we only agglomerate 2 nodes if the nodes are on the surface edge. ---*/ + if ((nDim==3) && (counter==2)) break; + /*--- Apply maxAgglomSize limit for 3D internal boundary face nodes (counter==1 in 3D). ---*/ + if (nChildren==maxAgglomSize) break; + } } - //Suitable_Indirect_Neighbors.clear(); /*--- Only take into account indirect neighbors for 3D faces, not 2D. ---*/ if (nDim == 3) { @@ -228,6 +242,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- We set the value of the child ---*/ nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; + /*--- Apply maxAgglomSize limit for 3D internal boundary face nodes. ---*/ + if (nChildren==maxAgglomSize) break; } } } @@ -427,22 +443,22 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- If the total would exceed maxAgglomSize, try to redistribute children to neighbors ---*/ if (nChildren_Total > maxAgglomSize) { - cout << " Merging isolated point " << iCoarsePoint - << " to point " << iCoarsePoint_Complete + cout << " Merging isolated point " << iCoarsePoint + << " to point " << iCoarsePoint_Complete << " would exceed limit (" << nChildren_Total << " > " << maxAgglomSize << ")" << endl; /*--- Find neighbors of the target coarse point that have room ---*/ unsigned short nChildrenToRedistribute = nChildren_Total - maxAgglomSize; - + for (auto jCoarsePoint : nodes->GetPoints(iCoarsePoint_Complete)) { if (nChildrenToRedistribute == 0) break; - + auto nChildren_Neighbor = nodes->GetnChildren_CV(jCoarsePoint); if (nChildren_Neighbor < maxAgglomSize) { - unsigned short nCanTransfer = min(nChildrenToRedistribute, + unsigned short nCanTransfer = min(nChildrenToRedistribute, static_cast(maxAgglomSize - nChildren_Neighbor)); - - cout << " Redistributing " << nCanTransfer << " children from point " + + cout << " Redistributing " << nCanTransfer << " children from point " << iCoarsePoint_Complete << " to neighbor " << jCoarsePoint << endl; /*--- Transfer children from target to neighbor ---*/ @@ -451,24 +467,24 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un auto nChildren_Current = nodes->GetnChildren_CV(iCoarsePoint_Complete); if (nChildren_Current > 0) { auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint_Complete, nChildren_Current - 1); - + /*--- Add to neighbor ---*/ auto nChildren_Neighbor_Current = nodes->GetnChildren_CV(jCoarsePoint); nodes->SetChildren_CV(jCoarsePoint, nChildren_Neighbor_Current, iFinePoint); nodes->SetnChildren_CV(jCoarsePoint, nChildren_Neighbor_Current + 1); - + /*--- Update parent ---*/ fine_grid->nodes->SetParent_CV(iFinePoint, jCoarsePoint); - + /*--- Remove from target (by reducing count) ---*/ nodes->SetnChildren_CV(iCoarsePoint_Complete, nChildren_Current - 1); - + nChildrenToRedistribute--; } } } } - + /*--- Update the target's child count after redistribution ---*/ nChildren_Target = nodes->GetnChildren_CV(iCoarsePoint_Complete); } @@ -485,8 +501,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Update the number of children control volumes ---*/ nodes->SetnChildren_CV(iCoarsePoint_Complete, nChildren); nodes->SetnChildren_CV(iCoarsePoint, 0); - - cout << " Final: point " << iCoarsePoint_Complete + + cout << " Final: point " << iCoarsePoint_Complete << " has " << nChildren << " children" << endl; } } @@ -609,10 +625,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un const su2double ratio = su2double(Global_nPointFine) / su2double(Global_nPointCoarse); cout << "********** ratio = " << ratio << endl; - - //if (((nDim == 2) && (ratio < 2.5)) || ((nDim == 3) && (ratio < 2.5))) { - // nijso: too high for very small test meshes. - if (((nDim == 2) && (ratio < 3.0)) || ((nDim == 3) && (ratio < 3.0))) { + // lower value leads to more levels being accepted. + if (((nDim == 2) && (ratio < 1.5)) || ((nDim == 3) && (ratio < 1.5))) { config->SetMGLevels(iMesh - 1); } else if (rank == MASTER_NODE) { PrintingToolbox::CTablePrinter MGTable(&std::cout); @@ -686,48 +700,58 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector Valley: only if of the same type---*/ if (counter == 1) { /*--- We agglomerate if there is only one marker and it is the same marker as the seed marker ---*/ - // note that this should be the same marker id, not just the same marker type - if (copy_marker[0] == marker_seed[0]) agglomerate_CV = true; - - /*--- If there is only one marker, but the marker is the SEND_RECEIVE ---*/ - - if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - agglomerate_CV = true; + // So this is the case when in 2D we are on an edge, and in 3D we are in the interior of a surface. + // note that this should be the same marker id, not just the same marker type. + // also note that the seed point can have 2 markers, one of them may be a send-receive. + if ((marker_seed.size()==1) && (copy_marker[0] == marker_seed[0])) agglomerate_CV = true; + if ((marker_seed.size() == 2) && (config->GetMarker_All_KindBC(marker_seed[0]) == SEND_RECEIVE)) { + if (copy_marker[0] == marker_seed[1]) { + agglomerate_CV = true; + } } - - if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || - (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { - if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { - agglomerate_CV = false; + if ((marker_seed.size() == 2) && (config->GetMarker_All_KindBC(marker_seed[1]) == SEND_RECEIVE)) { + if (copy_marker[0] == marker_seed[0]) { + agglomerate_CV = true; } } + + /*--- Note: If there is only one marker, but the marker is the SEND_RECEIVE, then the point is actually an + interior point and we do not agglomerate. ---*/ + + + // if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || + // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { + // if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { + // agglomerate_CV = false; + // } + // } } /*--- If there are two markers in the vertex that is going to be aglomerated ---*/ if (counter == 2) { - /*--- First we verify that the seed is a physical boundary ---*/ - if (config->GetMarker_All_KindBC(marker_seed[0]) != SEND_RECEIVE) { - /*--- Then we check that one of the markers is equal to the seed marker, and the other is send/receive ---*/ - if (((copy_marker[0] == marker_seed[0]) && (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE)) || - ((config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) && (copy_marker[1] == marker_seed[0]))) { + /*--- Both markers have to be the same. ---*/ + + if (marker_seed.size() == 2) { + if (((copy_marker[0] == marker_seed[0]) && (copy_marker[1] == marker_seed[1])) || + ((copy_marker[0] == marker_seed[1]) && (copy_marker[1] == marker_seed[0]))) { agglomerate_CV = true; } } } } - /*--- If the element belongs to the domain, it is always agglomerated. ---*/ + /*--- If the element belongs to the domain, it is never agglomerated with a boundary node. ---*/ else { - agglomerate_CV = true; + agglomerate_CV = false; // actually, for symmetry (and possibly other cells) we only agglomerate cells that are on the marker // at this point, the seed was on the boundary and the CV was not. so we check if the seed is a symmetry - if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || - (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { - agglomerate_CV = false; - } + // if ((config->GetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || + // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { + // agglomerate_CV = false; + // } } } @@ -1242,3 +1266,4 @@ void CMultiGridGeometry::FindNormal_Neighbor(const CConfig* config) { } } } + diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 7e17ed9c6015..3ee24b90407d 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -810,7 +810,7 @@ void CDriver::InitializeGeometryFVM(CConfig *config, CGeometry **&geometry) { geometry[MESH_0]->SetMGLevel(MESH_0); if ((config->GetnMGLevels() != 0) && (rank == MASTER_NODE)) - cout << "Setting the multigrid structure." << endl; + cout << "Setting the multigrid structure. NMG="<< config->GetnMGLevels() << endl; /*--- Loop over all the new grid ---*/ diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index a9c3875406bf..d451dd0654b9 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1940,6 +1940,27 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, SU2_MPI::Allreduce(&myCFLMax, &Max_CFL_Local, 1, MPI_DOUBLE, MPI_MAX, SU2_MPI::GetComm()); SU2_MPI::Allreduce(&myCFLSum, &Avg_CFL_Local, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); Avg_CFL_Local /= su2double(geometry[iMesh]->GetGlobal_nPointDomain()); + + /*--- Update the config CFL for MESH_0 with the average adapted value. ---*/ + config->SetCFL(MESH_0, Avg_CFL_Local); + + /*--- Update coarse mesh CFL values using the same scaling as in CMultiGridGeometry.cpp. + This ensures coarse meshes follow the adaptive CFL changes from the fine mesh. ---*/ + const unsigned short nMGLevels = config->GetnMGLevels(); + const unsigned short nDim = geometry[MESH_0]->GetnDim(); + + for (unsigned short iMGLevel = 1; iMGLevel <= nMGLevels; iMGLevel++) { + /*--- Get coarse/fine point counts for this level. ---*/ + const su2double nPointCoarse = su2double(geometry[iMGLevel]->GetGlobal_nPointDomain()); + const su2double nPointFine = su2double(geometry[iMGLevel-1]->GetGlobal_nPointDomain()); + + /*--- Apply the same scaling factor as used during geometry construction. ---*/ + const su2double factor = 2.0; + const su2double Coeff = pow(nPointFine / nPointCoarse, 1.0 / nDim); + const su2double CFL_Coarse = factor * config->GetCFL(iMGLevel - 1) / Coeff; + + config->SetCFL(iMGLevel, CFL_Coarse); + } } END_SU2_OMP_SAFE_GLOBAL_ACCESS } From 6d42a495107c3ac2838bac8e7de6f9f50f924bc7 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 17 Nov 2025 10:25:34 +0100 Subject: [PATCH 10/41] add EULER_WALLS by checking straightness --- .../include/geometry/CMultiGridGeometry.hpp | 6 + Common/src/geometry/CGeometry.cpp | 9 +- Common/src/geometry/CMultiGridGeometry.cpp | 113 +++++++++++------- 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 9cd252fd7764..3696ddfa0ffa 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -66,6 +66,12 @@ class CMultiGridGeometry final : public CGeometry { void SetSuitableNeighbors(vector& Suitable_Indirect_Neighbors, unsigned long iPoint, unsigned long Index_CoarseCV, const CGeometry* fine_grid) const; + /*! + * \brief Compute surface straightness for multigrid geometry. + * \param[in] config - Definition of the particular problem. + */ + void ComputeSurfStraightness(CConfig* config); + public: /*--- This is to suppress Woverloaded-virtual, omitting it has no negative impact. ---*/ using CGeometry::SetBoundControlVolume; diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index d2b79520f8f0..25b54a305c6c 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -2472,8 +2472,8 @@ void CGeometry::ComputeModifiedSymmetryNormals(const CConfig* config) { * All nodes that are shared by multiple symmetries have to get a corrected normal. */ /*--- Compute if markers are straight lines or planes. ---*/ - ComputeSurfStraightness(config, false); - + ComputeSurfStraightness(config, true); + cout << "ComputeModifiedSymmetryNormals:: boundIsStraight size = " << boundIsStraight.size() << endl; symmetryNormals.clear(); symmetryNormals.resize(nMarker); std::vector symMarkers, curvedSymMarkers; @@ -2589,7 +2589,7 @@ void CGeometry::ComputeSurfStraightness(const CConfig* config, bool print_on_scr constexpr passivedouble epsilon = 1.0e-6; su2double Area; string Local_TagBound, Global_TagBound; - + cout << "Computing surface straightness for symmetry and Euler wall boundary markers..." << endl; vector Normal(nDim), UnitNormal(nDim), RefUnitNormal(nDim); /*--- Assume now that this boundary marker is straight. As soon as one @@ -2602,6 +2602,7 @@ void CGeometry::ComputeSurfStraightness(const CConfig* config, bool print_on_scr the value false (or see cases specified in the conditional below) which could be wrong. ---*/ boundIsStraight.resize(nMarker); + cout << "boundisstraight size = " << boundIsStraight.size() << endl; fill(boundIsStraight.begin(), boundIsStraight.end(), true); /*--- Loop over all local markers ---*/ @@ -2685,7 +2686,7 @@ void CGeometry::ComputeSurfStraightness(const CConfig* config, bool print_on_scr /*--- Product of type (bool) is equivalnt to a 'logical and' ---*/ SU2_MPI::Allreduce(Buff_Send_isStraight.data(), Buff_Recv_isStraight.data(), nMarker_Global, MPI_INT, MPI_PROD, SU2_MPI::GetComm()); - + cout << "global markers = " << nMarker_Global << endl; /*--- Print results on screen. ---*/ if (rank == MASTER_NODE) { for (iMarker_Global = 0; iMarker_Global < nMarker_Global; iMarker_Global++) { diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 09776dbb9a7f..bf48ab21f6b2 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -37,6 +37,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Maximum agglomeration size in 2D is 4 nodes, in 3D is 8 nodes. ---*/ const short int maxAgglomSize=4; + /*--- Compute surface straightness to determine straight boundaries ---*/ + if (iMesh == MESH_0) ComputeSurfStraightness(config); + else boundIsStraight = fine_grid->boundIsStraight; + cout << "bound size = " << boundIsStraight.size() << endl; + /*--- Agglomeration Scheme II (Nishikawa, Diskin, Thomas) Create a queue system to do the agglomeration 1st) More than two markers ---> Vertices (never agglomerate) @@ -47,7 +52,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Set a marker to indicate indirect agglomeration, for quads and hexs, i.e. consider up to neighbors of neighbors of neighbors. For other levels this information is propagated down during their construction. ---*/ - + cout <<"Setting up multigrid level " << iMesh << " agglomeration..." << endl; if (iMesh == MESH_1) { for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) fine_grid->nodes->SetAgglomerate_Indirect(iPoint, false); @@ -64,7 +69,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } /*--- Create the coarse grid structure using as baseline the fine grid ---*/ - + cout << " Creating coarse grid structure..." << endl; CMultiGridQueue MGQueue_InnerCV(fine_grid->GetnPoint()); vector Suitable_Indirect_Neighbors; @@ -139,9 +144,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un // The seed/parent is one valley, so we set this part to true // if the child is only on this same valley, we set it to true as well. agglomerate_seed = true; - + cout << " seed has one marker, can be agglomerated. size=" << boundIsStraight.size() << endl; /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ - if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; + if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL && !boundIsStraight[marker_seed[0]]) agglomerate_seed = false; + cout << "end of seed agglomeration check" << endl; /*--- Note that if the marker is a SEND_RECEIVE, then the node is actually an interior point. In that case it can only be agglomerated with another interior point. ---*/ @@ -157,9 +163,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); - /* --- Euler walls can also not be agglomerated when the point has 2 markers ---*/ - if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || - (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { + /* --- Euler walls can also not be agglomerated when the point has 2 markers or if curved ---*/ + if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL && !boundIsStraight[copy_marker[0]]) || + (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL && !boundIsStraight[copy_marker[1]])) { agglomerate_seed = false; } @@ -173,7 +179,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- If there are more than 2 markers, the aglomeration will be discarded ---*/ if (counter > 2) agglomerate_seed = false; - + // note that if one of the markers is SEND_RECEIVE, then we could allow agglomeration since + // the real number of markers is then 2. /*--- If the seed (parent) can be agglomerated, we try to agglomerate connected childs to the parent ---*/ @@ -668,6 +675,46 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } +bool CMultiGridGeometry::GeometricalCheck(unsigned long iPoint, const CGeometry* fine_grid, + const CConfig* config) const { + su2double max_dimension = 1.2; + + /*--- Evaluate the total size of the element ---*/ + + bool Volume = true; + su2double ratio = pow(fine_grid->nodes->GetVolume(iPoint), 1.0 / su2double(nDim)) * max_dimension; + su2double limit = pow(config->GetDomainVolume(), 1.0 / su2double(nDim)); + if (ratio > limit) { + Volume = false; + cout << "Volume limit reached!" << endl; + } + /*--- Evaluate the stretching of the element ---*/ + + bool Stretching = true; + + /* unsigned short iNode, iDim; + unsigned long jPoint; + su2double *Coord_i = fine_grid->nodes->GetCoord(iPoint); + su2double max_dist = 0.0 ; su2double min_dist = 1E20; + for (iNode = 0; iNode < fine_grid->nodes->GetnPoint(iPoint); iNode ++) { + jPoint = fine_grid->nodes->GetPoint(iPoint, iNode); + su2double *Coord_j = fine_grid->nodes->GetCoord(jPoint); + su2double distance = 0.0; + for (iDim = 0; iDim < nDim; iDim++) + distance += (Coord_j[iDim]-Coord_i[iDim])*(Coord_j[iDim]-Coord_i[iDim]); + distance = sqrt(distance); + max_dist = max(distance, max_dist); + min_dist = min(distance, min_dist); + } + if ( max_dist/min_dist > 100.0 ) Stretching = false;*/ + + return (Stretching && Volume); +} + +void CMultiGridGeometry::ComputeSurfStraightness(CConfig* config) { + CGeometry::ComputeSurfStraightness(config, true); +} + bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector marker_seed, const CGeometry* fine_grid, const CConfig* config) const { bool agglomerate_CV = false; @@ -679,12 +726,13 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectornodes->GetBoundary(CVPoint)) { /*--- Identify the markers of the vertex that we want to agglomerate ---*/ // count number of markers on the agglomeration candidate - int counter = 0; - unsigned short copy_marker[3] = {}; for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker() && counter < 3; jMarker++) { if (fine_grid->nodes->GetVertex(CVPoint, jMarker) != -1) { copy_marker[counter] = jMarker; @@ -753,6 +801,17 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectornodes->GetBoundary(CVPoint)) { + // for (int i = 0; i < counter; i++) { + // if (config->GetMarker_All_KindBC(copy_marker[i]) == EULER_WALL && !boundIsStraight[copy_marker[i]]) { + // agglomerate_CV = false; + // break; + // } + // } + // } + } return agglomerate_CV; @@ -760,41 +819,7 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectornodes->GetVolume(iPoint), 1.0 / su2double(nDim)) * max_dimension; - su2double limit = pow(config->GetDomainVolume(), 1.0 / su2double(nDim)); - if (ratio > limit) { - Volume = false; - cout << "Volume limit reached!" << endl; - } - /*--- Evaluate the stretching of the element ---*/ - bool Stretching = true; - - /* unsigned short iNode, iDim; - unsigned long jPoint; - su2double *Coord_i = fine_grid->nodes->GetCoord(iPoint); - su2double max_dist = 0.0 ; su2double min_dist = 1E20; - for (iNode = 0; iNode < fine_grid->nodes->GetnPoint(iPoint); iNode ++) { - jPoint = fine_grid->nodes->GetPoint(iPoint, iNode); - su2double *Coord_j = fine_grid->nodes->GetCoord(jPoint); - su2double distance = 0.0; - for (iDim = 0; iDim < nDim; iDim++) - distance += (Coord_j[iDim]-Coord_i[iDim])*(Coord_j[iDim]-Coord_i[iDim]); - distance = sqrt(distance); - max_dist = max(distance, max_dist); - min_dist = min(distance, min_dist); - } - if ( max_dist/min_dist > 100.0 ) Stretching = false;*/ - - return (Stretching && Volume); -} void CMultiGridGeometry::SetSuitableNeighbors(vector& Suitable_Indirect_Neighbors, unsigned long iPoint, unsigned long Index_CoarseCV, const CGeometry* fine_grid) const { From 4495da690420f7e8ca8f1b2c73b3201ecf6f6199 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 17 Nov 2025 12:52:44 +0100 Subject: [PATCH 11/41] remove some commented cout statements --- Common/src/geometry/CMultiGridGeometry.cpp | 30 ++++++---------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index bf48ab21f6b2..267e5751cc09 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -40,7 +40,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Compute surface straightness to determine straight boundaries ---*/ if (iMesh == MESH_0) ComputeSurfStraightness(config); else boundIsStraight = fine_grid->boundIsStraight; - cout << "bound size = " << boundIsStraight.size() << endl; /*--- Agglomeration Scheme II (Nishikawa, Diskin, Thomas) Create a queue system to do the agglomeration @@ -52,7 +51,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Set a marker to indicate indirect agglomeration, for quads and hexs, i.e. consider up to neighbors of neighbors of neighbors. For other levels this information is propagated down during their construction. ---*/ - cout <<"Setting up multigrid level " << iMesh << " agglomeration..." << endl; if (iMesh == MESH_1) { for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) fine_grid->nodes->SetAgglomerate_Indirect(iPoint, false); @@ -69,7 +67,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } /*--- Create the coarse grid structure using as baseline the fine grid ---*/ - cout << " Creating coarse grid structure..." << endl; CMultiGridQueue MGQueue_InnerCV(fine_grid->GetnPoint()); vector Suitable_Indirect_Neighbors; @@ -78,18 +75,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un unsigned long Index_CoarseCV = 0; /*--- STEP 1: The first step is the boundary agglomeration. ---*/ - //cout << "loop over Markers" << endl; for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { - //cout << "*** loop:marker = " << iMarker << " " << config->GetMarker_All_TagBound(iMarker) << endl; for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - //cout << "*** seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " << fine_grid->nodes->GetCoord(iPoint, 1) << endl; - //cout << "*** seed point is on the marker: " << fine_grid->nodes->GetVertex(iPoint, iMarker) - //<< " , agglomerated=" <nodes->GetAgglomerate(iPoint) << " , domain = " <nodes->GetDomain(iPoint) - //<< " , geo =" << GeometricalCheck(iPoint, fine_grid, config) - //< marker_seed; @@ -98,8 +88,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un (GeometricalCheck(iPoint, fine_grid, config))) { unsigned short nChildren = 1; - //cout << " seed point is evaluated for agglomeration " << endl; - /*--- We set an index for the parent control volume, this also marks it as agglomerated. ---*/ @@ -118,9 +106,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker(); jMarker++) { const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); //fine_grid->GetMarker_Tag(jMarker); - //cout << " marker = " << jMarker<<" " << Marker_Tag << endl; if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { - //cout << " point is on marker" << endl; copy_marker[counter] = jMarker; counter++; @@ -130,9 +116,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } - - //cout << " counter for this seed point = " << counter << endl; - /*--- To agglomerate a vertex it must have only one physical bc!! This can be improved. If there is only one marker, it is a good candidate for agglomeration ---*/ @@ -144,10 +127,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un // The seed/parent is one valley, so we set this part to true // if the child is only on this same valley, we set it to true as well. agglomerate_seed = true; - cout << " seed has one marker, can be agglomerated. size=" << boundIsStraight.size() << endl; /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ + //if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL && !boundIsStraight[marker_seed[0]]) agglomerate_seed = false; - cout << "end of seed agglomeration check" << endl; /*--- Note that if the marker is a SEND_RECEIVE, then the node is actually an interior point. In that case it can only be agglomerated with another interior point. ---*/ @@ -164,6 +146,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); /* --- Euler walls can also not be agglomerated when the point has 2 markers or if curved ---*/ + // if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || + // (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { + // agglomerate_seed = false; + // } if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL && !boundIsStraight[copy_marker[0]]) || (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL && !boundIsStraight[copy_marker[1]])) { agglomerate_seed = false; @@ -209,9 +195,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; - /*--- In 2D, we only agglomerate 2 nodes if the nodes are on the line edge. ---*/ + /*--- In 2D, we agglomerate exactly 2 nodes if the nodes are on the line edge. ---*/ if ((nDim==2) && (counter==1)) break; - /*--- In 3D, we only agglomerate 2 nodes if the nodes are on the surface edge. ---*/ + /*--- In 3D, we agglomerate exactly 2 nodes if the nodes are on the surface edge. ---*/ if ((nDim==3) && (counter==2)) break; /*--- Apply maxAgglomSize limit for 3D internal boundary face nodes (counter==1 in 3D). ---*/ if (nChildren==maxAgglomSize) break; From c38a63349970d23690f04c9459747b0e1861df1a Mon Sep 17 00:00:00 2001 From: bigfooted Date: Wed, 19 Nov 2025 08:25:06 +0100 Subject: [PATCH 12/41] remove some commented cout statements --- Common/include/CConfig.hpp | 2 +- Common/include/geometry/CMultiGridGeometry.hpp | 9 --------- Common/src/geometry/CGeometry.cpp | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index eb8f7248397d..b5b1f97b40c4 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -3031,7 +3031,7 @@ class CConfig { * \brief Get the number of Runge-Kutta steps. * \return Number of Runge-Kutta steps. */ - unsigned short GetnRKStep(void) const { + unsigned short GetnRKStep(void) const { unsigned short iRKLimit = 1; diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 3696ddfa0ffa..5dba1dd4e3c4 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -161,13 +161,4 @@ class CMultiGridGeometry final : public CGeometry { */ void SetMultiGridWallTemperature(const CGeometry* fine_grid, unsigned short val_marker) override; - /*! - * \brief Write VTK file to visualize the agglomeration structure. - * \param[in] fine_grid - Geometrical definition of the fine grid. - * \param[in] config - Definition of the particular problem. - * \param[in] iMesh - Multigrid level index. - * \param[in] filename - Name of the output VTK file. - */ - void WriteAgglomerationVTK(const CGeometry* fine_grid, const CConfig* config, - unsigned short iMesh, const std::string& filename) const; }; diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index 25b54a305c6c..2dabe5de3b0a 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -2473,7 +2473,6 @@ void CGeometry::ComputeModifiedSymmetryNormals(const CConfig* config) { /*--- Compute if markers are straight lines or planes. ---*/ ComputeSurfStraightness(config, true); - cout << "ComputeModifiedSymmetryNormals:: boundIsStraight size = " << boundIsStraight.size() << endl; symmetryNormals.clear(); symmetryNormals.resize(nMarker); std::vector symMarkers, curvedSymMarkers; From d355978a8ee3f13eb9a4c1592506ff8e9712bcf4 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Wed, 19 Nov 2025 23:20:44 +0100 Subject: [PATCH 13/41] add convergence check and early exit for pre,post and correction smoothing --- Common/include/CConfig.hpp | 28 ++++ Common/src/CConfig.cpp | 11 ++ Common/src/geometry/CMultiGridGeometry.cpp | 4 +- QuickStart/inv_NACA0012.cfg | 13 +- .../integration/CMultiGridIntegration.hpp | 2 +- .../src/integration/CMultiGridIntegration.cpp | 129 ++++++++++++++---- .../src/integration/ComputeLinSysResRMS.hpp | 26 ++++ SU2_CFD/src/solvers/CSolver.cpp | 9 +- config_template.cfg | 3 + 9 files changed, 186 insertions(+), 39 deletions(-) create mode 100644 SU2_CFD/src/integration/ComputeLinSysResRMS.hpp diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index b5b1f97b40c4..12f9017c83ec 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -496,6 +496,10 @@ class CConfig { unsigned short *MG_PreSmooth, /*!< \brief Multigrid Pre smoothing. */ *MG_PostSmooth, /*!< \brief Multigrid Post smoothing. */ *MG_CorrecSmooth; /*!< \brief Multigrid Jacobi implicit smoothing of the correction. */ + bool MG_Smooth_EarlyExit; /*!< \brief Enable early exit for MG smoothing. */ + su2double MG_Smooth_Res_Threshold; /*!< \brief Residual reduction threshold for early exit. */ + bool MG_Smooth_Output; /*!< \brief Output per-iteration multigrid smoothing info. */ + su2double MG_Smooth_Coeff; /*!< \brief Smoothing coefficient for multigrid correction smoothing. */ su2double *LocationStations; /*!< \brief Airfoil sections in wing slicing subroutine. */ ENUM_MULTIZONE Kind_MZSolver; /*!< \brief Kind of multizone solver. */ @@ -3837,6 +3841,30 @@ class CConfig { return MG_CorrecSmooth[val_mesh]; } + /*! + * \brief Get whether early exit is enabled for MG smoothing. + * \return True if early exit is enabled. + */ + bool GetMG_Smooth_EarlyExit() const { return MG_Smooth_EarlyExit; } + + /*! + * \brief Get the residual threshold for early exit in MG smoothing. + * \return Residual threshold. + */ + su2double GetMG_Smooth_Res_Threshold() const { return MG_Smooth_Res_Threshold; } + + /*! + * \brief Get whether per-iteration output is enabled for MG smoothing. + * \return True if output is enabled. + */ + bool GetMG_Smooth_Output() const { return MG_Smooth_Output; } + + /*! + * \brief Get the smoothing coefficient for MG correction smoothing. + * \return Smoothing coefficient. + */ + su2double GetMG_Smooth_Coeff() const { return MG_Smooth_Coeff; } + /*! * \brief plane of the FFD (I axis) that should be fixed. * \param[in] val_index - Index of the arrray with all the planes in the I direction that should be fixed. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 3c866c6f4388..886076bc6578 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1955,6 +1955,17 @@ void CConfig::SetConfig_Options() { /*!\brief MG_DAMP_PROLONGATION\n DESCRIPTION: Damping factor for the correction prolongation. DEFAULT 0.75 \ingroup Config*/ addDoubleOption("MG_DAMP_PROLONGATION", Damp_Correc_Prolong, 0.75); + /*!\brief MG_SMOOTH_EARLY_EXIT\n DESCRIPTION: Enable early exit for MG smoothing iterations based on RMS residual. DEFAULT: NO \ingroup Config*/ + addBoolOption("MG_SMOOTH_EARLY_EXIT", MG_Smooth_EarlyExit, false); + /*!\brief MG_SMOOTH_RES_THRESHOLD\n DESCRIPTION: RMS residual threshold for early exit in MG smoothing. DEFAULT: 1e-2 \ingroup Config*/ + addDoubleOption("MG_SMOOTH_RES_THRESHOLD", MG_Smooth_Res_Threshold, 1e-2); + + /*!\brief MG_SMOOTH_OUTPUT\n DESCRIPTION: Output per-iteration RMS for MG smoothing. DEFAULT: NO \ingroup Config*/ + addBoolOption("MG_SMOOTH_OUTPUT", MG_Smooth_Output, false); + + /*!\brief MG_SMOOTH_COEFF\n DESCRIPTION: Smoothing coefficient for MG correction smoothing. DEFAULT: 1.25 \ingroup Config*/ + addDoubleOption("MG_SMOOTH_COEFF", MG_Smooth_Coeff, 1.25); + /*!\par CONFIG_CATEGORY: Spatial Discretization \ingroup Config*/ /*--- Options related to the spatial discretization ---*/ diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 267e5751cc09..2fb73749e8e5 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -610,9 +610,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if (iMesh != MESH_0) { //const su2double factor = 1.5; //nijso: too high - const su2double factor = 2.0; + const su2double factor = 1.1; const su2double Coeff = pow(su2double(Global_nPointFine) / Global_nPointCoarse, 1.0 / nDim); - const su2double CFL = factor * config->GetCFL(iMesh - 1) / Coeff; + const su2double CFL = factor * config->GetCFL(iMesh - 1);// / Coeff; config->SetCFL(iMesh, CFL); } diff --git a/QuickStart/inv_NACA0012.cfg b/QuickStart/inv_NACA0012.cfg index f0859e250869..5507dfacc849 100644 --- a/QuickStart/inv_NACA0012.cfg +++ b/QuickStart/inv_NACA0012.cfg @@ -105,7 +105,7 @@ CFL_ADAPT= NO CFL_ADAPT_PARAM= ( 0.1, 2.0, 10.0, 1e10 ) % % Number of total iterations -ITER= 250 +ITER= 5 % ------------------------ LINEAR SOLVER DEFINITION ---------------------------% % @@ -136,13 +136,22 @@ MG_PRE_SMOOTH= ( 1, 2, 3, 3 ) MG_POST_SMOOTH= ( 0, 0, 0, 0 ) % % Jacobi implicit smoothing of the correction -MG_CORRECTION_SMOOTH= ( 0, 0, 0, 0 ) +MG_CORRECTION_SMOOTH= ( 0, 3, 3, 3 ) % % Damping factor for the residual restriction MG_DAMP_RESTRICTION= 1.0 % % Damping factor for the correction prolongation MG_DAMP_PROLONGATION= 1.0 +% +% Enable early exit for multigrid smoothing iterations +MG_SMOOTH_EARLY_EXIT= YES +% +% RMS residual threshold for early exit (fraction of initial RMS) +MG_SMOOTH_RES_THRESHOLD= 0.99 +% +% Enable output of RMS residual during smoothing iterations +MG_SMOOTH_OUTPUT= YES % -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% % diff --git a/SU2_CFD/include/integration/CMultiGridIntegration.hpp b/SU2_CFD/include/integration/CMultiGridIntegration.hpp index 7f8d4da35e37..471c1e1c58d6 100644 --- a/SU2_CFD/include/integration/CMultiGridIntegration.hpp +++ b/SU2_CFD/include/integration/CMultiGridIntegration.hpp @@ -177,7 +177,7 @@ class CMultiGridIntegration final : public CIntegration { * \param[in] config - Definition of the particular problem. */ void SmoothProlongated_Correction(unsigned short RunTime_EqSystem, CSolver *solver, CGeometry *geometry, - unsigned short val_nSmooth, su2double val_smooth_coeff, CConfig *config); + unsigned short val_nSmooth, su2double val_smooth_coeff, CConfig *config, unsigned short iMesh); /*! * \brief Restrict solution from fine grid to a coarse grid. diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index e4e752e16a6d..78739a1481ab 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -28,6 +28,8 @@ #include "../../include/integration/CMultiGridIntegration.hpp" #include "../../../Common/include/parallelization/omp_structure.hpp" +#include "ComputeLinSysResRMS.hpp" + CMultiGridIntegration::CMultiGridIntegration() : CIntegration() { } @@ -217,7 +219,7 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, /*--- 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); - SmoothProlongated_Correction(RunTime_EqSystem, solver_fine, geometry_fine, config->GetMG_CorrecSmooth(iMesh), 1.25, config); + SmoothProlongated_Correction(RunTime_EqSystem, solver_fine, geometry_fine, config->GetMG_CorrecSmooth(iMesh), config->GetMG_Smooth_Coeff(), config, iMesh); SetProlongated_Correction(solver_fine, geometry_fine, config, iMesh); @@ -245,43 +247,52 @@ unsigned short iRKLimit) { const unsigned short nPreSmooth = config->GetMG_PreSmooth(iMesh); const unsigned long timeIter = config->GetTimeIter(); + // Early exit settings from config + const bool early_exit_enabled = config->GetMG_Smooth_EarlyExit(); + const su2double early_exit_threshold = config->GetMG_Smooth_Res_Threshold(); + const bool output_enabled = config->GetMG_Smooth_Output(); + + su2double initial_rms = 0.0; + if (early_exit_enabled || output_enabled) { + initial_rms = ComputeLinSysResRMS(solver_fine, geometry_fine); + if (output_enabled) { + cout << "MG Pre-Smoothing Level " << iMesh << " Initial RMS: " << initial_rms << endl; + } + } + /*--- Do a presmoothing on the grid iMesh to be restricted to the grid iMesh+1 ---*/ for (unsigned short iPreSmooth = 0; iPreSmooth < nPreSmooth; iPreSmooth++) { - /*--- Time and space integration ---*/ for (unsigned short iRKStep = 0; iRKStep < iRKLimit; iRKStep++) { - /*--- Send-Receive boundary conditions, and preprocessing ---*/ solver_fine->Preprocessing(geometry_fine, solver_container_fine, config, iMesh, iRKStep, RunTime_EqSystem, false); - if (iRKStep == 0) { - /*--- Set the old solution ---*/ solver_fine->Set_OldSolution(); - if (classical_rk4) solver_fine->Set_NewSolution(); - // nijso asks: only call when classical_rk4? - // this copies solution to old solution, which we already did above. - //solver_fine->Set_OldSolution(); - - /*--- Compute time step, max eigenvalue, and integration scheme (steady and unsteady problems) ---*/ solver_fine->SetTime_Step(geometry_fine, solver_container_fine, config, iMesh, timeIter); - - /*--- Restrict the solution and gradient for the adjoint problem ---*/ - // nijso asks: why do we call this here but not in the post-smoothing? Adjoint_Setup(geometry, solver_container, config_container, RunTime_EqSystem, timeIter, iZone); - } - //cout << "pre-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; /*--- Space integration ---*/ Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); - /*--- Time integration, update solution using the old solution plus the solution increment ---*/ Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); - /*--- Send-Receive boundary conditions, and postprocessing ---*/ solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); + } + // Early exit check and output + if (early_exit_enabled || output_enabled) { + su2double current_rms = ComputeLinSysResRMS(solver_fine, geometry_fine); + if (output_enabled) { + cout << "MG Pre-Smoothing Level " << iMesh << " Iteration " << iPreSmooth + 1 << "/" << nPreSmooth << " RMS: " << current_rms << endl; + } + if (early_exit_enabled && current_rms < early_exit_threshold * initial_rms) { + if (output_enabled) { + cout << "MG Pre-Smoothing Level " << iMesh << " Early exit at iteration " << iPreSmooth + 1 << endl; + } + break; + } } } } @@ -293,32 +304,49 @@ void CMultiGridIntegration::PostSmoothing(unsigned short RunTime_EqSystem, CSolv const unsigned short nPostSmooth = config->GetMG_PostSmooth(iMesh); const unsigned long timeIter = config->GetTimeIter(); + // Early exit settings from config + const bool early_exit_enabled = config->GetMG_Smooth_EarlyExit(); + const su2double early_exit_threshold = config->GetMG_Smooth_Res_Threshold(); + const bool output_enabled = config->GetMG_Smooth_Output(); + + su2double initial_rms = 0.0; + if (early_exit_enabled || output_enabled) { + initial_rms = ComputeLinSysResRMS(solver_fine, geometry_fine); + if (output_enabled) { + cout << "MG Post-Smoothing Level " << iMesh << " Initial RMS: " << initial_rms << endl; + } + } + /*--- Do a postsmoothing on the grid iMesh after prolongation from the grid iMesh+1 ---*/ for (unsigned short iPostSmooth = 0; iPostSmooth < nPostSmooth; iPostSmooth++) { - for (unsigned short iRKStep = 0; iRKStep < iRKLimit; iRKStep++) { - solver_fine->Preprocessing(geometry_fine, solver_container_fine, config, iMesh, iRKStep, RunTime_EqSystem, false); - if (iRKStep == 0) { - /*--- Set the old solution ---*/ solver_fine->Set_OldSolution(); - if (classical_rk4) solver_fine->Set_NewSolution(); - solver_fine->SetTime_Step(geometry_fine, solver_container_fine, config, iMesh, timeIter); - + solver_fine->SetTime_Step(geometry_fine, solver_container_fine, config, iMesh, timeIter); } - /*--- Space integration ---*/ Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); - //cout << "post-smoothing mesh " << iMesh << " rkstep " << iRKStep << endl; /*--- Time integration, update solution using the old solution plus the solution increment ---*/ Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); - /*--- Send-Receive boundary conditions, and postprocessing ---*/ solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); + } + // Early exit check and output + if (early_exit_enabled || output_enabled) { + su2double current_rms = ComputeLinSysResRMS(solver_fine, geometry_fine); + if (output_enabled) { + cout << "MG Post-Smoothing Level " << iMesh << " Iteration " << iPostSmooth + 1 << "/" << nPostSmooth << " RMS: " << current_rms << endl; + } + if (early_exit_enabled && current_rms < early_exit_threshold * initial_rms) { + if (output_enabled) { + cout << "MG Post-Smoothing Level " << iMesh << " Early exit at iteration " << iPostSmooth + 1 << endl; + } + break; + } } } } @@ -400,7 +428,7 @@ void CMultiGridIntegration::GetProlongated_Correction(unsigned short RunTime_EqS } void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_EqSystem, CSolver *solver, CGeometry *geometry, - unsigned short val_nSmooth, su2double val_smooth_coeff, CConfig *config) { + unsigned short val_nSmooth, su2double val_smooth_coeff, CConfig *config, unsigned short iMesh) { /*--- Check if there is work to do. ---*/ if (val_nSmooth == 0) return; @@ -418,7 +446,19 @@ void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_ } END_SU2_OMP_FOR - /*--- Jacobi iterations. ---*/ + /*--- Compute initial RMS for adaptive smoothing ---*/ + su2double initial_rms = 0.0; + if (config->GetMG_Smooth_EarlyExit()) { + initial_rms = ComputeLinSysResRMS(solver, geometry); + } + + /*--- Output initial RMS if enabled ---*/ + if (config->GetMG_Smooth_Output()) { + cout << "MG Correction-Smoothing Level " << iMesh << " Initial RMS: " << initial_rms << endl; + } + + /*--- Jacobi iterations with adaptive early exit ---*/ + unsigned short actual_iterations = val_nSmooth; for (iSmooth = 0; iSmooth < val_nSmooth; iSmooth++) { @@ -470,6 +510,35 @@ void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_ } } + /*--- Output RMS residual if enabled ---*/ + if (config->GetMG_Smooth_Output()) { + const su2double RMS_Res = ComputeLinSysResRMS(solver, geometry); + cout << "MG Correction-Smoothing Level " << iMesh << " Iteration " << iSmooth+1 << "/" << val_nSmooth << " RMS: " << RMS_Res << endl; + } + + /*--- Adaptive early exit check after first iteration ---*/ + if (config->GetMG_Smooth_EarlyExit() && iSmooth == 0 && val_nSmooth > 1) { + su2double current_rms = ComputeLinSysResRMS(solver, geometry); + su2double reduction_ratio = current_rms / initial_rms; + + // If RMS reduction is sufficient (ratio <= threshold), additional iterations may not be necessary + if (reduction_ratio <= config->GetMG_Smooth_Res_Threshold()) { + if (config->GetMG_Smooth_Output()) { + cout << "MG Correction-Smoothing Level " << iMesh << " Early exit: sufficient RMS reduction (" + << reduction_ratio << " <= " << config->GetMG_Smooth_Res_Threshold() << ")" << endl; + } + actual_iterations = 1; // Only do this one iteration + break; + } + // If reduction is insufficient (ratio > threshold), continue with remaining iterations + } + + } + + /*--- Log if iterations were skipped ---*/ + if (config->GetMG_Smooth_Output() && actual_iterations < val_nSmooth) { + cout << "MG Correction-Smoothing Level " << iMesh << " completed " << actual_iterations + << "/" << val_nSmooth << " iterations (adaptive early exit)" << endl; } } diff --git a/SU2_CFD/src/integration/ComputeLinSysResRMS.hpp b/SU2_CFD/src/integration/ComputeLinSysResRMS.hpp new file mode 100644 index 000000000000..f69698146207 --- /dev/null +++ b/SU2_CFD/src/integration/ComputeLinSysResRMS.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "../../../Common/include/geometry/CGeometry.hpp" +#include "../../include/solvers/CSolver.hpp" +#include +#include +#include "../../../Common/include/CConfig.hpp" + +inline su2double ComputeLinSysResRMS(const CSolver* solver, const CGeometry* geometry) { + unsigned short nVar = solver->GetnVar(); + unsigned long nPointDomain = geometry->GetnPointDomain(); + std::vector sumRes(nVar, 0.0); + su2double localSum = 0.0; + for (unsigned long i = 0; i < nPointDomain; ++i) { + const su2double* res = solver->LinSysRes.GetBlock(i); + for (unsigned short v = 0; v < nVar; ++v) { + sumRes[v] += res[v] * res[v]; + } + } + for (unsigned short v = 0; v < nVar; ++v) localSum += sumRes[v]; + su2double globalSum = 0.0; + SU2_MPI::Allreduce(&localSum, &globalSum, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + unsigned long globalNPointDomain = 0; + SU2_MPI::Allreduce(&nPointDomain, &globalNPointDomain, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); + if (globalNPointDomain == 0) return 0.0; + return std::sqrt(globalSum / (globalNPointDomain * nVar)); +} diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index d451dd0654b9..5e2d89509eb7 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1955,11 +1955,12 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, const su2double nPointFine = su2double(geometry[iMGLevel-1]->GetGlobal_nPointDomain()); /*--- Apply the same scaling factor as used during geometry construction. ---*/ - const su2double factor = 2.0; + const su2double factor = 1.1; const su2double Coeff = pow(nPointFine / nPointCoarse, 1.0 / nDim); - const su2double CFL_Coarse = factor * config->GetCFL(iMGLevel - 1) / Coeff; - - config->SetCFL(iMGLevel, CFL_Coarse); + const su2double CFL_Coarse = factor * config->GetCFL(iMGLevel - 1);// / Coeff; + cout << "Adapted CFL for MG Level " << iMGLevel << ": " << CFL_Coarse << endl; + // nijso: comment this if we do not adapt the coarse level CFLs + //config->SetCFL(iMGLevel, CFL_Coarse); } } END_SU2_OMP_SAFE_GLOBAL_ACCESS diff --git a/config_template.cfg b/config_template.cfg index aa7a1a97fc97..54c9a4c5aca5 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1597,6 +1597,9 @@ MG_POST_SMOOTH= ( 0, 0, 0, 0 ) % Jacobi implicit smoothing of the correction MG_CORRECTION_SMOOTH= ( 0, 0, 0, 0 ) % +% Smoothing coefficient for multigrid correction smoothing (1.25 by default) +MG_SMOOTH_COEFF= 1.25 +% % Damping factor for the residual restriction MG_DAMP_RESTRICTION= 0.75 % From 9642b949d4a861ea4f8f0abc644047dbcfc623ee Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 23 Nov 2025 23:29:19 +0100 Subject: [PATCH 14/41] implicit lines --- Common/include/CConfig.hpp | 7 + .../include/geometry/CMultiGridGeometry.hpp | 12 + Common/src/CConfig.cpp | 3 + Common/src/geometry/CMultiGridGeometry.cpp | 300 ++++++++++++++++++ 4 files changed, 322 insertions(+) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 12f9017c83ec..9d847ae0990a 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -499,6 +499,7 @@ class CConfig { bool MG_Smooth_EarlyExit; /*!< \brief Enable early exit for MG smoothing. */ su2double MG_Smooth_Res_Threshold; /*!< \brief Residual reduction threshold for early exit. */ bool MG_Smooth_Output; /*!< \brief Output per-iteration multigrid smoothing info. */ + bool MG_Implicit_Lines; /*!< \\brief Enable implicit-lines agglomeration from walls. */ su2double MG_Smooth_Coeff; /*!< \brief Smoothing coefficient for multigrid correction smoothing. */ su2double *LocationStations; /*!< \brief Airfoil sections in wing slicing subroutine. */ @@ -3859,6 +3860,12 @@ class CConfig { */ bool GetMG_Smooth_Output() const { return MG_Smooth_Output; } + /*!\ + * \brief Get whether implicit-lines agglomeration is enabled. + * \return True if implicit-lines agglomeration is enabled. + */ + bool GetMG_Implicit_Lines() const { return MG_Implicit_Lines; } + /*! * \brief Get the smoothing coefficient for MG correction smoothing. * \return Smoothing coefficient. diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 5dba1dd4e3c4..4afac2a1eba8 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -28,6 +28,7 @@ #pragma once #include "CGeometry.hpp" +class CMultiGridQueue; /*! * \class CMultiGridGeometry @@ -66,6 +67,17 @@ class CMultiGridGeometry final : public CGeometry { void SetSuitableNeighbors(vector& Suitable_Indirect_Neighbors, unsigned long iPoint, unsigned long Index_CoarseCV, const CGeometry* fine_grid) const; + /*! + * \brief Agglomerate high-aspect ratio interior cells along implicit lines starting from wall vertices. + * \param[in,out] Index_CoarseCV - Reference to the current coarse control volume index that will be + * incremented as new coarse CVs are created. + * \param[in] fine_grid - Geometrical definition of the problem (fine grid). + * \param[in] config - Definition of the particular problem. + * \param[in,out] MGQueue_InnerCV - Queue used for STEP 2; agglomerated points will be removed from it. + */ + void AgglomerateImplicitLines(unsigned long &Index_CoarseCV, const CGeometry* fine_grid, const CConfig* config, + CMultiGridQueue &MGQueue_InnerCV); + /*! * \brief Compute surface straightness for multigrid geometry. * \param[in] config - Definition of the particular problem. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 886076bc6578..81c1a35a8910 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1963,6 +1963,9 @@ void CConfig::SetConfig_Options() { /*!\brief MG_SMOOTH_OUTPUT\n DESCRIPTION: Output per-iteration RMS for MG smoothing. DEFAULT: NO \ingroup Config*/ addBoolOption("MG_SMOOTH_OUTPUT", MG_Smooth_Output, false); + /*!\brief MG_IMPLICIT_LINES\n DESCRIPTION: Enable agglomeration along implicit lines from wall seeds. DEFAULT: NO \ingroup Config*/ + addBoolOption("MG_IMPLICIT_LINES", MG_Implicit_Lines, false); + /*!\brief MG_SMOOTH_COEFF\n DESCRIPTION: Smoothing coefficient for MG correction smoothing. DEFAULT: 1.25 \ingroup Config*/ addDoubleOption("MG_SMOOTH_COEFF", MG_Smooth_Coeff, 1.25); diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 2fb73749e8e5..a463ddf09b45 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -29,6 +29,11 @@ #include "../../include/geometry/CMultiGridQueue.hpp" #include "../../include/toolboxes/printing_toolbox.hpp" #include "../../../Common/include/toolboxes/geometry_toolbox.hpp" +#include +#include +#include +#include +#include /*--- Nijso says: this could perhaps be replaced by metis partitioning? ---*/ @@ -303,6 +308,14 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } + /*--- Agglomerate high-aspect-ratio interior nodes along implicit lines from walls. ---*/ + if (config->GetMG_Implicit_Lines()) { + AgglomerateImplicitLines(Index_CoarseCV, fine_grid, config, MGQueue_InnerCV); + } + + + + /*--- STEP 2: Agglomerate the domain points. ---*/ //cout << "*********** STEP 2 ***" << endl; auto iteration = 0ul; @@ -900,6 +913,293 @@ void CMultiGridGeometry::SetSuitableNeighbors(vector& Suitable_In // Suitable_Indirect_Neighbors.resize(it2 - Suitable_Indirect_Neighbors.begin()); } + +void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, const CGeometry* fine_grid, + const CConfig* config, CMultiGridQueue &MGQueue_InnerCV) { + /*--- Parameters ---*/ + const su2double angle_threshold_deg = 20.0; /* stop line if direction deviates more than this */ + const su2double pi = acos(-1.0); + const su2double cos_threshold = cos(angle_threshold_deg * pi / 180.0); + const unsigned long max_line_length = 12; /* maximum number of nodes on implicit line (including wall) */ + + const unsigned long nPoint = fine_grid->GetnPoint(); + vector reserved(nPoint, 0); + const bool debug = (std::getenv("SU2_MG_IMPLICIT_DEBUG") != nullptr); + if (debug) std::cout << "[MG_IMPLICIT] Starting AgglomerateImplicitLines: nPoint=" << nPoint + << " angle_thresh=" << angle_threshold_deg << " max_line_length=" << max_line_length << std::endl; + + /*--- Collect implicit lines starting at wall vertices ---*/ + vector> lines; // each line: [W, n1, n2, ...] + vector wall_nodes; // wall node for each line (index into lines) + for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { + /* skip non-wall markers (keep same condition as FindNormal_Neighbor) */ + if (config->GetMarker_All_KindBC(iMarker) == SEND_RECEIVE || + config->GetMarker_All_KindBC(iMarker) == INTERNAL_BOUNDARY || + config->GetMarker_All_KindBC(iMarker) == NEARFIELD_BOUNDARY) + continue; + cout << "We are on marker with name " << config->GetMarker_CfgFile_TagBound(iMarker) << " and kind " + << config->GetMarker_All_KindBC(iMarker) << endl; + + for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { + const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); + cout << "node number " << iPoint << endl; + ///* skip already agglomerated or non-domain points */ + //if (fine_grid->nodes->GetAgglomerate(iPoint) || !fine_grid->nodes->GetDomain(iPoint)) continue; + + /* get normal at the vertex; if not available skip */ + const long ChildVertex = fine_grid->nodes->GetVertex(iPoint, iMarker); + if (ChildVertex == -1) continue; + su2double Normal[MAXNDIM] = {0.0}; + fine_grid->vertex[iMarker][ChildVertex]->GetNormal(Normal); + + /* start building the implicit line */ + vector L; + L.push_back(iPoint); + + /* prev direction: use vertex normal (assumed inward) */ + vector prev_dir(nDim, 0.0); + for (unsigned i = 0; i < nDim; ++i) prev_dir[i] = Normal[i]; + su2double norm_prev = GeometryToolbox::Norm(nDim, prev_dir.data()); + if (norm_prev == 0.0) continue; + for (unsigned i = 0; i < nDim; ++i) prev_dir[i] /= norm_prev; + + unsigned long current = iPoint; + while (L.size() < max_line_length) { + /* find best interior neighbor aligned with prev_dir */ + su2double best_dot = -2.0; + unsigned long best_neighbor = ULONG_MAX; + for (auto jPoint : fine_grid->nodes->GetPoints(current)) { + if (jPoint == current) continue; + if (fine_grid->nodes->GetAgglomerate(jPoint)) { + if (debug) { + const auto parent = fine_grid->nodes->GetParent_CV(jPoint); + unsigned short nchild = 0; + if (parent != ULONG_MAX) nchild = nodes->GetnChildren_CV(parent); + std::cout << "[MG_IMPLICIT] Neighbor " << jPoint << " already agglomerated; parent=" << parent + << " nChildren=" << nchild << " children: "; + if (parent != ULONG_MAX) { + for (unsigned short ci = 0; ci < nchild; ++ci) { + std::cout << nodes->GetChildren_CV(parent, ci) << " "; + } + } + std::cout << std::endl; + + /*--- If parent has exactly two children, try to identify wall child and other child ---*/ + if (parent != ULONG_MAX && nchild == 2) { + unsigned long wall_child = ULONG_MAX; + unsigned long other_child = ULONG_MAX; + for (unsigned short ci = 0; ci < nchild; ++ci) { + const auto ch = nodes->GetChildren_CV(parent, ci); + if (fine_grid->nodes->GetBoundary(ch)) wall_child = ch; + else other_child = ch; + } + if (wall_child != ULONG_MAX && other_child != ULONG_MAX) { + std::cout << "[MG_IMPLICIT] node " << wall_child << " was agglomerated with node " << other_child + << ", node " << wall_child << " has " << nchild << " children" << std::endl; + } else { + std::cout << "[MG_IMPLICIT] parent " << parent << " children do not match expected (wall + child)" << std::endl; + } + } + } + } + else { + cout << "neighbor " << jPoint << " not agglomerated!!!!!!!!!!!!!!!!!!!" << endl; + } + if (!fine_grid->nodes->GetDomain(jPoint)) continue; + if (fine_grid->nodes->GetBoundary(jPoint)) continue; /* avoid boundary nodes */ + + /* compute direction */ + su2double vec[MAXNDIM] = {0.0}; + su2double len = 0.0; + for (auto d = 0u; d < nDim; ++d) { + vec[d] = fine_grid->nodes->GetCoord(jPoint, d) - fine_grid->nodes->GetCoord(current, d); + len += vec[d] * vec[d]; + } + if (len <= 0.0) continue; + len = sqrt(len); + for (auto d = 0u; d < nDim; ++d) vec[d] /= len; + + /* alignment with prev_dir */ + su2double dot = 0.0; + for (auto d = 0u; d < nDim; ++d) dot += vec[d] * prev_dir[d]; + if (dot > best_dot) { + best_dot = dot; + best_neighbor = jPoint; + } + } + + if (best_neighbor == ULONG_MAX) break; + if (best_dot < cos_threshold) break; /* deviation too large */ + + /* append neighbor */ + L.push_back(best_neighbor); + + /* update prev_dir */ + su2double dir_new[MAXNDIM] = {0.0}; + su2double len_new = 0.0; + for (auto d = 0u; d < nDim; ++d) { + dir_new[d] = fine_grid->nodes->GetCoord(best_neighbor, d) - fine_grid->nodes->GetCoord(current, d); + len_new += dir_new[d] * dir_new[d]; + } + if (len_new <= 0.0) break; + len_new = sqrt(len_new); + for (auto d = 0u; d < nDim; ++d) prev_dir[d] = dir_new[d] / len_new; + + current = best_neighbor; + } + + /* accept line only if it contains at least two interior nodes (i.e., length >= 3 incl. wall) */ + if (L.size() >= 3) { + if (debug) { + std::cout << "[MG_IMPLICIT] Found implicit line starting at wall " << iPoint << " : "; + for (auto pid : L) { + std::cout << pid << " "; + } + std::cout << " (len=" << L.size() << ")\n"; + } + lines.push_back(L); + wall_nodes.push_back(iPoint); + } + } + } + + if (lines.empty()) return; + + /*--- Advancing-front greedy pairing with cross-line merging: + For each pair position (first+second, then third+fourth...), attempt to + merge pairs across lines when their wall seeds share the same parent. + If no neighbor-line match exists, create a 2-child coarse CV for the pair. ---*/ + unsigned pair_idx = 0; + bool done_stage = false; + while (!done_stage) { + done_stage = true; + + // map wall parent -> list of line indices that start with a wall node having that parent + unordered_map> parent_to_lines; + parent_to_lines.reserve(lines.size() * 2); + for (unsigned long li = 0; li < lines.size(); ++li) { + const auto &L = lines[li]; + if (L.empty()) continue; + const auto W = L[0]; + const auto pW = fine_grid->nodes->GetParent_CV(W); + parent_to_lines[pW].push_back(li); + } + + // track which lines had a pair processed this stage + vector line_processed(lines.size(), 0); + + // First, attempt cross-line merges for parents that have >1 wall-line + for (auto &entry : parent_to_lines) { + const auto &line_ids = entry.second; + if (line_ids.size() < 2) continue; + + // pair up lines in consecutive order (0 with 1, 2 with 3, ...) + 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; // both must have the pair at this stage + + const auto a = L1[idx1]; + const auto b = L1[idx2]; + const auto c = L2[idx1]; + const auto d = L2[idx2]; + + // skip if any already agglomerated or reserved + 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 checks for all + if (!GeometricalCheck(a, fine_grid, config) || !GeometricalCheck(b, fine_grid, config) || + !GeometricalCheck(c, fine_grid, config) || !GeometricalCheck(d, fine_grid, config)) + continue; + + // create a new coarse control volume merging {a,b,c,d} + if (debug) { + std::cout << "[MG_IMPLICIT] Stage " << pair_idx << + ": CROSS-LINE merge creating coarse CV " << Index_CoarseCV << " from points "; + std::cout << a << "," << b << "," << c << "," << d << std::endl; + } + 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); + + Index_CoarseCV++; + line_processed[li1] = line_processed[li2] = 1; + done_stage = false; + } + } + + // Second, for remaining lines, create 2-child coarse CVs for that stage + 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; // no pair at this stage + + const auto a = L[idx1]; + const auto 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; + + if (debug) { + std::cout << "[MG_IMPLICIT] Stage " << pair_idx << ": 2-child merge creating coarse CV " << Index_CoarseCV + << " from points " << a << "," << b << std::endl; + } + + // 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); + + Index_CoarseCV++; + done_stage = false; + } + + pair_idx++; + // stop when no more pairs available at this stage across any line + bool any_more = false; + for (const auto &L : lines) { + if (L.size() > 1 + 2 * pair_idx) { + any_more = true; + break; + } + } + if (!any_more) break; + } + + /* NOTE: cross-line / neighbor-line merging (when wall nodes were agglomerated together) + is not implemented here yet. This function implements the advancing-front greedy + pairing along implicit lines as a first pass. */ +} + void CMultiGridGeometry::SetPoint_Connectivity(const CGeometry* fine_grid) { /*--- Temporary, CPoint (nodes) then compresses this structure. ---*/ vector > points(nPoint); From cbaa3f7b4095bdf3e75399e44c18d33a7f303fa3 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 24 Nov 2025 08:19:47 +0100 Subject: [PATCH 15/41] implicit lines info --- Common/include/CConfig.hpp | 7 +++++++ Common/src/CConfig.cpp | 3 +++ Common/src/geometry/CGeometry.cpp | 3 +-- Common/src/geometry/CMultiGridGeometry.cpp | 17 +---------------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 9d847ae0990a..5ccc6ab285ea 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -500,6 +500,7 @@ class CConfig { su2double MG_Smooth_Res_Threshold; /*!< \brief Residual reduction threshold for early exit. */ bool MG_Smooth_Output; /*!< \brief Output per-iteration multigrid smoothing info. */ bool MG_Implicit_Lines; /*!< \\brief Enable implicit-lines agglomeration from walls. */ + bool MG_Implicit_Debug; /*!< \brief Enable debug output for implicit-lines agglomeration. */ su2double MG_Smooth_Coeff; /*!< \brief Smoothing coefficient for multigrid correction smoothing. */ su2double *LocationStations; /*!< \brief Airfoil sections in wing slicing subroutine. */ @@ -3866,6 +3867,12 @@ class CConfig { */ bool GetMG_Implicit_Lines() const { return MG_Implicit_Lines; } + /*!\ + * \brief Get whether implicit-lines debug output is enabled. + * \return True if implicit-lines debug output is enabled. + */ + bool GetMG_Implicit_Debug() const { return MG_Implicit_Debug; } + /*! * \brief Get the smoothing coefficient for MG correction smoothing. * \return Smoothing coefficient. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 81c1a35a8910..063167511d53 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1966,6 +1966,9 @@ void CConfig::SetConfig_Options() { /*!\brief MG_IMPLICIT_LINES\n DESCRIPTION: Enable agglomeration along implicit lines from wall seeds. DEFAULT: NO \ingroup Config*/ addBoolOption("MG_IMPLICIT_LINES", MG_Implicit_Lines, false); + /*!\brief MG_IMPLICIT_DEBUG\n DESCRIPTION: Enable debug output for implicit-lines agglomeration. DEFAULT: NO \ingroup Config*/ + addBoolOption("MG_IMPLICIT_DEBUG", MG_Implicit_Debug, false); + /*!\brief MG_SMOOTH_COEFF\n DESCRIPTION: Smoothing coefficient for MG correction smoothing. DEFAULT: 1.25 \ingroup Config*/ addDoubleOption("MG_SMOOTH_COEFF", MG_Smooth_Coeff, 1.25); diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index 2dabe5de3b0a..6329f31e4f2c 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -3907,8 +3907,7 @@ void CGeometry::ColorMGLevels(unsigned short nMGLevels, const CGeometry* const* } else for (auto iPoint = 0ul; iPoint < coarseMesh->GetnPoint(); ++iPoint){ - //CoarseGridColor_(iPoint, step) = color[coarseMesh->nodes->GetParent_CV(iPoint)]; - CoarseGridColor_(iPoint, step) = coarseMesh->nodes->GetParent_CV(iPoint); + CoarseGridColor_(iPoint, step) = color[coarseMesh->nodes->GetParent_CV(iPoint)]; } } } diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index a463ddf09b45..cb77bb8a9f45 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -654,21 +654,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } - // print out all point connections - // for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) { - // cout << iPoint << ", parent=" << fine_grid->nodes->GetParent_CV(iPoint) << endl; - // } - - // // loop over coarse points - // for (auto iCoarsePoint = 0ul; iCoarsePoint < Index_CoarseCV; iCoarsePoint++) { - // cout << iCoarsePoint << endl; - // // print all fine-point children of the coarse point - // for (auto iChildren = 0u; iChildren < nodes->GetnChildren_CV(iCoarsePoint); iChildren++) { - // //const auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint, iChildren); - // cout << " " <GetChildren_CV(iCoarsePoint, iChildren) << endl; - // } - // } - edgeColorGroupSize = config->GetEdgeColoringGroupSize(); } @@ -924,7 +909,7 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, const unsigned long nPoint = fine_grid->GetnPoint(); vector reserved(nPoint, 0); - const bool debug = (std::getenv("SU2_MG_IMPLICIT_DEBUG") != nullptr); + const bool debug = config->GetMG_Implicit_Debug(); if (debug) std::cout << "[MG_IMPLICIT] Starting AgglomerateImplicitLines: nPoint=" << nPoint << " angle_thresh=" << angle_threshold_deg << " max_line_length=" << max_line_length << std::endl; From d28ce20d575a449d6b5d2e65702abf86f0844e75 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Wed, 26 Nov 2025 08:31:13 +0100 Subject: [PATCH 16/41] adaptive damping factor for pre-smoothing --- Common/include/CConfig.hpp | 3 + .../integration/CMultiGridIntegration.hpp | 9 +- .../src/integration/CMultiGridIntegration.cpp | 157 +++++++++++++++++- 3 files changed, 159 insertions(+), 10 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 5ccc6ab285ea..842fe28832de 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -6799,6 +6799,9 @@ class CConfig { */ su2double GetDamp_Res_Restric(void) const { return Damp_Res_Restric; } + /*!\n+ * \brief Set the damping factor for the residual restriction.\n+ * \param[in] val New value for the damping factor.\n+ */ + void SetDamp_Res_Restric(su2double val) { Damp_Res_Restric = val; } + /*! * \brief Value of the damping factor for the correction prolongation. * \return Value of the damping factor. diff --git a/SU2_CFD/include/integration/CMultiGridIntegration.hpp b/SU2_CFD/include/integration/CMultiGridIntegration.hpp index 471c1e1c58d6..07a4e61c07f5 100644 --- a/SU2_CFD/include/integration/CMultiGridIntegration.hpp +++ b/SU2_CFD/include/integration/CMultiGridIntegration.hpp @@ -68,7 +68,8 @@ class CMultiGridIntegration final : public CIntegration { void MultiGrid_Cycle(CGeometry ****geometry, CSolver *****solver_container, CNumerics ******numerics_container, CConfig **config, unsigned short iMesh, unsigned short mu, unsigned short RunTime_EqSystem, - unsigned short iZone, unsigned short iInst); + unsigned short iZone, unsigned short iInst, + unsigned short *lastPreSmoothIters); /*! * \brief Compute the forcing term. @@ -79,7 +80,8 @@ class CMultiGridIntegration final : public CIntegration { * \param[in] config - Definition of the particular problem. */ void SetForcing_Term(CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, - CGeometry *geo_coarse, CConfig *config, unsigned short iMesh); + CGeometry *geo_coarse, CConfig *config, unsigned short iMesh, + su2double val_factor = -1.0); /*! * \brief Add the truncation error to the residual. @@ -154,7 +156,8 @@ class CMultiGridIntegration final : public CIntegration { CConfig *config, unsigned short iMesh, unsigned short iZone, - unsigned short iRKLimit); + unsigned short iRKLimit, + unsigned short *lastPreSmoothIters); /*! * \brief Compute the fine grid correction from the coarse solution. diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index 78739a1481ab..0b0168257e23 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -29,6 +29,13 @@ #include "../../../Common/include/parallelization/omp_structure.hpp" #include "ComputeLinSysResRMS.hpp" +#include + +// Forward declaration of the adaptive damping helper so it can be used by +// MultiGrid_Cycle before the helper's definition further down the file. +static su2double Damp_Restric_Adapt(const unsigned short *lastPreSmoothIters, + unsigned short lastPreSmoothCount, + CConfig *config); CMultiGridIntegration::CMultiGridIntegration() : CIntegration() { } @@ -106,8 +113,16 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, /*--- Perform the Full Approximation Scheme multigrid ---*/ + // Allocate per-MG-run storage for the number of pre-smoothing iterations + const unsigned short nMGLevels = config[iZone]->GetnMGLevels(); + unsigned short *lastPreSmoothIters = new unsigned short[nMGLevels + 1]; + for (unsigned short ii = 0; ii <= nMGLevels; ++ii) lastPreSmoothIters[ii] = 0; + MultiGrid_Cycle(geometry, solver_container, numerics_container, config, - FinestMesh, RecursiveParam, RunTime_EqSystem, iZone, iInst); + FinestMesh, RecursiveParam, RunTime_EqSystem, iZone, iInst, + lastPreSmoothIters); + + delete [] lastPreSmoothIters; /*--- Computes primitive variables and gradients in the finest mesh (useful for the next solver (turbulence) and output ---*/ @@ -136,7 +151,8 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, unsigned short RecursiveParam, unsigned short RunTime_EqSystem, unsigned short iZone, - unsigned short iInst) { + unsigned short iInst, + unsigned short *lastPreSmoothIters) { CConfig* config = config_container[iZone]; @@ -157,7 +173,7 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, // standard multigrid: pre-smoothing // start with solution on fine grid h // apply nonlinear smoother (e.g. Gauss-Seidel) to system to damp high-frequency errors. - PreSmoothing(RunTime_EqSystem, geometry, solver_container, config_container, solver_fine, numerics_fine, geometry_fine, solver_container_fine, config, iMesh, iZone,iRKLimit); + PreSmoothing(RunTime_EqSystem, geometry, solver_container, config_container, solver_fine, numerics_fine, geometry_fine, solver_container_fine, config, iMesh, iZone, iRKLimit, lastPreSmoothIters); /*--- Compute Forcing Term $P_(k+1) = I^(k+1)_k(P_k+F_k(u_k))-F_(k+1)(I^(k+1)_k u_k)$ and update solution for multigrid ---*/ @@ -196,7 +212,10 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, /*--- Compute $P_(k+1) = I^(k+1)_k(r_k) - r_(k+1) ---*/ - SetForcing_Term(solver_fine, solver_coarse, geometry_fine, geometry_coarse, config, iMesh+1); + // Adapt damping based on recorded pre-smoothing iterations and apply to forcing term + cout << "calling damp_restric_Adapt" << endl; + su2double adapted_factor = Damp_Restric_Adapt(lastPreSmoothIters, config->GetnMGLevels() + 1, config); + SetForcing_Term(solver_fine, solver_coarse, geometry_fine, geometry_coarse, config, iMesh+1, adapted_factor); /*--- Restore the time integration settings. ---*/ @@ -213,7 +232,7 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, nextRecurseParam = 0; MultiGrid_Cycle(geometry, solver_container, numerics_container, config_container, - iMesh+1, nextRecurseParam, RunTime_EqSystem, iZone, iInst); + iMesh+1, nextRecurseParam, RunTime_EqSystem, iZone, iInst, lastPreSmoothIters); } /*--- 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))$ ---*/ @@ -242,7 +261,8 @@ CSolver** solver_container_fine, CConfig *config, unsigned short iMesh, unsigned short iZone, -unsigned short iRKLimit) { +unsigned short iRKLimit, +unsigned short *lastPreSmoothIters) { const bool classical_rk4 = (config->GetKind_TimeIntScheme() == CLASSICAL_RK4_EXPLICIT); const unsigned short nPreSmooth = config->GetMG_PreSmooth(iMesh); const unsigned long timeIter = config->GetTimeIter(); @@ -261,6 +281,7 @@ unsigned short iRKLimit) { } /*--- Do a presmoothing on the grid iMesh to be restricted to the grid iMesh+1 ---*/ + unsigned short actual_iterations = nPreSmooth; for (unsigned short iPreSmooth = 0; iPreSmooth < nPreSmooth; iPreSmooth++) { /*--- Time and space integration ---*/ for (unsigned short iRKStep = 0; iRKStep < iRKLimit; iRKStep++) { @@ -291,10 +312,14 @@ unsigned short iRKLimit) { if (output_enabled) { cout << "MG Pre-Smoothing Level " << iMesh << " Early exit at iteration " << iPreSmooth + 1 << endl; } + actual_iterations = iPreSmooth + 1; break; } } } + + // Record actual iterations performed for this MG level + if (lastPreSmoothIters != nullptr) lastPreSmoothIters[iMesh] = actual_iterations; } @@ -588,7 +613,8 @@ void CMultiGridIntegration::SetProlongated_Solution(unsigned short RunTime_EqSys } void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, - CGeometry *geo_coarse, CConfig *config, unsigned short iMesh) { + CGeometry *geo_coarse, CConfig *config, unsigned short iMesh, + su2double val_factor) { unsigned long Point_Fine, Point_Coarse, iVertex; unsigned short iMarker, iVar, iChildren; @@ -596,6 +622,7 @@ void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coar const unsigned short nVar = sol_coarse->GetnVar(); su2double factor = config->GetDamp_Res_Restric(); + if (val_factor > 0.0) factor = val_factor; auto *Residual = new su2double[nVar]; @@ -637,6 +664,122 @@ void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coar } +/* + * Damp_Restric_Adapt + * ------------------ + * Compute an adapted restriction damping factor (Damp_Res_Restric) based on the + * number of pre-smoothing iterations actually performed on each multigrid level. + * + * Arguments: + * - lastPreSmoothIters: array of length at least (nMGLevels+1) containing the + * recorded number of pre-smoothing iterations performed at each MG level. + * The convention used here is that index == level (0..nMGLevels). + * - lastPreSmoothCount: length of the array passed in (for safety checks). + * - config: the CConfig pointer used to read current settings (no write performed). + * + * Returns: + * - the adapted damping factor (may be equal to current factor if no change). + * + * Notes: + * - This routine performs MPI Allreduce operations to make the adapt decision + * globally consistent across ranks. + * - The function intentionally does NOT write back into CConfig; it returns the + * new factor so the caller can decide how to apply it (e.g., via a setter + * once available or by passing it into the next call). + */ +static su2double Damp_Restric_Adapt(const unsigned short *lastPreSmoothIters, + unsigned short lastPreSmoothCount, + CConfig *config) { + //cout << "starting Damp_Restric_Adapt" << endl; + if (config == nullptr) return 0.0; + + const unsigned short nMGLevels = config->GetnMGLevels(); + + // Safety: require the provided array to have at least nMGLevels+1 entries + if (lastPreSmoothIters == nullptr || lastPreSmoothCount < (nMGLevels + 1)) { + // Not enough information to adapt; return current factor unchanged + return config->GetDamp_Res_Restric(); + } + + int local_any_full = 0; // true if any inspected coarse level reached configured max + int local_all_one = 1; // true if all inspected coarse levels performed exactly 1 iter + int local_inspected = 0; // number of coarse levels that have recorded a pre-smooth value + + // Inspect all coarse-grid levels (levels 1..nMGLevels). Ignore entries + // that are still zero (not yet visited during this MG run). + for (unsigned short lvl = 1; lvl <= nMGLevels; ++lvl) { + const unsigned short performed = lastPreSmoothIters[lvl]; + const unsigned short configured = config->GetMG_PreSmooth(lvl); + //cout << " Level " << lvl << ": performed=" << performed + // << " configured=" << configured << endl; + if (performed == 0) continue; // skip un-inspected level + ++local_inspected; + if (performed >= configured) local_any_full = 1; + if (performed != 1) local_all_one = 0; + } + //cout << "local_inspected=" << local_inspected + // << " local_any_full=" << local_any_full + // << " local_all_one=" << local_all_one << endl; + + + // Debug output: local decision stats + //cout << "Damp_Restric_Adapt local: inspected=" << local_inspected + // << " any_full=" << local_any_full << " all_one=" << local_all_one << endl; + + // Make decision globally consistent across MPI ranks + int global_any_full = 0; + int global_all_one = 0; + int global_inspected = 0; + + // Sum inspected counts across ranks; if nobody inspected any levels, skip adaptation + SU2_MPI::Allreduce(&local_inspected, &global_inspected, 1, MPI_INT, MPI_SUM, SU2_MPI::GetComm()); + + if (global_inspected == 0) { + // No ranks have inspected coarse levels yet — do not adapt + return config->GetDamp_Res_Restric(); + } + + SU2_MPI::Allreduce(&local_any_full, &global_any_full, 1, MPI_INT, MPI_MAX, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(&local_all_one, &global_all_one, 1, MPI_INT, MPI_MIN, SU2_MPI::GetComm()); + + //cout << "Damp_Restric_Adapt global: inspected=" << global_inspected + // << " any_full=" << global_any_full << " all_one=" << global_all_one << endl; + + const su2double current = config->GetDamp_Res_Restric(); + //cout << "Current Damp_Res_Restric: " << current << endl; + su2double new_factor = current; + + const su2double scale_down = 0.95; + const su2double scale_up = 1.01; + const su2double clamp_min = 0.1; + const su2double clamp_max = 0.9; + + if (global_any_full) { + new_factor = current * scale_down; + } else if (global_all_one) { + new_factor = current * scale_up; + } else { + // no change + return current; + } + + // Clamp result + new_factor = std::min(std::max(new_factor, clamp_min), clamp_max); + + // Optionally log the change on all ranks (config controls verbosity) + if (config->GetMG_Smooth_Output()) { + cout << "Adaptive Damp_Res_Restric: " << current << " -> " << new_factor << endl; + } + // Persist the adapted factor into the runtime config so subsequent cycles + // observe the updated value. This is safe because the adapt decision was + // already made through global MPI reductions, so calling the setter on all + // ranks yields the same value everywhere. + config->SetDamp_Res_Restric(new_factor); + cout << "ending Damp_Restric_Adapt, damping = " <GetDamp_Res_Restric() << endl; + return new_factor; +} + + void CMultiGridIntegration::SetResidual_Term(CGeometry *geometry, CSolver *solver) { AD::StartNoSharedReading(); From 9f1934101c9f200c725e69bd8291a98313fd4355 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Thu, 27 Nov 2025 10:14:47 +0100 Subject: [PATCH 17/41] fix cross-lines in implicit line agglomeration --- Common/src/geometry/CMultiGridGeometry.cpp | 26 +++++++++++++++++++ SU2_CFD/include/solvers/CScalarSolver.inl | 2 +- .../src/integration/CMultiGridIntegration.cpp | 4 +-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index cb77bb8a9f45..c361d7816e6a 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -1106,6 +1106,30 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, !GeometricalCheck(c, fine_grid, config) || !GeometricalCheck(d, fine_grid, config)) continue; + + // Check for duplicate indices — guard against merging the same node twice. + if (a == b || a == c || a == d || b == c || b == d || c == d) { + if (debug) { + std::cout << "[MG_IMPLICIT] Duplicate indices detected in CROSS-LINE merge at stage " << pair_idx + << " lines " << li1 << "," << li2 << " : indices = " << a << "," << b << "," << c + << "," << d << "\n"; + std::cout << "[MG_IMPLICIT] Parents: " << fine_grid->nodes->GetParent_CV(a) << "," + << fine_grid->nodes->GetParent_CV(b) << "," << fine_grid->nodes->GetParent_CV(c) << "," + << fine_grid->nodes->GetParent_CV(d) << "\n"; + std::cout << "[MG_IMPLICIT] Reserved flags: " << reserved[a] << "," << reserved[b] << "," + << reserved[c] << "," << reserved[d] << "\n"; + } else { + std::cout << "[MG_IMPLICIT] Duplicate indices detected in CROSS-LINE merge; skipping merge for Index_CoarseCV=" + << Index_CoarseCV << "\n"; + } + + // Stop implicit-line agglomeration for other lines that share this parent + // so only one wall-line per parent is processed (prevents later conflicts). + for (auto other_li : entry.second) line_processed[other_li] = 1; + + continue; + } + // create a new coarse control volume merging {a,b,c,d} if (debug) { std::cout << "[MG_IMPLICIT] Stage " << pair_idx << @@ -1130,6 +1154,8 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, Index_CoarseCV++; line_processed[li1] = line_processed[li2] = 1; + // mark all other lines sharing this parent as processed (stop their implicit agglomeration) + for (auto other_li : line_ids) if (other_li != li1 && other_li != li2) line_processed[other_li] = 1; done_stage = false; } } diff --git a/SU2_CFD/include/solvers/CScalarSolver.inl b/SU2_CFD/include/solvers/CScalarSolver.inl index bcf9fae33602..556bdf2f88d4 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.inl +++ b/SU2_CFD/include/solvers/CScalarSolver.inl @@ -132,7 +132,7 @@ void CScalarSolver::Upwind_Residual(CGeometry* geometry, CSolver** /*--- Define booleans that are solver specific through CConfig's GlobalParams which have to be set in CFluidIteration * before calling these solver functions. ---*/ const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); - const bool muscl = config->GetMUSCL(); + const bool muscl = config->GetMUSCL() && (iMesh == MESH_0); const bool limiter = (config->GetKind_SlopeLimit() != LIMITER::NONE) && (config->GetInnerIter() <= config->GetLimiterIter()); diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index 0b0168257e23..e793c22b9527 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -213,7 +213,7 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, /*--- Compute $P_(k+1) = I^(k+1)_k(r_k) - r_(k+1) ---*/ // Adapt damping based on recorded pre-smoothing iterations and apply to forcing term - cout << "calling damp_restric_Adapt" << endl; + //cout << "calling damp_restric_Adapt" << endl; su2double adapted_factor = Damp_Restric_Adapt(lastPreSmoothIters, config->GetnMGLevels() + 1, config); SetForcing_Term(solver_fine, solver_coarse, geometry_fine, geometry_coarse, config, iMesh+1, adapted_factor); @@ -775,7 +775,7 @@ static su2double Damp_Restric_Adapt(const unsigned short *lastPreSmoothIters, // already made through global MPI reductions, so calling the setter on all // ranks yields the same value everywhere. config->SetDamp_Res_Restric(new_factor); - cout << "ending Damp_Restric_Adapt, damping = " <GetDamp_Res_Restric() << endl; + //cout << "ending Damp_Restric_Adapt, damping = " <GetDamp_Res_Restric() << endl; return new_factor; } From 691a73cac433517a3401d1df944c02991f01ac38 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Thu, 27 Nov 2025 22:17:58 +0100 Subject: [PATCH 18/41] Reduce MGLEVELS based on mesh size --- Common/include/CConfig.hpp | 9 ++++++ Common/src/CConfig.cpp | 5 ++++ SU2_CFD/src/drivers/CDriver.cpp | 50 +++++++++++++++++++++++++++++++++ config_template.cfg | 8 ++++++ 4 files changed, 72 insertions(+) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 842fe28832de..a7d5d03a8683 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -491,6 +491,8 @@ class CConfig { unsigned short nMG_PreSmooth, /*!< \brief Number of MG pre-smooth parameters found in config file. */ nMG_PostSmooth, /*!< \brief Number of MG post-smooth parameters found in config file. */ nMG_CorrecSmooth; /*!< \brief Number of MG correct-smooth parameters found in config file. */ + unsigned long MG_Min_MeshSize; /*!< \brief Minimum mesh size per coarsest level to allow another MG level. */ + short *FFD_Fix_IDir, *FFD_Fix_JDir, *FFD_Fix_KDir; /*!< \brief Exact sections. */ unsigned short *MG_PreSmooth, /*!< \brief Multigrid Pre smoothing. */ @@ -3873,6 +3875,13 @@ class CConfig { */ bool GetMG_Implicit_Debug() const { return MG_Implicit_Debug; } + /*!\ + * \brief Get the minimum mesh size threshold used to compute effective MG levels. + * \return Minimum mesh size per coarsest level. + */ + unsigned long GetMG_Min_MeshSize() const { return MG_Min_MeshSize; } + + /*! * \brief Get the smoothing coefficient for MG correction smoothing. * \return Smoothing coefficient. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 063167511d53..cfe014879eb5 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1969,6 +1969,11 @@ void CConfig::SetConfig_Options() { /*!\brief MG_IMPLICIT_DEBUG\n DESCRIPTION: Enable debug output for implicit-lines agglomeration. DEFAULT: NO \ingroup Config*/ addBoolOption("MG_IMPLICIT_DEBUG", MG_Implicit_Debug, false); + /*!\brief MG_MIN_MESHSIZE + \ DESCRIPTION: Minimum global mesh size (points) to allow another multigrid level. DEFAULT: 1000 \ingroup Config*/ + addUnsignedLongOption("MG_MIN_MESHSIZE", MG_Min_MeshSize, 1000); + + /*!\brief MG_SMOOTH_COEFF\n DESCRIPTION: Smoothing coefficient for MG correction smoothing. DEFAULT: 1.25 \ingroup Config*/ addDoubleOption("MG_SMOOTH_COEFF", MG_Smooth_Coeff, 1.25); diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 3ee24b90407d..93fb335e1baf 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -105,6 +105,33 @@ #endif #include +// Compute the runtime effective number of multigrid levels for a zone. +// This helper does the MPI Allreduce of the local point count and returns +// the effective number of MG levels using the same algorithm used elsewhere +// in the codebase. It returns 0 if multigrid is disabled by the user. +static unsigned short getEffectiveMGLevels(CConfig* config, unsigned long local_nPoint, unsigned short nDim, unsigned long &Global_nPointFine) { + const unsigned short user_req = config->GetnMGLevels(); + if (user_req == 0) return 0; // user explicitly disabled multigrid + + Global_nPointFine = 0ul; + SU2_MPI::Allreduce(&local_nPoint, &Global_nPointFine, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); + + const unsigned long mg_min = config->GetMG_Min_MeshSize(); + const unsigned short reduction = (nDim == 2) ? 4u : 8u; + + unsigned short computed_max_levels = 1; + if ((mg_min > 0) && (Global_nPointFine > mg_min)) { + const double ratio = static_cast(Global_nPointFine) / static_cast(mg_min); + const double levels = floor(log(ratio) / log(static_cast(reduction))); + computed_max_levels = (levels < 1.0) ? 1 : static_cast(levels); + } + + unsigned short effective = user_req; + if (computed_max_levels < effective) effective = computed_max_levels; + + return effective; +} + CDriver::CDriver(char* confFile, unsigned short val_nZone, SU2_Comm MPICommunicator, bool dummy_geo) : CDriverBase(confFile, val_nZone, MPICommunicator), StopCalc(false), fsi(false), fem_solver(false), dry_run(dummy_geo) { @@ -701,6 +728,29 @@ void CDriver::InitializeGeometryFVM(CConfig *config, CGeometry **&geometry) { nDim = geometry_aux->GetnDim(); + /*--- Compute effective MG levels from mesh size here so that we overwrite + the configured MG levels before any subsequent calls to + config->GetnMGLevels() (which are used to size arrays). ---*/ + { + const unsigned short user_req = config->GetnMGLevels(); + if (user_req != 0) { + unsigned long Global_nPointFine = 0ul; + const unsigned short effective = getEffectiveMGLevels(config, geometry_aux->GetnPoint(), nDim, Global_nPointFine); + + if (effective != user_req) { + if (rank == MASTER_NODE) { + const unsigned long mg_min = config->GetMG_Min_MeshSize(); + cout << "WARNING: MGLEVEL=" << user_req << " treated as maximum. " + << "Effective MG levels set to " << effective + << " (Global points=" << Global_nPointFine << ", MG_MIN_MESHSIZE=" << mg_min << ").\n"; + } + config->SetMGLevels(effective); + requestedMGlevels = config->GetnMGLevels(); + } + } + } + + /*--- Color the initial grid and set the send-receive domains (ParMETIS) ---*/ geometry_aux->SetColorGrid_Parallel(config); diff --git a/config_template.cfg b/config_template.cfg index 54c9a4c5aca5..aed2114d5bcc 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1606,6 +1606,14 @@ MG_DAMP_RESTRICTION= 0.75 % Damping factor for the correction prolongation MG_DAMP_PROLONGATION= 0.75 +% Minimum number of global mesh points (finest grid) used to decide +% whether additional multigrid levels are meaningful. During startup the +% code may compute an "effective" number of MG levels based on the global +% finest-grid point count and this threshold. Smaller meshes may reduce the +% effective MG depth automatically. Default = 1000 +MG_MIN_MESHSIZE= 1000 + + % -------------------------- MESH SMOOTHING -----------------------------% % % Before each computation, implicitly smooth the nodal coordinates From 85456c81f727324178e4a74e6a827a0ce27cab56 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Fri, 28 Nov 2025 17:45:57 +0100 Subject: [PATCH 19/41] change default nr of implicit lines --- Common/src/geometry/CMultiGridGeometry.cpp | 2 +- SU2_CFD/src/integration/CMultiGridIntegration.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index c361d7816e6a..3349ae4363d7 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -905,7 +905,7 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, const su2double angle_threshold_deg = 20.0; /* stop line if direction deviates more than this */ const su2double pi = acos(-1.0); const su2double cos_threshold = cos(angle_threshold_deg * pi / 180.0); - const unsigned long max_line_length = 12; /* maximum number of nodes on implicit line (including wall) */ + const unsigned long max_line_length = 20; /* maximum number of nodes on implicit line (including wall) */ const unsigned long nPoint = fine_grid->GetnPoint(); vector reserved(nPoint, 0); diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index e793c22b9527..7736da151f60 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -742,8 +742,6 @@ static su2double Damp_Restric_Adapt(const unsigned short *lastPreSmoothIters, SU2_MPI::Allreduce(&local_any_full, &global_any_full, 1, MPI_INT, MPI_MAX, SU2_MPI::GetComm()); SU2_MPI::Allreduce(&local_all_one, &global_all_one, 1, MPI_INT, MPI_MIN, SU2_MPI::GetComm()); - //cout << "Damp_Restric_Adapt global: inspected=" << global_inspected - // << " any_full=" << global_any_full << " all_one=" << global_all_one << endl; const su2double current = config->GetDamp_Res_Restric(); //cout << "Current Damp_Res_Restric: " << current << endl; @@ -751,8 +749,8 @@ static su2double Damp_Restric_Adapt(const unsigned short *lastPreSmoothIters, const su2double scale_down = 0.95; const su2double scale_up = 1.01; - const su2double clamp_min = 0.1; - const su2double clamp_max = 0.9; + const su2double clamp_min = 0.05; + const su2double clamp_max = 1.0; if (global_any_full) { new_factor = current * scale_down; From 99d6712d1d71a9d2c4e673e6225bf487a9f82c2b Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 29 Nov 2025 00:26:38 +0100 Subject: [PATCH 20/41] adaptive CFL on multigrid --- Common/src/geometry/CMultiGridGeometry.cpp | 2 +- .../src/integration/CMultiGridIntegration.cpp | 7 +- SU2_CFD/src/solvers/CSolver.cpp | 209 +++++++++--------- 3 files changed, 106 insertions(+), 112 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 3349ae4363d7..81b1436b22c5 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -624,7 +624,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if (iMesh != MESH_0) { //const su2double factor = 1.5; //nijso: too high const su2double factor = 1.1; - const su2double Coeff = pow(su2double(Global_nPointFine) / Global_nPointCoarse, 1.0 / nDim); + //const su2double Coeff = pow(su2double(Global_nPointFine) / Global_nPointCoarse, 1.0 / nDim); const su2double CFL = factor * config->GetCFL(iMesh - 1);// / Coeff; config->SetCFL(iMesh, CFL); } diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index 7736da151f60..e3cf70de3127 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -747,10 +747,11 @@ static su2double Damp_Restric_Adapt(const unsigned short *lastPreSmoothIters, //cout << "Current Damp_Res_Restric: " << current << endl; su2double new_factor = current; - const su2double scale_down = 0.95; + const su2double scale_down = 0.99; const su2double scale_up = 1.01; - const su2double clamp_min = 0.05; - const su2double clamp_max = 1.0; + const su2double clamp_min = 0.1; + // larger than this is bad for stability + const su2double clamp_max = 0.95; if (global_any_full) { new_factor = current * scale_down; diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 5e2d89509eb7..2b786c181a17 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1726,113 +1726,105 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, static bool reduceCFL, resetCFL, canIncrease; for (unsigned short iMesh = 0; iMesh <= config->GetnMGLevels(); iMesh++) { + if (iMesh == MESH_0) { - /* Store the mean flow, and turbulence solvers more clearly. */ - - CSolver *solverFlow = solver_container[iMesh][FLOW_SOL]; - CSolver *solverTurb = solver_container[iMesh][TURB_SOL]; - CSolver *solverSpecies = solver_container[iMesh][SPECIES_SOL]; - - /* Compute the reduction factor for CFLs on the coarse levels. */ + /* Store the mean flow, and turbulence solvers more clearly. */ - if (iMesh == MESH_0) { - MGFactor[iMesh] = 1.0; - } else { - const su2double CFLRatio = config->GetCFL(iMesh)/config->GetCFL(iMesh-1); - MGFactor[iMesh] = MGFactor[iMesh-1]*CFLRatio; - } + CSolver *solverFlow = solver_container[iMesh][FLOW_SOL]; + CSolver *solverTurb = solver_container[iMesh][TURB_SOL]; + CSolver *solverSpecies = solver_container[iMesh][SPECIES_SOL]; /* Check whether we achieved the requested reduction in the linear solver residual within the specified number of linear iterations. */ - su2double linResTurb = 0.0; - su2double linResSpecies = 0.0; - if ((iMesh == MESH_0) && solverTurb) linResTurb = solverTurb->GetResLinSolver(); - if ((iMesh == MESH_0) && solverSpecies) linResSpecies = solverSpecies->GetResLinSolver(); + su2double linResTurb = 0.0; + su2double linResSpecies = 0.0; + if (solverTurb) linResTurb = solverTurb->GetResLinSolver(); + if (solverSpecies) linResSpecies = solverSpecies->GetResLinSolver(); - /* Max linear residual between flow and turbulence/species transport. */ - const su2double linRes = max(solverFlow->GetResLinSolver(), max(linResTurb, linResSpecies)); + /* Max linear residual between flow and turbulence/species transport. */ + const su2double linRes = max(solverFlow->GetResLinSolver(), max(linResTurb, linResSpecies)); - /* Tolerance limited to an acceptable value. */ - const su2double linTol = max(acceptableLinTol, config->GetLinear_Solver_Error()); + /* Tolerance limited to an acceptable value. */ + const su2double linTol = max(acceptableLinTol, config->GetLinear_Solver_Error()); - /* Check that we are meeting our nonlinear residual reduction target - over time so that we do not get stuck in limit cycles, this is done - on the fine grid and applied to all others. */ + /* Check that we are meeting our nonlinear residual reduction target + over time so that we do not get stuck in limit cycles, this is done + on the fine grid and applied to all others. */ - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { /* Only the master thread updates the shared variables. */ + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + { /* Only the master thread updates the shared variables. */ - /* Check if we should decrease or if we can increase, the 20% is to avoid flip-flopping. */ - resetCFL = linRes > 0.99; - unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + /* Check if we should decrease or if we can increase, the 20% is to avoid flip-flopping. */ + resetCFL = linRes > 0.99; + unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); - /* only change CFL number when larger than starting iteration */ - reduceCFL = (linRes > 1.2*linTol) && (iter >= startingIter); + /* only change CFL number when larger than starting iteration */ + reduceCFL = (linRes > 1.2*linTol) && (iter >= startingIter); - canIncrease = (linRes < linTol) && (iter >= startingIter); + canIncrease = (linRes < linTol) && (iter >= startingIter); - if ((iMesh == MESH_0) && (Res_Count > 0)) { - Old_Func = New_Func; - if (NonLinRes_Series.empty()) NonLinRes_Series.resize(Res_Count,0.0); + if (Res_Count > 0) { + Old_Func = New_Func; + if (NonLinRes_Series.empty()) NonLinRes_Series.resize(Res_Count,0.0); - /* Sum the RMS residuals for all equations. */ + /* Sum the RMS residuals for all equations. */ - New_Func = 0.0; - unsigned short totalVars = 0; - for (unsigned short iVar = 0; iVar < solverFlow->GetnVar(); iVar++) { - New_Func += log10(solverFlow->GetRes_RMS(iVar)); - ++totalVars; - } - if ((iMesh == MESH_0) && solverTurb) { - for (unsigned short iVar = 0; iVar < solverTurb->GetnVar(); iVar++) { - New_Func += log10(solverTurb->GetRes_RMS(iVar)); + New_Func = 0.0; + unsigned short totalVars = 0; + for (unsigned short iVar = 0; iVar < solverFlow->GetnVar(); iVar++) { + New_Func += log10(solverFlow->GetRes_RMS(iVar)); ++totalVars; } - } - if ((iMesh == MESH_0) && solverSpecies) { - for (unsigned short iVar = 0; iVar < solverSpecies->GetnVar(); iVar++) { - New_Func += log10(solverSpecies->GetRes_RMS(iVar)); - ++totalVars; + if (solverTurb) { + for (unsigned short iVar = 0; iVar < solverTurb->GetnVar(); iVar++) { + New_Func += log10(solverTurb->GetRes_RMS(iVar)); + ++totalVars; + } } - } - New_Func /= totalVars; + if (solverSpecies) { + for (unsigned short iVar = 0; iVar < solverSpecies->GetnVar(); iVar++) { + New_Func += log10(solverSpecies->GetRes_RMS(iVar)); + ++totalVars; + } + } + New_Func /= totalVars; - /* Compute the difference in the nonlinear residuals between the - current and previous iterations, taking care with very low initial - residuals (due to initialization). */ + /* Compute the difference in the nonlinear residuals between the + current and previous iterations, taking care with very low initial + residuals (due to initialization). */ - if ((config->GetInnerIter() == 1) && (New_Func - Old_Func > 10)) { - Old_Func = New_Func; - } - NonLinRes_Series[NonLinRes_Counter] = New_Func - Old_Func; + if ((config->GetInnerIter() == 1) && (New_Func - Old_Func > 10)) { + Old_Func = New_Func; + } + NonLinRes_Series[NonLinRes_Counter] = New_Func - Old_Func; - /* Increment the counter, if we hit the max size, then start over. */ + /* Increment the counter, if we hit the max size, then start over. */ - NonLinRes_Counter++; - if (NonLinRes_Counter == Res_Count) NonLinRes_Counter = 0; + NonLinRes_Counter++; + if (NonLinRes_Counter == Res_Count) NonLinRes_Counter = 0; - /* Detect flip-flop convergence to reduce CFL and large increases - to reset to minimum value, in that case clear the history. */ + /* Detect flip-flop convergence to reduce CFL and large increases + to reset to minimum value, in that case clear the history. */ - if (config->GetInnerIter() >= Res_Count) { - unsigned long signChanges = 0; - su2double totalChange = 0.0; - auto prev = NonLinRes_Series.front(); - for (const auto& val : NonLinRes_Series) { - totalChange += val; - signChanges += (prev > 0) ^ (val > 0); - prev = val; - } - reduceCFL |= (signChanges > Res_Count/4) && (totalChange > -0.5); + if (config->GetInnerIter() >= Res_Count) { + unsigned long signChanges = 0; + su2double totalChange = 0.0; + auto prev = NonLinRes_Series.front(); + for (const auto& val : NonLinRes_Series) { + totalChange += val; + signChanges += (prev > 0) ^ (val > 0); + prev = val; + } + reduceCFL |= (signChanges > Res_Count/4) && (totalChange > -0.5); - if (totalChange > 2.0) { // orders of magnitude - resetCFL = true; - NonLinRes_Counter = 0; - for (auto& val : NonLinRes_Series) val = 0.0; + if (totalChange > 2.0) { // orders of magnitude + resetCFL = true; + NonLinRes_Counter = 0; + for (auto& val : NonLinRes_Series) val = 0.0; + } } } - } } /* End safe global access, now all threads update the CFL number. */ END_SU2_OMP_SAFE_GLOBAL_ACCESS @@ -1843,7 +1835,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, const su2double CFLSpeciesReduction = config->GetCFLRedCoeff_Species(); SU2_OMP_MASTER - if ((iMesh == MESH_0) && fullComms) { + if (fullComms) { Min_CFL_Local = 1e30; Max_CFL_Local = 0.0; Avg_CFL_Local = 0.0; @@ -1864,7 +1856,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, su2double underRelaxationFlow = solverFlow->GetNodes()->GetUnderRelaxation(iPoint); su2double underRelaxationTurb = 1.0; - if ((iMesh == MESH_0) && solverTurb) + if (solverTurb) underRelaxationTurb = solverTurb->GetNodes()->GetUnderRelaxation(iPoint); const su2double underRelaxation = min(underRelaxationFlow,underRelaxationTurb); @@ -1886,10 +1878,12 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, if (CFL*CFLFactor <= CFLMin) { CFL = CFLMin; - CFLFactor = MGFactor[iMesh]; + // nijso says: we might need to add this back in again when we have more complex + // cfl scaling for multigrid (happening in CMultiGridGeometry.cpp) + //CFLFactor = MGFactor[iMesh]; } else if (CFL*CFLFactor >= CFLMax) { CFL = CFLMax; - CFLFactor = MGFactor[iMesh]; + //CFLFactor = MGFactor[iMesh]; } /* If we detect a stalled nonlinear residual, then force the CFL @@ -1897,23 +1891,26 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, if (resetCFL) { CFL = CFLMin; - CFLFactor = MGFactor[iMesh]; + //CFLFactor = MGFactor[iMesh]; } /* Apply the adjustment to the CFL and store local values. */ CFL *= CFLFactor; + + //cout << "CFL = " << CFL << " Factor = " << CFLFactor << endl; + solverFlow->GetNodes()->SetLocalCFL(iPoint, CFL); - if ((iMesh == MESH_0) && solverTurb) { + if (solverTurb) { solverTurb->GetNodes()->SetLocalCFL(iPoint, CFL * CFLTurbReduction); } - if ((iMesh == MESH_0) && solverSpecies) { + if (solverSpecies) { solverSpecies->GetNodes()->SetLocalCFL(iPoint, CFL * CFLSpeciesReduction); } /* Store min and max CFL for reporting on the fine grid. */ - if ((iMesh == MESH_0) && fullComms) { + if (fullComms) { myCFLMin = min(CFL,myCFLMin); myCFLMax = max(CFL,myCFLMax); myCFLSum += CFL; @@ -1924,7 +1921,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Reduce the min/max/avg local CFL numbers. */ - if ((iMesh == MESH_0) && fullComms) { + if (fullComms) { SU2_OMP_CRITICAL { /* OpenMP reduction. */ Min_CFL_Local = min(Min_CFL_Local,myCFLMin); @@ -1942,32 +1939,28 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, Avg_CFL_Local /= su2double(geometry[iMesh]->GetGlobal_nPointDomain()); /*--- Update the config CFL for MESH_0 with the average adapted value. ---*/ - config->SetCFL(MESH_0, Avg_CFL_Local); - - /*--- Update coarse mesh CFL values using the same scaling as in CMultiGridGeometry.cpp. - This ensures coarse meshes follow the adaptive CFL changes from the fine mesh. ---*/ - const unsigned short nMGLevels = config->GetnMGLevels(); - const unsigned short nDim = geometry[MESH_0]->GetnDim(); - - for (unsigned short iMGLevel = 1; iMGLevel <= nMGLevels; iMGLevel++) { - /*--- Get coarse/fine point counts for this level. ---*/ - const su2double nPointCoarse = su2double(geometry[iMGLevel]->GetGlobal_nPointDomain()); - const su2double nPointFine = su2double(geometry[iMGLevel-1]->GetGlobal_nPointDomain()); - - /*--- Apply the same scaling factor as used during geometry construction. ---*/ - const su2double factor = 1.1; - const su2double Coeff = pow(nPointFine / nPointCoarse, 1.0 / nDim); - const su2double CFL_Coarse = factor * config->GetCFL(iMGLevel - 1);// / Coeff; - cout << "Adapted CFL for MG Level " << iMGLevel << ": " << CFL_Coarse << endl; - // nijso: comment this if we do not adapt the coarse level CFLs - //config->SetCFL(iMGLevel, CFL_Coarse); - } + cout << "imesh="<< iMesh <<"Avg CFL="<SetCFL(iMesh, Avg_CFL_Local); } END_SU2_OMP_SAFE_GLOBAL_ACCESS } + } else { + const su2double factor = 1.1; + const su2double CFL = factor * config->GetCFL(iMesh - 1);// / Coeff; + cout << "imesh="<< iMesh <<"Avg CFL="<SetCFL(iMesh, CFL); } +} + + //for (unsigned short iMesh = 1; iMesh <= config->GetnMGLevels(); iMesh++) { + // const su2double CFLRatio = config->GetCFL(iMesh)/config->GetCFL(iMesh-1); + // MGFactor[iMesh] = MGFactor[iMesh-1]*CFLRatio; + // cout << " CFL Level " << iMesh << " initial: " << config->GetCFL(iMesh)<< endl; + // cout << " CFL Level " << iMesh-1 << " initial: " << config->GetCFL(iMesh-1) << endl; + } void CSolver::SetResidual_RMS(const CGeometry *geometry, const CConfig *config) { From 2c40c9cb26f87f838b2fef5c1fd1bd5e9a039666 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 29 Nov 2025 00:34:29 +0100 Subject: [PATCH 21/41] run precommit --- .../include/geometry/CMultiGridGeometry.hpp | 5 +- Common/src/geometry/CGeometry.cpp | 2 +- Common/src/geometry/CMultiGridGeometry.cpp | 248 ++++++++---------- 3 files changed, 118 insertions(+), 137 deletions(-) diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 4afac2a1eba8..857f1b9c41c7 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -75,8 +75,8 @@ class CMultiGridGeometry final : public CGeometry { * \param[in] config - Definition of the particular problem. * \param[in,out] MGQueue_InnerCV - Queue used for STEP 2; agglomerated points will be removed from it. */ - void AgglomerateImplicitLines(unsigned long &Index_CoarseCV, const CGeometry* fine_grid, const CConfig* config, - CMultiGridQueue &MGQueue_InnerCV); + void AgglomerateImplicitLines(unsigned long& Index_CoarseCV, const CGeometry* fine_grid, const CConfig* config, + CMultiGridQueue& MGQueue_InnerCV); /*! * \brief Compute surface straightness for multigrid geometry. @@ -172,5 +172,4 @@ class CMultiGridGeometry final : public CGeometry { * \param[in] val_marker - Index of the boundary marker. */ void SetMultiGridWallTemperature(const CGeometry* fine_grid, unsigned short val_marker) override; - }; diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index f771d837dba5..cd19a0297c57 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -3906,7 +3906,7 @@ void CGeometry::ColorMGLevels(unsigned short nMGLevels, const CGeometry* const* CoarseGridColor_(iPoint, step) = CoarseGridColor_(coarseMesh->nodes->GetParent_CV(iPoint), step - 1); } else - for (auto iPoint = 0ul; iPoint < coarseMesh->GetnPoint(); ++iPoint){ + for (auto iPoint = 0ul; iPoint < coarseMesh->GetnPoint(); ++iPoint) { CoarseGridColor_(iPoint, step) = color[coarseMesh->nodes->GetParent_CV(iPoint)]; } } diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 81b1436b22c5..7afcaf41ce6f 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -35,16 +35,17 @@ #include #include - /*--- Nijso says: this could perhaps be replaced by metis partitioning? ---*/ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, unsigned short iMesh) : CGeometry() { nDim = fine_grid->GetnDim(); // Write the number of dimensions of the coarse grid. /*--- Maximum agglomeration size in 2D is 4 nodes, in 3D is 8 nodes. ---*/ - const short int maxAgglomSize=4; + const short int maxAgglomSize = 4; /*--- Compute surface straightness to determine straight boundaries ---*/ - if (iMesh == MESH_0) ComputeSurfStraightness(config); - else boundIsStraight = fine_grid->boundIsStraight; + if (iMesh == MESH_0) + ComputeSurfStraightness(config); + else + boundIsStraight = fine_grid->boundIsStraight; /*--- Agglomeration Scheme II (Nishikawa, Diskin, Thomas) Create a queue system to do the agglomeration @@ -84,9 +85,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - /*--- If the element has not been previously agglomerated and it - belongs to this physical domain, and it meets the geometrical - criteria, the agglomeration is studied. ---*/ + /*--- If the element has not been previously agglomerated and it + belongs to this physical domain, and it meets the geometrical + criteria, the agglomeration is studied. ---*/ vector marker_seed; if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint)) && @@ -110,7 +111,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un that are in that point ---*/ for (auto jMarker = 0u; jMarker < fine_grid->GetnMarker(); jMarker++) { - const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); //fine_grid->GetMarker_Tag(jMarker); + const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); // fine_grid->GetMarker_Tag(jMarker); if (fine_grid->nodes->GetVertex(iPoint, jMarker) != -1) { copy_marker[counter] = jMarker; counter++; @@ -133,11 +134,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un // if the child is only on this same valley, we set it to true as well. agglomerate_seed = true; /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ - //if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; - if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL && !boundIsStraight[marker_seed[0]]) agglomerate_seed = false; + // if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; + if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL && !boundIsStraight[marker_seed[0]]) + agglomerate_seed = false; /*--- Note that if the marker is a SEND_RECEIVE, then the node is actually an interior point. In that case it can only be agglomerated with another interior point. ---*/ - } /*--- If there are two markers, we will agglomerate if any of the @@ -162,9 +163,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- In 2D, corners are not agglomerated, but in 3D counter=2 means we are on the edge of a 2D face. In that case, agglomerate if both nodes are the same. ---*/ - //if (nDim == 2) agglomerate_seed = false; - - + // if (nDim == 2) agglomerate_seed = false; } /*--- If there are more than 2 markers, the aglomeration will be discarded ---*/ @@ -173,11 +172,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un // note that if one of the markers is SEND_RECEIVE, then we could allow agglomeration since // the real number of markers is then 2. - /*--- If the seed (parent) can be agglomerated, we try to agglomerate connected childs to the parent ---*/ /*--- Note that in 2D we allow a maximum of 4 nodes to be agglomerated ---*/ if (agglomerate_seed) { - //cout << " seed can be agglomerated to more points." << endl; + // cout << " seed can be agglomerated to more points." << endl; /*--- Now we do a sweep over all the nodes that surround the seed point ---*/ for (auto CVPoint : fine_grid->nodes->GetPoints(iPoint)) { @@ -191,7 +189,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- The new point can be agglomerated ---*/ if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { - //cout << " agglomerate " << CVPoint << " with seed point "<< iPoint << endl; + // cout << " agglomerate " << CVPoint << " with seed point "<< iPoint << endl; /*--- We set the value of the parent ---*/ fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); @@ -201,16 +199,14 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; /*--- In 2D, we agglomerate exactly 2 nodes if the nodes are on the line edge. ---*/ - if ((nDim==2) && (counter==1)) break; + if ((nDim == 2) && (counter == 1)) break; /*--- In 3D, we agglomerate exactly 2 nodes if the nodes are on the surface edge. ---*/ - if ((nDim==3) && (counter==2)) break; + if ((nDim == 3) && (counter == 2)) break; /*--- Apply maxAgglomSize limit for 3D internal boundary face nodes (counter==1 in 3D). ---*/ - if (nChildren==maxAgglomSize) break; - + if (nChildren == maxAgglomSize) break; } } - /*--- Only take into account indirect neighbors for 3D faces, not 2D. ---*/ if (nDim == 3) { Suitable_Indirect_Neighbors.clear(); @@ -228,7 +224,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un // << endl; /*--- The new point can be agglomerated ---*/ if (SetBoundAgglomeration(CVPoint, marker_seed, fine_grid, config)) { - //cout << " Boundary: indirect neighbor " << CVPoint << " can be agglomerated." << endl; + // cout << " Boundary: indirect neighbor " << CVPoint << " can be agglomerated." << endl; /*--- We set the value of the parent ---*/ fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); @@ -241,14 +237,13 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un nodes->SetChildren_CV(Index_CoarseCV, nChildren, CVPoint); nChildren++; /*--- Apply maxAgglomSize limit for 3D internal boundary face nodes. ---*/ - if (nChildren==maxAgglomSize) break; + if (nChildren == maxAgglomSize) break; } } } } - - /*--- At this stage we can check if the node is an isolated node. ---*/ + /*--- At this stage we can check if the node is an isolated node. ---*/ /*--- Update the number of children of the coarse control volume. ---*/ @@ -264,10 +259,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - //cout << "point " << iPoint << ", parent = " << fine_grid->nodes->GetParent_CV(iPoint) - // << " " << fine_grid->nodes->GetAgglomerate(iPoint) << endl; + // cout << "point " << iPoint << ", parent = " << fine_grid->nodes->GetParent_CV(iPoint) + // << " " << fine_grid->nodes->GetAgglomerate(iPoint) << endl; if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint))) { - //cout << " Boundary:mark left-over nodes " << endl; + // cout << " Boundary:mark left-over nodes " << endl; fine_grid->nodes->SetParent_CV(iPoint, Index_CoarseCV); nodes->SetChildren_CV(Index_CoarseCV, 0, iPoint); nodes->SetnChildren_CV(Index_CoarseCV, 1); @@ -276,7 +271,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } - /* --- Note that we can check index_coarse_cv for nr of children ---*/ // for (auto icoarse_cv = 0u; icoarse_cv < Index_CoarseCV; icoarse_cv++) { // cout << "coarse node " << icoarse_cv << ", children = " << nodes->GetnChildren_CV(icoarse_cv) << " " << endl; @@ -286,7 +280,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un // for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { // for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { // const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - // cout << "ipoint = " << iPoint << " , " << nodes->GetnChildren_CV(iPoint) << " " << fine_grid->nodes->GetParent_CV(iPoint) << endl; + // cout << "ipoint = " << iPoint << " , " << nodes->GetnChildren_CV(iPoint) << " " << + // fine_grid->nodes->GetParent_CV(iPoint) << endl; // } // } @@ -313,11 +308,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un AgglomerateImplicitLines(Index_CoarseCV, fine_grid, config, MGQueue_InnerCV); } - - - /*--- STEP 2: Agglomerate the domain points. ---*/ - //cout << "*********** STEP 2 ***" << endl; + // cout << "*********** STEP 2 ***" << endl; auto iteration = 0ul; while (!MGQueue_InnerCV.EmptyQueue() && (iteration < fine_grid->GetnPoint())) { const auto iPoint = MGQueue_InnerCV.NextCV(); @@ -329,7 +321,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if ((!fine_grid->nodes->GetAgglomerate(iPoint)) && (fine_grid->nodes->GetDomain(iPoint)) && (GeometricalCheck(iPoint, fine_grid, config))) { unsigned short nChildren = 1; - //cout << "***** internal seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " << fine_grid->nodes->GetCoord(iPoint, 1) << endl; + // cout << "***** internal seed point " << iPoint << ", coord = " << fine_grid->nodes->GetCoord(iPoint, 0) << " " + // << fine_grid->nodes->GetCoord(iPoint, 1) << endl; /*--- We set an index for the parent control volume ---*/ @@ -352,7 +345,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if ((!fine_grid->nodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint)) && (GeometricalCheck(CVPoint, fine_grid, config))) { /*--- We set the value of the parent ---*/ - //cout << "agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; + // cout << "agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); /*--- We set the value of the child ---*/ @@ -365,7 +358,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un MGQueue_InnerCV.Update(CVPoint, fine_grid); } - if (nChildren==maxAgglomSize) break; + if (nChildren == maxAgglomSize) break; } /*--- Identify the indirect neighbors ---*/ @@ -377,12 +370,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Now we do a sweep over all the indirect nodes that can be added ---*/ for (auto CVPoint : Suitable_Indirect_Neighbors) { - // if we have reached the maximum, get out. - if (nChildren==maxAgglomSize) break; + if (nChildren == maxAgglomSize) break; /*--- The new point can be agglomerated ---*/ if ((!fine_grid->nodes->GetAgglomerate(CVPoint)) && (fine_grid->nodes->GetDomain(CVPoint))) { - //cout << "indirect agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; + // cout << "indirect agglomerate " << CVPoint << " to internal seed point " << iPoint << endl; /*--- We set the value of the parent ---*/ fine_grid->nodes->SetParent_CV(CVPoint, Index_CoarseCV); @@ -449,8 +441,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- If the total would exceed maxAgglomSize, try to redistribute children to neighbors ---*/ if (nChildren_Total > maxAgglomSize) { - cout << " Merging isolated point " << iCoarsePoint - << " to point " << iCoarsePoint_Complete + cout << " Merging isolated point " << iCoarsePoint << " to point " << iCoarsePoint_Complete << " would exceed limit (" << nChildren_Total << " > " << maxAgglomSize << ")" << endl; /*--- Find neighbors of the target coarse point that have room ---*/ @@ -461,11 +452,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un auto nChildren_Neighbor = nodes->GetnChildren_CV(jCoarsePoint); if (nChildren_Neighbor < maxAgglomSize) { - unsigned short nCanTransfer = min(nChildrenToRedistribute, - static_cast(maxAgglomSize - nChildren_Neighbor)); + unsigned short nCanTransfer = + min(nChildrenToRedistribute, static_cast(maxAgglomSize - nChildren_Neighbor)); - cout << " Redistributing " << nCanTransfer << " children from point " - << iCoarsePoint_Complete << " to neighbor " << jCoarsePoint << endl; + cout << " Redistributing " << nCanTransfer << " children from point " << iCoarsePoint_Complete + << " to neighbor " << jCoarsePoint << endl; /*--- Transfer children from target to neighbor ---*/ for (unsigned short iTransfer = 0; iTransfer < nCanTransfer; iTransfer++) { @@ -508,8 +499,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un nodes->SetnChildren_CV(iCoarsePoint_Complete, nChildren); nodes->SetnChildren_CV(iCoarsePoint, 0); - cout << " Final: point " << iCoarsePoint_Complete - << " has " << nChildren << " children" << endl; + cout << " Final: point " << iCoarsePoint_Complete << " has " << nChildren << " children" << endl; } } @@ -622,10 +612,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un SetGlobal_nPointDomain(Global_nPointCoarse); if (iMesh != MESH_0) { - //const su2double factor = 1.5; //nijso: too high + // const su2double factor = 1.5; //nijso: too high const su2double factor = 1.1; - //const su2double Coeff = pow(su2double(Global_nPointFine) / Global_nPointCoarse, 1.0 / nDim); - const su2double CFL = factor * config->GetCFL(iMesh - 1);// / Coeff; + // const su2double Coeff = pow(su2double(Global_nPointFine) / Global_nPointCoarse, 1.0 / nDim); + const su2double CFL = factor * config->GetCFL(iMesh - 1); // / Coeff; config->SetCFL(iMesh, CFL); } @@ -654,11 +644,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } - edgeColorGroupSize = config->GetEdgeColoringGroupSize(); } - bool CMultiGridGeometry::GeometricalCheck(unsigned long iPoint, const CGeometry* fine_grid, const CConfig* config) const { su2double max_dimension = 1.2; @@ -695,12 +683,10 @@ bool CMultiGridGeometry::GeometricalCheck(unsigned long iPoint, const CGeometry* return (Stretching && Volume); } -void CMultiGridGeometry::ComputeSurfStraightness(CConfig* config) { - CGeometry::ComputeSurfStraightness(config, true); -} +void CMultiGridGeometry::ComputeSurfStraightness(CConfig* config) { CGeometry::ComputeSurfStraightness(config, true); } -bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector marker_seed, const CGeometry* fine_grid, - const CConfig* config) const { +bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector marker_seed, + const CGeometry* fine_grid, const CConfig* config) const { bool agglomerate_CV = false; /*--- Basic condition, the point has not been previously agglomerated, it belongs to the domain, @@ -735,7 +721,7 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectorGetMarker_All_KindBC(marker_seed[0]) == SEND_RECEIVE)) { if (copy_marker[0] == marker_seed[1]) { agglomerate_CV = true; @@ -750,7 +736,6 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vectorGetMarker_All_KindBC(marker_seed[0]) == SYMMETRY_PLANE) || // (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL)) { // if (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) { @@ -762,8 +747,6 @@ bool CMultiGridGeometry::SetBoundAgglomeration(unsigned long CVPoint, vector& Suitable_Indirect_Neighbors, unsigned long iPoint, unsigned long Index_CoarseCV, const CGeometry* fine_grid) const { /*--- Create a list with the first neighbors, including the seed. ---*/ @@ -823,8 +803,8 @@ void CMultiGridGeometry::SetSuitableNeighbors(vector& Suitable_In auto end = First_Neighbor_Points.end(); if (find(First_Neighbor_Points.begin(), end, kPoint) == end) { - Second_Neighbor_Points.push_back(kPoint); //neighbor of a neighbor, not connected to original ipoint - Second_Origin_Points.push_back(jPoint); // the neighbor that is connected to ipoint + Second_Neighbor_Points.push_back(kPoint); // neighbor of a neighbor, not connected to original ipoint + Second_Origin_Points.push_back(jPoint); // the neighbor that is connected to ipoint } } } @@ -898,9 +878,8 @@ void CMultiGridGeometry::SetSuitableNeighbors(vector& Suitable_In // Suitable_Indirect_Neighbors.resize(it2 - Suitable_Indirect_Neighbors.begin()); } - -void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, const CGeometry* fine_grid, - const CConfig* config, CMultiGridQueue &MGQueue_InnerCV) { +void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long& Index_CoarseCV, const CGeometry* fine_grid, + const CConfig* config, CMultiGridQueue& MGQueue_InnerCV) { /*--- Parameters ---*/ const su2double angle_threshold_deg = 20.0; /* stop line if direction deviates more than this */ const su2double pi = acos(-1.0); @@ -910,26 +889,27 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, const unsigned long nPoint = fine_grid->GetnPoint(); vector reserved(nPoint, 0); const bool debug = config->GetMG_Implicit_Debug(); - if (debug) std::cout << "[MG_IMPLICIT] Starting AgglomerateImplicitLines: nPoint=" << nPoint - << " angle_thresh=" << angle_threshold_deg << " max_line_length=" << max_line_length << std::endl; + if (debug) + std::cout << "[MG_IMPLICIT] Starting AgglomerateImplicitLines: nPoint=" << nPoint + << " angle_thresh=" << angle_threshold_deg << " max_line_length=" << max_line_length << std::endl; /*--- Collect implicit lines starting at wall vertices ---*/ - vector> lines; // each line: [W, n1, n2, ...] - vector wall_nodes; // wall node for each line (index into lines) + vector> lines; // each line: [W, n1, n2, ...] + vector wall_nodes; // wall node for each line (index into lines) for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { /* skip non-wall markers (keep same condition as FindNormal_Neighbor) */ if (config->GetMarker_All_KindBC(iMarker) == SEND_RECEIVE || config->GetMarker_All_KindBC(iMarker) == INTERNAL_BOUNDARY || config->GetMarker_All_KindBC(iMarker) == NEARFIELD_BOUNDARY) continue; - cout << "We are on marker with name " << config->GetMarker_CfgFile_TagBound(iMarker) << " and kind " + cout << "We are on marker with name " << config->GetMarker_CfgFile_TagBound(iMarker) << " and kind " << config->GetMarker_All_KindBC(iMarker) << endl; for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); cout << "node number " << iPoint << endl; ///* skip already agglomerated or non-domain points */ - //if (fine_grid->nodes->GetAgglomerate(iPoint) || !fine_grid->nodes->GetDomain(iPoint)) continue; + // if (fine_grid->nodes->GetAgglomerate(iPoint) || !fine_grid->nodes->GetDomain(iPoint)) continue; /* get normal at the vertex; if not available skip */ const long ChildVertex = fine_grid->nodes->GetVertex(iPoint, iMarker); @@ -955,41 +935,43 @@ 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->GetAgglomerate(jPoint)) { - if (debug) { - const auto parent = fine_grid->nodes->GetParent_CV(jPoint); - unsigned short nchild = 0; - if (parent != ULONG_MAX) nchild = nodes->GetnChildren_CV(parent); - std::cout << "[MG_IMPLICIT] Neighbor " << jPoint << " already agglomerated; parent=" << parent - << " nChildren=" << nchild << " children: "; - if (parent != ULONG_MAX) { - for (unsigned short ci = 0; ci < nchild; ++ci) { - std::cout << nodes->GetChildren_CV(parent, ci) << " "; - } + if (fine_grid->nodes->GetAgglomerate(jPoint)) { + if (debug) { + const auto parent = fine_grid->nodes->GetParent_CV(jPoint); + unsigned short nchild = 0; + if (parent != ULONG_MAX) nchild = nodes->GetnChildren_CV(parent); + std::cout << "[MG_IMPLICIT] Neighbor " << jPoint << " already agglomerated; parent=" << parent + << " nChildren=" << nchild << " children: "; + if (parent != ULONG_MAX) { + for (unsigned short ci = 0; ci < nchild; ++ci) { + std::cout << nodes->GetChildren_CV(parent, ci) << " "; + } + } + std::cout << std::endl; + + /*--- If parent has exactly two children, try to identify wall child and other child ---*/ + if (parent != ULONG_MAX && nchild == 2) { + unsigned long wall_child = ULONG_MAX; + unsigned long other_child = ULONG_MAX; + for (unsigned short ci = 0; ci < nchild; ++ci) { + const auto ch = nodes->GetChildren_CV(parent, ci); + if (fine_grid->nodes->GetBoundary(ch)) + wall_child = ch; + else + other_child = ch; } - std::cout << std::endl; - - /*--- If parent has exactly two children, try to identify wall child and other child ---*/ - if (parent != ULONG_MAX && nchild == 2) { - unsigned long wall_child = ULONG_MAX; - unsigned long other_child = ULONG_MAX; - for (unsigned short ci = 0; ci < nchild; ++ci) { - const auto ch = nodes->GetChildren_CV(parent, ci); - if (fine_grid->nodes->GetBoundary(ch)) wall_child = ch; - else other_child = ch; - } - if (wall_child != ULONG_MAX && other_child != ULONG_MAX) { - std::cout << "[MG_IMPLICIT] node " << wall_child << " was agglomerated with node " << other_child - << ", node " << wall_child << " has " << nchild << " children" << std::endl; - } else { - std::cout << "[MG_IMPLICIT] parent " << parent << " children do not match expected (wall + child)" << std::endl; - } + if (wall_child != ULONG_MAX && other_child != ULONG_MAX) { + std::cout << "[MG_IMPLICIT] node " << wall_child << " was agglomerated with node " << other_child + << ", node " << wall_child << " has " << nchild << " children" << std::endl; + } else { + std::cout << "[MG_IMPLICIT] parent " << parent << " children do not match expected (wall + child)" + << std::endl; } } } - else { - cout << "neighbor " << jPoint << " not agglomerated!!!!!!!!!!!!!!!!!!!" << endl; - } + } else { + cout << "neighbor " << jPoint << " not agglomerated!!!!!!!!!!!!!!!!!!!" << endl; + } if (!fine_grid->nodes->GetDomain(jPoint)) continue; if (fine_grid->nodes->GetBoundary(jPoint)) continue; /* avoid boundary nodes */ @@ -1063,7 +1045,7 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, unordered_map> parent_to_lines; parent_to_lines.reserve(lines.size() * 2); for (unsigned long li = 0; li < lines.size(); ++li) { - const auto &L = lines[li]; + const auto& L = lines[li]; if (L.empty()) continue; const auto W = L[0]; const auto pW = fine_grid->nodes->GetParent_CV(W); @@ -1074,8 +1056,8 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, vector line_processed(lines.size(), 0); // First, attempt cross-line merges for parents that have >1 wall-line - for (auto &entry : parent_to_lines) { - const auto &line_ids = entry.second; + for (auto& entry : parent_to_lines) { + const auto& line_ids = entry.second; if (line_ids.size() < 2) continue; // pair up lines in consecutive order (0 with 1, 2 with 3, ...) @@ -1084,11 +1066,11 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, 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& 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; // both must have the pair at this stage + if (L1.size() <= idx2 || L2.size() <= idx2) continue; // both must have the pair at this stage const auto a = L1[idx1]; const auto b = L1[idx2]; @@ -1106,21 +1088,21 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, !GeometricalCheck(c, fine_grid, config) || !GeometricalCheck(d, fine_grid, config)) continue; - // Check for duplicate indices — guard against merging the same node twice. if (a == b || a == c || a == d || b == c || b == d || c == d) { if (debug) { std::cout << "[MG_IMPLICIT] Duplicate indices detected in CROSS-LINE merge at stage " << pair_idx - << " lines " << li1 << "," << li2 << " : indices = " << a << "," << b << "," << c - << "," << d << "\n"; + << " lines " << li1 << "," << li2 << " : indices = " << a << "," << b << "," << c << "," << d + << "\n"; std::cout << "[MG_IMPLICIT] Parents: " << fine_grid->nodes->GetParent_CV(a) << "," << fine_grid->nodes->GetParent_CV(b) << "," << fine_grid->nodes->GetParent_CV(c) << "," << fine_grid->nodes->GetParent_CV(d) << "\n"; - std::cout << "[MG_IMPLICIT] Reserved flags: " << reserved[a] << "," << reserved[b] << "," - << reserved[c] << "," << reserved[d] << "\n"; + std::cout << "[MG_IMPLICIT] Reserved flags: " << reserved[a] << "," << reserved[b] << "," << reserved[c] + << "," << reserved[d] << "\n"; } else { - std::cout << "[MG_IMPLICIT] Duplicate indices detected in CROSS-LINE merge; skipping merge for Index_CoarseCV=" - << Index_CoarseCV << "\n"; + std::cout + << "[MG_IMPLICIT] Duplicate indices detected in CROSS-LINE merge; skipping merge for Index_CoarseCV=" + << Index_CoarseCV << "\n"; } // Stop implicit-line agglomeration for other lines that share this parent @@ -1132,8 +1114,8 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, // create a new coarse control volume merging {a,b,c,d} if (debug) { - std::cout << "[MG_IMPLICIT] Stage " << pair_idx << - ": CROSS-LINE merge creating coarse CV " << Index_CoarseCV << " from points "; + std::cout << "[MG_IMPLICIT] Stage " << pair_idx << ": CROSS-LINE merge creating coarse CV " << Index_CoarseCV + << " from points "; std::cout << a << "," << b << "," << c << "," << d << std::endl; } fine_grid->nodes->SetParent_CV(a, Index_CoarseCV); @@ -1155,7 +1137,8 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, Index_CoarseCV++; line_processed[li1] = line_processed[li2] = 1; // mark all other lines sharing this parent as processed (stop their implicit agglomeration) - for (auto other_li : line_ids) if (other_li != li1 && other_li != li2) line_processed[other_li] = 1; + for (auto other_li : line_ids) + if (other_li != li1 && other_li != li2) line_processed[other_li] = 1; done_stage = false; } } @@ -1163,10 +1146,10 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, // Second, for remaining lines, create 2-child coarse CVs for that stage for (unsigned long li = 0; li < lines.size(); ++li) { if (line_processed[li]) continue; - const auto &L = lines[li]; + const auto& L = lines[li]; const auto idx1 = 1 + 2 * pair_idx; const auto idx2 = idx1 + 1; - if (L.size() <= idx2) continue; // no pair at this stage + if (L.size() <= idx2) continue; // no pair at this stage const auto a = L[idx1]; const auto b = L[idx2]; @@ -1174,10 +1157,10 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, if (reserved[a] || reserved[b]) continue; if (!GeometricalCheck(a, fine_grid, config) || !GeometricalCheck(b, fine_grid, config)) continue; - if (debug) { - std::cout << "[MG_IMPLICIT] Stage " << pair_idx << ": 2-child merge creating coarse CV " << Index_CoarseCV - << " from points " << a << "," << b << std::endl; - } + if (debug) { + std::cout << "[MG_IMPLICIT] Stage " << pair_idx << ": 2-child merge creating coarse CV " << Index_CoarseCV + << " from points " << a << "," << b << std::endl; + } // create 2-child coarse CV fine_grid->nodes->SetParent_CV(a, Index_CoarseCV); @@ -1197,7 +1180,7 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, pair_idx++; // stop when no more pairs available at this stage across any line bool any_more = false; - for (const auto &L : lines) { + for (const auto& L : lines) { if (L.size() > 1 + 2 * pair_idx) { any_more = true; break; @@ -1213,7 +1196,7 @@ void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long &Index_CoarseCV, void CMultiGridGeometry::SetPoint_Connectivity(const CGeometry* fine_grid) { /*--- Temporary, CPoint (nodes) then compresses this structure. ---*/ - vector > points(nPoint); + vector> points(nPoint); for (auto iCoarsePoint = 0ul; iCoarsePoint < nPoint; iCoarsePoint++) { /*--- For each child CV (of the fine grid), ---*/ @@ -1588,4 +1571,3 @@ void CMultiGridGeometry::FindNormal_Neighbor(const CConfig* config) { } } } - From 944a2fc131648c10eb317deab141d1a3b904b15c Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 6 Dec 2025 17:10:56 +0100 Subject: [PATCH 22/41] fix gauss elimination NaN --- Common/src/linear_algebra/CSysMatrix.cpp | 21 +++++++++++++++++++++ SU2_CFD/src/solvers/CSolver.cpp | 5 ++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index dab97682f499..e934651d9614 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -505,9 +505,21 @@ void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* v #define A(I, J) matrix[(I)*nVar + (J)] /*--- Transform system in Upper Matrix ---*/ + + /*--- Regularization epsilon to prevent divide-by-zero ---*/ + constexpr ScalarType eps = 1e-12; + for (auto iVar = 1ul; iVar < nVar; iVar++) { for (auto jVar = 0ul; jVar < iVar; jVar++) { + + /*--- Regularize pivot if too small to prevent divide-by-zero ---*/ + if (std::abs(A(jVar, jVar)) < eps) { + ScalarType sign = (A(jVar, jVar) >= ScalarType(0)) ? ScalarType(1) : ScalarType(-1); + A(jVar, jVar) = sign * eps; + } + ScalarType weight = A(iVar, jVar) / A(jVar, jVar); + for (auto kVar = jVar; kVar < nVar; kVar++) A(iVar, kVar) -= weight * A(jVar, kVar); vec[iVar] -= weight * vec[jVar]; } @@ -517,6 +529,13 @@ void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* v for (auto iVar = nVar; iVar > 0ul;) { iVar--; // unsigned type for (auto jVar = iVar + 1; jVar < nVar; jVar++) vec[iVar] -= A(iVar, jVar) * vec[jVar]; + + /*--- Regularize diagonal if too small ---*/ + if (std::abs(A(iVar, iVar)) < eps) { + ScalarType sign = (A(iVar, iVar) >= ScalarType(0)) ? ScalarType(1) : ScalarType(-1); + A(iVar, iVar) = sign * eps; + } + vec[iVar] /= A(iVar, iVar); } #undef A @@ -819,6 +838,7 @@ void CSysMatrix::ComputeLU_SGSPreconditioner(const CSysVector::ComputeLU_SGSPreconditioner(const CSysVector begin;) { iPoint--; // because of unsigned type auto idx = iPoint * nVar; + DiagonalProduct(prod, iPoint, dia_prod); // Compute D.x* UpperProduct(prod, iPoint, row_end, up_prod); // Compute U.x_(n+1) VectorSubtraction(dia_prod, up_prod, &prod[idx]); // Compute y = D.x*-U.x_(n+1) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 2b786c181a17..3582e05f1d75 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1945,10 +1945,9 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, END_SU2_OMP_SAFE_GLOBAL_ACCESS } } else { - const su2double factor = 1.1; - const su2double CFL = factor * config->GetCFL(iMesh - 1);// / Coeff; + const su2double factor = 1.5; + const su2double CFL = factor * config->GetCFL(iMesh - 1); cout << "imesh="<< iMesh <<"Avg CFL="<SetCFL(iMesh, CFL); } From fb8e4ee8cc14f921ce5d84b8608c9e7ff34c3082 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 6 Dec 2025 17:33:36 +0100 Subject: [PATCH 23/41] fix euler wall agglomeration --- Common/include/geometry/CGeometry.hpp | 1 + .../include/geometry/CMultiGridGeometry.hpp | 9 + Common/src/geometry/CGeometry.cpp | 9 +- Common/src/geometry/CMultiGridGeometry.cpp | 174 ++++++++++++++++-- .../include/solvers/CFVMFlowSolverBase.inl | 29 +++ SU2_CFD/src/drivers/CDriver.cpp | 2 + .../src/integration/CMultiGridIntegration.cpp | 1 + 7 files changed, 204 insertions(+), 21 deletions(-) diff --git a/Common/include/geometry/CGeometry.hpp b/Common/include/geometry/CGeometry.hpp index f515e03e26ea..ccf7cd861569 100644 --- a/Common/include/geometry/CGeometry.hpp +++ b/Common/include/geometry/CGeometry.hpp @@ -245,6 +245,7 @@ class CGeometry { /*!< \brief Bool if boundary-marker is straight(2D)/plane(3D) for each local marker. */ vector boundIsStraight; + vector SurfaceAreaCfgFile; /*!< \brief Total Surface area for all markers. */ /*--- Partitioning-specific variables ---*/ diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 857f1b9c41c7..32578cf58083 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -84,6 +84,15 @@ class CMultiGridGeometry final : public CGeometry { */ void ComputeSurfStraightness(CConfig* config); + /*! + * \brief Compute local curvature at a boundary vertex on Euler wall. + * \param[in] fine_grid - Fine grid geometry. + * \param[in] iPoint - Point index. + * \param[in] iMarker - Marker index. + * \return Maximum angle (in degrees) between this vertex normal and adjacent vertex normals. + */ + su2double ComputeLocalCurvature(const CGeometry* fine_grid, unsigned long iPoint, unsigned short iMarker) const; + public: /*--- This is to suppress Woverloaded-virtual, omitting it has no negative impact. ---*/ using CGeometry::SetBoundControlVolume; diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index cd19a0297c57..05e4b670dc31 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -2588,11 +2588,10 @@ void CGeometry::ComputeSurfStraightness(const CConfig* config, bool print_on_scr constexpr passivedouble epsilon = 1.0e-6; su2double Area; string Local_TagBound, Global_TagBound; - cout << "Computing surface straightness for symmetry and Euler wall boundary markers..." << endl; - vector Normal(nDim), UnitNormal(nDim), RefUnitNormal(nDim); + cout << "Computing surface straightness for symmetry and Euler wall boundary markers..." << endl; - /*--- Assume now that this boundary marker is straight. As soon as one - AreaElement is found that is not aligend with a Reference then it is + vector Normal(nDim), UnitNormal(nDim), RefUnitNormal(nDim); /*--- Assume now that this boundary marker is straight. As soon as one + AreaElement is found that is not aligned with a Reference then it is certain that the boundary marker is not straight and one can stop searching. Another possibility is that this process doesn't own any nodes of that boundary, in that case we also have to assume the @@ -2701,6 +2700,8 @@ void CGeometry::ComputeSurfStraightness(const CConfig* config, bool print_on_scr } // if print_on_scren } + + void CGeometry::ComputeSurf_Curvature(CConfig* config) { unsigned short iMarker, iNeigh_Point, iDim, iNode, iNeighbor_Nodes, Neighbor_Node; unsigned long Neighbor_Point, iVertex, iPoint, jPoint, iElem_Bound, iEdge, nLocalVertex, MaxLocalVertex, diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 7afcaf41ce6f..68b9972254f1 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -33,7 +33,9 @@ #include #include #include +#include #include +#include /*--- Nijso says: this could perhaps be replaced by metis partitioning? ---*/ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, unsigned short iMesh) : CGeometry() { @@ -41,11 +43,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Maximum agglomeration size in 2D is 4 nodes, in 3D is 8 nodes. ---*/ const short int maxAgglomSize = 4; - /*--- Compute surface straightness to determine straight boundaries ---*/ - if (iMesh == MESH_0) - ComputeSurfStraightness(config); - else - boundIsStraight = fine_grid->boundIsStraight; + /*--- Inherit boundary properties from fine grid ---*/ + boundIsStraight = fine_grid->boundIsStraight; /*--- Agglomeration Scheme II (Nishikawa, Diskin, Thomas) Create a queue system to do the agglomeration @@ -80,8 +79,19 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un unsigned long Index_CoarseCV = 0; + /*--- Statistics for Euler wall agglomeration ---*/ + map euler_wall_agglomerated, euler_wall_rejected_curvature, euler_wall_rejected_straight; + for (unsigned short iMarker = 0; iMarker < fine_grid->GetnMarker(); iMarker++) { + if (config->GetMarker_All_KindBC(iMarker) == EULER_WALL) { + euler_wall_agglomerated[iMarker] = 0; + euler_wall_rejected_curvature[iMarker] = 0; + euler_wall_rejected_straight[iMarker] = 0; + } + } + /*--- STEP 1: The first step is the boundary agglomeration. ---*/ for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { + cout << "marker name = " << config->GetMarker_All_TagBound(iMarker) << endl; for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); @@ -130,13 +140,31 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Valley -> Valley : conditionally allowed when both points are on the same marker. ---*/ /*--- ! Note that in the case of MPI SEND_RECEIVE markers, we might need other conditions ---*/ if (counter == 1) { + cout << "we have exactly one marker at point " << iPoint << endl; + cout << " marker is " << marker_seed[0] << ", marker name = " + << config->GetMarker_All_TagBound(marker_seed[0]); + cout << ", marker type = " << config->GetMarker_All_KindBC(marker_seed[0]) << endl; // The seed/parent is one valley, so we set this part to true // if the child is only on this same valley, we set it to true as well. agglomerate_seed = true; - /*--- Euler walls can be curved and agglomerating them leads to difficulties ---*/ - // if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) agglomerate_seed = false; - if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL && !boundIsStraight[marker_seed[0]]) - agglomerate_seed = false; + /*--- Euler walls: check curvature-based agglomeration criterion ---*/ + if (config->GetMarker_All_KindBC(marker_seed[0]) == EULER_WALL) { + /*--- Allow agglomeration if marker is straight OR local curvature is small ---*/ + if (!boundIsStraight[marker_seed[0]]) { + /*--- Compute local curvature at this point ---*/ + su2double local_curvature = ComputeLocalCurvature(fine_grid, iPoint, marker_seed[0]); + // limit to 30 degrees + if (local_curvature >= 30.0) { + agglomerate_seed = false; // High curvature: do not agglomerate + euler_wall_rejected_curvature[marker_seed[0]]++; + } else { + euler_wall_agglomerated[marker_seed[0]]++; + } + } else { + /*--- Straight wall: agglomerate ---*/ + euler_wall_agglomerated[marker_seed[0]]++; + } + } /*--- Note that if the marker is a SEND_RECEIVE, then the node is actually an interior point. In that case it can only be agglomerated with another interior point. ---*/ } @@ -151,14 +179,25 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); - /* --- Euler walls can also not be agglomerated when the point has 2 markers or if curved ---*/ - // if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL) || - // (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL)) { - // agglomerate_seed = false; - // } - if ((config->GetMarker_All_KindBC(copy_marker[0]) == EULER_WALL && !boundIsStraight[copy_marker[0]]) || - (config->GetMarker_All_KindBC(copy_marker[1]) == EULER_WALL && !boundIsStraight[copy_marker[1]])) { - agglomerate_seed = false; + /*--- Euler walls: check curvature-based agglomeration criterion for both markers ---*/ + bool euler_wall_rejected_here = false; + for (unsigned short i = 0; i < 2; i++) { + if (config->GetMarker_All_KindBC(copy_marker[i]) == EULER_WALL) { + if (!boundIsStraight[copy_marker[i]]) { + /*--- Compute local curvature at this point ---*/ + su2double local_curvature = ComputeLocalCurvature(fine_grid, iPoint, copy_marker[i]); + // limit to 30 degrees + if (local_curvature >= 30.0) { + agglomerate_seed = false; // High curvature: do not agglomerate + euler_wall_rejected_curvature[copy_marker[i]]++; + euler_wall_rejected_here = true; + } + } + /*--- Track agglomeration if not rejected ---*/ + if (agglomerate_seed && !euler_wall_rejected_here) { + euler_wall_agglomerated[copy_marker[i]]++; + } + } } /*--- In 2D, corners are not agglomerated, but in 3D counter=2 means we are on the @@ -644,6 +683,43 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } + /*--- Output Euler wall agglomeration statistics ---*/ + if (rank == MASTER_NODE) { + /*--- Gather global statistics for Euler walls ---*/ + bool has_euler_walls = false; + for (unsigned short iMarker = 0; iMarker < fine_grid->GetnMarker(); iMarker++) { + if (config->GetMarker_All_KindBC(iMarker) == EULER_WALL) { + has_euler_walls = true; + break; + } + } + + if (has_euler_walls) { + cout << endl; + cout << "Euler Wall Agglomeration Statistics (45° curvature threshold):" << endl; + cout << "----------------------------------------------------------------" << endl; + + for (unsigned short iMarker = 0; iMarker < fine_grid->GetnMarker(); iMarker++) { + if (config->GetMarker_All_KindBC(iMarker) == EULER_WALL) { + string marker_name = config->GetMarker_All_TagBound(iMarker); + unsigned long agglomerated = euler_wall_agglomerated[iMarker]; + unsigned long rejected = euler_wall_rejected_curvature[iMarker]; + unsigned long total = agglomerated + rejected; + + if (total > 0) { + su2double accept_rate = 100.0 * su2double(agglomerated) / su2double(total); + cout << " Marker: " << marker_name << endl; + cout << " Seeds agglomerated: " << agglomerated << " (" + << std::setprecision(1) << std::fixed << accept_rate << "%)" << endl; + cout << " Seeds rejected (>45° curv): " << rejected << " (" + << std::setprecision(1) << std::fixed << (100.0 - accept_rate) << "%)" << endl; + } + } + } + cout << "----------------------------------------------------------------" << endl; + } + } + edgeColorGroupSize = config->GetEdgeColoringGroupSize(); } @@ -1571,3 +1647,67 @@ void CMultiGridGeometry::FindNormal_Neighbor(const CConfig* config) { } } } + +su2double CMultiGridGeometry::ComputeLocalCurvature(const CGeometry* fine_grid, unsigned long iPoint, + unsigned short iMarker) const { + /*--- Compute local curvature (maximum angle between adjacent face normals) at a boundary vertex. + This is used to determine if agglomeration is safe based on a curvature threshold. ---*/ + + /*--- Get the vertex index for this point on this marker ---*/ + long iVertex = fine_grid->nodes->GetVertex(iPoint, iMarker); + if (iVertex < 0) return 0.0; // Point not on this marker + + /*--- Get the normal at this vertex ---*/ + su2double Normal_i[MAXNDIM] = {0.0}; + fine_grid->vertex[iMarker][iVertex]->GetNormal(Normal_i); + su2double Area_i = GeometryToolbox::Norm(int(nDim), Normal_i); + + if (Area_i < 1e-12) return 0.0; // Skip degenerate vertices + + /*--- Normalize the normal ---*/ + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Normal_i[iDim] /= Area_i; + } + + /*--- Find maximum angle with neighboring vertices on the same marker ---*/ + su2double max_angle = 0.0; + + /*--- Loop over edges connected to this point ---*/ + for (unsigned short iEdge = 0; iEdge < fine_grid->nodes->GetnPoint(iPoint); iEdge++) { + unsigned long jPoint = fine_grid->nodes->GetPoint(iPoint, iEdge); + + /*--- Check if neighbor is also on this marker ---*/ + long jVertex = fine_grid->nodes->GetVertex(jPoint, iMarker); + if (jVertex < 0) continue; // Not on this marker + + /*--- Get normal at neighbor vertex ---*/ + su2double Normal_j[MAXNDIM] = {0.0}; + fine_grid->vertex[iMarker][jVertex]->GetNormal(Normal_j); + su2double Area_j = GeometryToolbox::Norm(int(nDim), Normal_j); + + if (Area_j < 1e-12) continue; // Skip degenerate neighbor + + /*--- Normalize the neighbor normal ---*/ + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Normal_j[iDim] /= Area_j; + } + + /*--- Compute dot product: cos(angle) = n_i · n_j ---*/ + su2double dot_product = 0.0; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + dot_product += Normal_i[iDim] * Normal_j[iDim]; + } + + /*--- Clamp to [-1, 1] to avoid numerical issues with acos ---*/ + dot_product = max(-1.0, min(1.0, dot_product)); + + /*--- Compute angle in degrees ---*/ + su2double angle_rad = acos(dot_product); + su2double angle_deg = angle_rad * 180.0 / PI_NUMBER; + + /*--- Track maximum angle ---*/ + max_angle = max(max_angle, angle_deg); + } + + return max_angle; +} diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 8dc0e4b63931..5ea610abe4af 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -1130,6 +1130,35 @@ void CFVMFlowSolverBase::BC_Sym_Plane(CGeometry* geometry, CSolve /*--- Halo points do not need to be considered. ---*/ if (!geometry->nodes->GetDomain(iPoint)) continue; + /*--- On coarse multigrid levels with agglomerated Euler walls, skip flux computation + * and directly zero momentum residuals to avoid errors from averaged normals. ---*/ + if (config->GetMarker_All_KindBC(val_marker) == EULER_WALL && geometry->GetMGLevel() > MESH_0) { + for (auto iDim = 0u; iDim < nDim; iDim++) { + LinSysRes(iPoint, iVel + iDim) = 0.0; + } + if (implicit) { + /*--- Zero momentum rows in Jacobian ---*/ + for (auto iDim = 0u; iDim < nDim; iDim++) { + auto* block = Jacobian.GetBlock(iPoint, iPoint); + for (auto jVar = 0u; jVar < nVar; jVar++) { + block[(iVel + iDim) * nVar + jVar] = 0.0; + } + block[(iVel + iDim) * nVar + (iVel + iDim)] = 1.0; // Diagonal + } + /*--- Zero momentum columns in off-diagonal blocks ---*/ + for (size_t iNeigh = 0; iNeigh < geometry->nodes->GetnPoint(iPoint); ++iNeigh) { + auto jPoint = geometry->nodes->GetPoint(iPoint, iNeigh); + auto* block = Jacobian.GetBlock(iPoint, jPoint); + for (auto iDim = 0u; iDim < nDim; iDim++) { + for (auto jVar = 0u; jVar < nVar; jVar++) { + block[(iVel + iDim) * nVar + jVar] = 0.0; + } + } + } + } + continue; // Skip normal flux computation + } + /*--- Get the normal of the current symmetry. This may be the original normal of the vertex * or a modified normal if there are intersecting symmetries. ---*/ diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 93fb335e1baf..c8cafb7f6933 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -841,6 +841,8 @@ void CDriver::InitializeGeometryFVM(CConfig *config, CGeometry **&geometry) { geometry[MESH_0]->ComputeSurf_Curvature(config); } + + /*--- Compute the global surface areas for all markers. ---*/ geometry[MESH_0]->ComputeSurfaceAreaCfgFile(config); diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index e3cf70de3127..0ed3d1ad9ab5 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -645,6 +645,7 @@ void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coar delete [] Residual; + /*--- Zero momentum components of truncation error on viscous walls ---*/ for (iMarker = 0; iMarker < config->GetnMarker_All(); iMarker++) { if (config->GetViscous_Wall(iMarker)) { SU2_OMP_FOR_STAT(32) From b91a9927a9da8b4883dbcea821dcfb71570cdc2c Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 6 Dec 2025 17:39:12 +0100 Subject: [PATCH 24/41] precommit --- Common/src/geometry/CGeometry.cpp | 23 ++++++++++------------ Common/src/geometry/CMultiGridGeometry.cpp | 17 ++++++++-------- Common/src/linear_algebra/CSysMatrix.cpp | 1 - 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index 05e4b670dc31..58a35af774f8 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -2588,17 +2588,16 @@ void CGeometry::ComputeSurfStraightness(const CConfig* config, bool print_on_scr constexpr passivedouble epsilon = 1.0e-6; su2double Area; string Local_TagBound, Global_TagBound; - cout << "Computing surface straightness for symmetry and Euler wall boundary markers..." << endl; - - vector Normal(nDim), UnitNormal(nDim), RefUnitNormal(nDim); /*--- Assume now that this boundary marker is straight. As soon as one - AreaElement is found that is not aligned with a Reference then it is - certain that the boundary marker is not straight and one can stop - searching. Another possibility is that this process doesn't own - any nodes of that boundary, in that case we also have to assume the - boundary is straight. - Any boundary type other than SYMMETRY_PLANE or EULER_WALL gets - the value false (or see cases specified in the conditional below) - which could be wrong. ---*/ + cout << "Computing surface straightness for symmetry and Euler wall boundary markers..." << endl; + + vector Normal(nDim), UnitNormal(nDim), RefUnitNormal(nDim); /*--- Assume now that this boundary marker is + straight. As soon as one AreaElement is found that is not aligned with a Reference then it is certain that the + boundary marker is not straight and one can stop searching. Another possibility is that this process doesn't own + any nodes of that boundary, in that case we also have to assume the + boundary is straight. + Any boundary type other than SYMMETRY_PLANE or EULER_WALL gets + the value false (or see cases specified in the conditional below) + which could be wrong. ---*/ boundIsStraight.resize(nMarker); cout << "boundisstraight size = " << boundIsStraight.size() << endl; fill(boundIsStraight.begin(), boundIsStraight.end(), true); @@ -2700,8 +2699,6 @@ void CGeometry::ComputeSurfStraightness(const CConfig* config, bool print_on_scr } // if print_on_scren } - - void CGeometry::ComputeSurf_Curvature(CConfig* config) { unsigned short iMarker, iNeigh_Point, iDim, iNode, iNeighbor_Nodes, Neighbor_Node; unsigned long Neighbor_Point, iVertex, iPoint, jPoint, iElem_Bound, iEdge, nLocalVertex, MaxLocalVertex, diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 68b9972254f1..84a32a5196ac 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -80,7 +80,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un unsigned long Index_CoarseCV = 0; /*--- Statistics for Euler wall agglomeration ---*/ - map euler_wall_agglomerated, euler_wall_rejected_curvature, euler_wall_rejected_straight; + map euler_wall_agglomerated, euler_wall_rejected_curvature, + euler_wall_rejected_straight; for (unsigned short iMarker = 0; iMarker < fine_grid->GetnMarker(); iMarker++) { if (config->GetMarker_All_KindBC(iMarker) == EULER_WALL) { euler_wall_agglomerated[iMarker] = 0; @@ -141,8 +142,8 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- ! Note that in the case of MPI SEND_RECEIVE markers, we might need other conditions ---*/ if (counter == 1) { cout << "we have exactly one marker at point " << iPoint << endl; - cout << " marker is " << marker_seed[0] << ", marker name = " - << config->GetMarker_All_TagBound(marker_seed[0]); + cout << " marker is " << marker_seed[0] + << ", marker name = " << config->GetMarker_All_TagBound(marker_seed[0]); cout << ", marker type = " << config->GetMarker_All_KindBC(marker_seed[0]) << endl; // The seed/parent is one valley, so we set this part to true // if the child is only on this same valley, we set it to true as well. @@ -709,10 +710,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un if (total > 0) { su2double accept_rate = 100.0 * su2double(agglomerated) / su2double(total); cout << " Marker: " << marker_name << endl; - cout << " Seeds agglomerated: " << agglomerated << " (" - << std::setprecision(1) << std::fixed << accept_rate << "%)" << endl; - cout << " Seeds rejected (>45° curv): " << rejected << " (" - << std::setprecision(1) << std::fixed << (100.0 - accept_rate) << "%)" << endl; + cout << " Seeds agglomerated: " << agglomerated << " (" << std::setprecision(1) << std::fixed + << accept_rate << "%)" << endl; + cout << " Seeds rejected (>45° curv): " << rejected << " (" << std::setprecision(1) << std::fixed + << (100.0 - accept_rate) << "%)" << endl; } } } @@ -1649,7 +1650,7 @@ void CMultiGridGeometry::FindNormal_Neighbor(const CConfig* config) { } su2double CMultiGridGeometry::ComputeLocalCurvature(const CGeometry* fine_grid, unsigned long iPoint, - unsigned short iMarker) const { + unsigned short iMarker) const { /*--- Compute local curvature (maximum angle between adjacent face normals) at a boundary vertex. This is used to determine if agglomeration is safe based on a curvature threshold. ---*/ diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index e934651d9614..97a9ea200e0e 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -511,7 +511,6 @@ void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* v for (auto iVar = 1ul; iVar < nVar; iVar++) { for (auto jVar = 0ul; jVar < iVar; jVar++) { - /*--- Regularize pivot if too small to prevent divide-by-zero ---*/ if (std::abs(A(jVar, jVar)) < eps) { ScalarType sign = (A(jVar, jVar) >= ScalarType(0)) ? ScalarType(1) : ScalarType(-1); From 6332e2848c3dc4eba0af6278f47a0512fb704635 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 7 Dec 2025 16:38:26 +0100 Subject: [PATCH 25/41] CFL adapt oscillation detection --- SU2_CFD/src/solvers/CSolver.cpp | 136 +++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 13 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 3582e05f1d75..b1e973b6c653 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1721,7 +1721,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, const bool fullComms = (config->GetComm_Level() == COMM_FULL); /* Number of iterations considered to check for stagnation. */ - const auto Res_Count = min(100ul, config->GetnInner_Iter()-1); + const auto Res_Count = min(400ul, config->GetnInner_Iter()-1); static bool reduceCFL, resetCFL, canIncrease; @@ -1804,25 +1804,135 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, NonLinRes_Counter++; if (NonLinRes_Counter == Res_Count) NonLinRes_Counter = 0; - /* Detect flip-flop convergence to reduce CFL and large increases - to reset to minimum value, in that case clear the history. */ + /* Detect oscillations and divergence using peak-valley progression + plus slope-based guards. This replaces the older sign-change check. + - Maintain a history window (Res_Count up to 400 iters). + - Extract peaks/valleys with minimum separation to avoid noise. + - Require new peaks/valleys to progress downward; otherwise flag. + - Use least-squares slope on last 3 peaks/valleys to catch divergence. + - Forget extrema older than a lookback window (300 iters). */ + + if (config->GetInnerIter() >= 3) { + const unsigned long EXT_WINDOW = 300; + const unsigned long MIN_SEPARATION = 10; /* iters between extrema to avoid noise */ + const su2double VALUE_TOL = 0.05; /* log10 residual tolerance for peak/valley improvement */ + const su2double SLOPE_FLAT = -1.0e-3; /* slope >= this => stagnation */ + const su2double SLOPE_UP = 1.5e-3; /* slope > this => divergence */ + + /* Reconstruct New_Func history from the delta buffer. */ + vector funcHistory; + funcHistory.reserve(Res_Count); + su2double curr = New_Func; + funcHistory.push_back(curr); + for (unsigned long i = 1; i < Res_Count; i++) { + unsigned long idx = (NonLinRes_Counter + Res_Count - i) % Res_Count; + curr -= NonLinRes_Series[idx]; + funcHistory.push_back(curr); + } + reverse(funcHistory.begin(), funcHistory.end()); + + /* Map history index to iteration number. */ + const unsigned long histSize = funcHistory.size(); + const unsigned long currIter = config->GetInnerIter(); + const unsigned long startIter = currIter - histSize + 1; + + /* Find peaks and valleys. */ + vector> peaks; + vector> valleys; + unsigned long lastExtIdx = 0; + for (unsigned long i = 1; i + 1 < histSize; i++) { + if (i - lastExtIdx < MIN_SEPARATION) continue; + su2double a = funcHistory[i-1]; + su2double b = funcHistory[i]; + su2double c = funcHistory[i+1]; + unsigned long iter_i = startIter + i; + /* Drop extrema that fall outside lookback window. */ + if (currIter > EXT_WINDOW && iter_i + EXT_WINDOW < currIter) continue; + if (b > a && b > c) { + peaks.emplace_back(iter_i, b); + lastExtIdx = i; + } else if (b < a && b < c) { + valleys.emplace_back(iter_i, b); + lastExtIdx = i; + } + } - if (config->GetInnerIter() >= Res_Count) { - unsigned long signChanges = 0; - su2double totalChange = 0.0; - auto prev = NonLinRes_Series.front(); - for (const auto& val : NonLinRes_Series) { - totalChange += val; - signChanges += (prev > 0) ^ (val > 0); - prev = val; + auto slopeLSQ = [](const vector>& pts) { + if (pts.size() < 2) return 0.0; + const unsigned long n = pts.size(); + su2double meanX = 0.0, meanY = 0.0; + for (const auto& p : pts) { meanX += p.first; meanY += p.second; } + meanX /= n; meanY /= n; + su2double num = 0.0, den = 0.0; + for (const auto& p : pts) { + su2double dx = p.first - meanX; + num += dx * (p.second - meanY); + den += dx * dx; + } + return (den > 1e-16) ? num / den : 0.0; + }; + + /* Keep only the last three peaks/valleys for slope; drop older than window. */ + auto trimLastN = [](const vector>& v, unsigned long N) { + vector> out; + const unsigned long sz = v.size(); + const unsigned long start = (sz > N) ? sz - N : 0; + for (unsigned long i = start; i < sz; ++i) out.push_back(v[i]); + return out; + }; + + auto recentPeaks = trimLastN(peaks, 3); + auto recentValleys = trimLastN(valleys, 3); + + bool peaksStagnant = false, valleysStagnant = false; + if (peaks.size() >= 2) { + peaksStagnant = peaks.back().second >= peaks[peaks.size()-2].second - VALUE_TOL; + } + if (valleys.size() >= 2) { + valleysStagnant = valleys.back().second >= valleys[valleys.size()-2].second - VALUE_TOL; } - reduceCFL |= (signChanges > Res_Count/4) && (totalChange > -0.5); - if (totalChange > 2.0) { // orders of magnitude + /* Slope of last 3 peaks/valleys (only if enough points). */ + su2double slopePeaks = 0.0, slopeValleys = 0.0; + bool haveSlopePeaks = (recentPeaks.size() >= 2); + bool haveSlopeValleys = (recentValleys.size() >= 2); + if (haveSlopePeaks) slopePeaks = slopeLSQ(recentPeaks); + if (haveSlopeValleys) slopeValleys = slopeLSQ(recentValleys); + + bool slopeFlat = (haveSlopePeaks && slopePeaks >= SLOPE_FLAT) || (haveSlopeValleys && slopeValleys >= SLOPE_FLAT); + bool slopeUp = (haveSlopePeaks && slopePeaks > SLOPE_UP) || (haveSlopeValleys && slopeValleys > SLOPE_UP); + + /* Only act when we have at least two peaks and two valleys to avoid premature locking. */ + bool haveData = (peaks.size() >= 2 && valleys.size() >= 2); + bool progressionFail = haveData && peaksStagnant && valleysStagnant; + bool oscillationDetected = haveData && (progressionFail || slopeFlat || slopeUp); + + reduceCFL |= oscillationDetected; + + /* Strong divergence safety: if the total change over the full buffer is large positive, reset. */ + su2double totalChange = 0.0; + for (const auto& v : NonLinRes_Series) totalChange += v; + if (totalChange > 2.0 || slopeUp) { // 2 orders worse or clear rising slope resetCFL = true; NonLinRes_Counter = 0; for (auto& val : NonLinRes_Series) val = 0.0; } + + /* Stagnation guard: if over a recent window there is essentially no improvement, + lower CFL to escape the stall. */ + const unsigned long STALL_WINDOW = min(100ul, Res_Count); + if (config->GetInnerIter() >= STALL_WINDOW) { + su2double stallChange = 0.0; + for (unsigned long i = 0; i < STALL_WINDOW; i++) { + unsigned long idx = (NonLinRes_Counter + Res_Count - 1 - i) % Res_Count; + stallChange += NonLinRes_Series[idx]; + } + /* If cumulative change is greater than a small negative tolerance, we are stalled. */ + const su2double STALL_TOL = -0.1; /* about 0.1 log10 improvement threshold over window */ + if (stallChange > STALL_TOL) { + reduceCFL = true; + } + } } } } /* End safe global access, now all threads update the CFL number. */ From 7b53432a021f4d192d48c6223db2a4aa09f6016c Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 7 Dec 2025 18:00:49 +0100 Subject: [PATCH 26/41] simpler CFL adapt oscillation detection --- SU2_CFD/src/solvers/CSolver.cpp | 52 +++++++++++++-------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index b1e973b6c653..1917ce19333e 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -30,6 +30,7 @@ #include "../../include/gradients/computeGradientsGreenGauss.hpp" #include "../../include/gradients/computeGradientsLeastSquares.hpp" #include "../../include/limiters/computeLimiters.hpp" +#include #include "../../../Common/include/toolboxes/MMS/CIncTGVSolution.hpp" #include "../../../Common/include/toolboxes/MMS/CInviscidVortexSolution.hpp" #include "../../../Common/include/toolboxes/MMS/CMMSIncEulerSolution.hpp" @@ -1836,9 +1837,9 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, const unsigned long currIter = config->GetInnerIter(); const unsigned long startIter = currIter - histSize + 1; - /* Find peaks and valleys. */ - vector> peaks; - vector> valleys; + /* Find peaks and valleys using deques for automatic size management. */ + static deque> peaks; + static deque> valleys; unsigned long lastExtIdx = 0; for (unsigned long i = 1; i + 1 < histSize; i++) { if (i - lastExtIdx < MIN_SEPARATION) continue; @@ -1849,41 +1850,28 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Drop extrema that fall outside lookback window. */ if (currIter > EXT_WINDOW && iter_i + EXT_WINDOW < currIter) continue; if (b > a && b > c) { - peaks.emplace_back(iter_i, b); + peaks.push_back(make_pair(iter_i, b)); + if (peaks.size() > 3) peaks.pop_front(); lastExtIdx = i; } else if (b < a && b < c) { - valleys.emplace_back(iter_i, b); + valleys.push_back(make_pair(iter_i, b)); + if (valleys.size() > 3) valleys.pop_front(); lastExtIdx = i; } } - auto slopeLSQ = [](const vector>& pts) { + auto slopeLSQ = [](const deque>& pts) { if (pts.size() < 2) return 0.0; - const unsigned long n = pts.size(); - su2double meanX = 0.0, meanY = 0.0; - for (const auto& p : pts) { meanX += p.first; meanY += p.second; } - meanX /= n; meanY /= n; - su2double num = 0.0, den = 0.0; - for (const auto& p : pts) { - su2double dx = p.first - meanX; - num += dx * (p.second - meanY); - den += dx * dx; + if (pts.size() == 2) { + /* Two points: simple slope. */ + return (pts[1].second - pts[0].second) / su2double(pts[1].first - pts[0].first); } - return (den > 1e-16) ? num / den : 0.0; + /* Three points: average of two consecutive slopes. */ + const su2double slope1 = (pts[1].second - pts[0].second) / su2double(pts[1].first - pts[0].first); + const su2double slope2 = (pts[2].second - pts[1].second) / su2double(pts[2].first - pts[1].first); + return 0.5 * (slope1 + slope2); }; - /* Keep only the last three peaks/valleys for slope; drop older than window. */ - auto trimLastN = [](const vector>& v, unsigned long N) { - vector> out; - const unsigned long sz = v.size(); - const unsigned long start = (sz > N) ? sz - N : 0; - for (unsigned long i = start; i < sz; ++i) out.push_back(v[i]); - return out; - }; - - auto recentPeaks = trimLastN(peaks, 3); - auto recentValleys = trimLastN(valleys, 3); - bool peaksStagnant = false, valleysStagnant = false; if (peaks.size() >= 2) { peaksStagnant = peaks.back().second >= peaks[peaks.size()-2].second - VALUE_TOL; @@ -1894,10 +1882,10 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Slope of last 3 peaks/valleys (only if enough points). */ su2double slopePeaks = 0.0, slopeValleys = 0.0; - bool haveSlopePeaks = (recentPeaks.size() >= 2); - bool haveSlopeValleys = (recentValleys.size() >= 2); - if (haveSlopePeaks) slopePeaks = slopeLSQ(recentPeaks); - if (haveSlopeValleys) slopeValleys = slopeLSQ(recentValleys); + bool haveSlopePeaks = (peaks.size() >= 2); + bool haveSlopeValleys = (valleys.size() >= 2); + if (haveSlopePeaks) slopePeaks = slopeLSQ(peaks); + if (haveSlopeValleys) slopeValleys = slopeLSQ(valleys); bool slopeFlat = (haveSlopePeaks && slopePeaks >= SLOPE_FLAT) || (haveSlopeValleys && slopeValleys >= SLOPE_FLAT); bool slopeUp = (haveSlopePeaks && slopePeaks > SLOPE_UP) || (haveSlopeValleys && slopeValleys > SLOPE_UP); From 2013392685229beeb3f259d761d1bfab1e4238c6 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 7 Dec 2025 20:40:50 +0100 Subject: [PATCH 27/41] asdd XOR flipflop again --- SU2_CFD/src/solvers/CSolver.cpp | 133 +++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 9 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 1917ce19333e..c004a2903f40 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1765,6 +1765,11 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, canIncrease = (linRes < linTol) && (iter >= startingIter); + if (rank == MASTER_NODE && iter % 50 == 0) { + cout << "CFL Debug - Iter " << iter << ": linRes=" << linRes << " linTol=" << linTol + << " reduceCFL=" << reduceCFL << " canIncrease=" << canIncrease << " resetCFL=" << resetCFL << endl; + } + if (Res_Count > 0) { Old_Func = New_Func; if (NonLinRes_Series.empty()) NonLinRes_Series.resize(Res_Count,0.0); @@ -1805,8 +1810,29 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, NonLinRes_Counter++; if (NonLinRes_Counter == Res_Count) NonLinRes_Counter = 0; + /* Fast flip-flop detection using XOR sign changes (from old code). + This catches rapid oscillations that the peak-valley logic might miss. */ + if (config->GetInnerIter() >= Res_Count) { + unsigned long signChanges = 0; + su2double totalChange = 0.0; + auto prev = NonLinRes_Series.front(); + for (const auto& val : NonLinRes_Series) { + totalChange += val; + signChanges += (prev > 0) ^ (val > 0); + prev = val; + } + /* Flip-flop: many sign changes with little net progress */ + reduceCFL |= (signChanges > Res_Count/4) && (totalChange > -0.5); + + if (totalChange > 2.0) { // orders of magnitude divergence + resetCFL = true; + NonLinRes_Counter = 0; + for (auto& val : NonLinRes_Series) val = 0.0; + } + } + /* Detect oscillations and divergence using peak-valley progression - plus slope-based guards. This replaces the older sign-change check. + plus slope-based guards. This works alongside the flip-flop check above. - Maintain a history window (Res_Count up to 400 iters). - Extract peaks/valleys with minimum separation to avoid noise. - Require new peaks/valleys to progress downward; otherwise flag. @@ -1815,10 +1841,14 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, if (config->GetInnerIter() >= 3) { const unsigned long EXT_WINDOW = 300; - const unsigned long MIN_SEPARATION = 10; /* iters between extrema to avoid noise */ + const unsigned long MIN_SEPARATION = 30; /* iters between extrema to avoid noise */ const su2double VALUE_TOL = 0.05; /* log10 residual tolerance for peak/valley improvement */ const su2double SLOPE_FLAT = -1.0e-3; /* slope >= this => stagnation */ - const su2double SLOPE_UP = 1.5e-3; /* slope > this => divergence */ + const su2double SLOPE_UP = 0.1; /* slope > this => divergence (log10 per iter) */ + + /* Counter to avoid reacting to every single iteration */ + static unsigned long oscillationCounter = 0; + static bool previousOscillation = false; /* Reconstruct New_Func history from the delta buffer. */ vector funcHistory; @@ -1840,6 +1870,15 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Find peaks and valleys using deques for automatic size management. */ static deque> peaks; static deque> valleys; + + /* Clear old extrema that are outside the lookback window */ + while (!peaks.empty() && currIter > EXT_WINDOW && peaks.front().first + EXT_WINDOW < currIter) { + peaks.pop_front(); + } + while (!valleys.empty() && currIter > EXT_WINDOW && valleys.front().first + EXT_WINDOW < currIter) { + valleys.pop_front(); + } + unsigned long lastExtIdx = 0; for (unsigned long i = 1; i + 1 < histSize; i++) { if (i - lastExtIdx < MIN_SEPARATION) continue; @@ -1864,11 +1903,16 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, if (pts.size() < 2) return 0.0; if (pts.size() == 2) { /* Two points: simple slope. */ - return (pts[1].second - pts[0].second) / su2double(pts[1].first - pts[0].first); + const su2double dt = su2double(pts[1].first - pts[0].first); + if (dt < 1e-12) return 0.0; /* Avoid division by zero */ + return (pts[1].second - pts[0].second) / dt; } /* Three points: average of two consecutive slopes. */ - const su2double slope1 = (pts[1].second - pts[0].second) / su2double(pts[1].first - pts[0].first); - const su2double slope2 = (pts[2].second - pts[1].second) / su2double(pts[2].first - pts[1].first); + const su2double dt1 = su2double(pts[1].first - pts[0].first); + const su2double dt2 = su2double(pts[2].first - pts[1].first); + if (dt1 < 1e-12 || dt2 < 1e-12) return 0.0; /* Avoid division by zero */ + const su2double slope1 = (pts[1].second - pts[0].second) / dt1; + const su2double slope2 = (pts[2].second - pts[1].second) / dt2; return 0.5 * (slope1 + slope2); }; @@ -1887,19 +1931,77 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, if (haveSlopePeaks) slopePeaks = slopeLSQ(peaks); if (haveSlopeValleys) slopeValleys = slopeLSQ(valleys); - bool slopeFlat = (haveSlopePeaks && slopePeaks >= SLOPE_FLAT) || (haveSlopeValleys && slopeValleys >= SLOPE_FLAT); + /* If slope is exactly zero (extrema at same iteration), it's not meaningful - ignore it */ + bool slopeFlat = false; + if (haveSlopePeaks && fabs(slopePeaks) > 1e-12 && slopePeaks >= SLOPE_FLAT) slopeFlat = true; + if (haveSlopeValleys && fabs(slopeValleys) > 1e-12 && slopeValleys >= SLOPE_FLAT) slopeFlat = true; + bool slopeUp = (haveSlopePeaks && slopePeaks > SLOPE_UP) || (haveSlopeValleys && slopeValleys > SLOPE_UP); + /* Check overall trend: even with oscillations, is the average moving down? */ + su2double overallSlope = 0.0; + if (peaks.size() >= 2 && valleys.size() >= 2) { + /* Use the oldest peak/valley and newest peak/valley to get overall trend */ + su2double oldestValue = min(peaks.front().second, valleys.front().second); + su2double newestValue = min(peaks.back().second, valleys.back().second); + unsigned long oldestIter = min(peaks.front().first, valleys.front().first); + unsigned long newestIter = max(peaks.back().first, valleys.back().first); + su2double dt = su2double(newestIter - oldestIter); + if (dt > 1e-12) { + overallSlope = (newestValue - oldestValue) / dt; + } + } + /* Only act when we have at least two peaks and two valleys to avoid premature locking. */ bool haveData = (peaks.size() >= 2 && valleys.size() >= 2); bool progressionFail = haveData && peaksStagnant && valleysStagnant; - bool oscillationDetected = haveData && (progressionFail || slopeFlat || slopeUp); - reduceCFL |= oscillationDetected; + /* If all slopes are zero (extrema at same time or too close), don't treat as oscillation */ + bool allSlopesZero = (fabs(slopePeaks) < 1e-12 && fabs(slopeValleys) < 1e-12 && fabs(overallSlope) < 1e-12); + + /* Oscillation is only bad if slopes are problematic AND overall trend is not improving */ + bool oscillationDetected = false; + if (haveData && !allSlopesZero) { + oscillationDetected = (progressionFail || (slopeFlat && overallSlope >= -1.0e-3) || slopeUp); + } + + if (rank == MASTER_NODE && currIter % 50 == 0 && haveData) { + cout << "Oscillation Debug - Iter " << currIter << ": peaks=" << peaks.size() << " valleys=" << valleys.size() + << " slopePeaks=" << slopePeaks << " slopeValleys=" << slopeValleys + << " overallSlope=" << overallSlope + << " slopeFlat=" << slopeFlat << " progressionFail=" << progressionFail + << " oscillationDetected=" << oscillationDetected << endl; + } + + /* If overall trend is good (decreasing), don't block CFL increase */ + if (overallSlope < -1.0e-3) { + oscillationDetected = false; + } + + /* Damping: only reduce CFL if oscillation persists for multiple iterations */ + if (oscillationDetected) { + if (previousOscillation) { + oscillationCounter++; + } else { + oscillationCounter = 1; + } + } else { + oscillationCounter = 0; + } + previousOscillation = oscillationDetected; + + /* Only act on oscillation if it persists for at least 3 checks */ + if (oscillationCounter >= 3) { + reduceCFL |= true; + } /* Strong divergence safety: if the total change over the full buffer is large positive, reset. */ su2double totalChange = 0.0; for (const auto& v : NonLinRes_Series) totalChange += v; + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "Divergence Debug - Iter " << currIter << ": totalChange=" << totalChange + << " slopeUp=" << slopeUp << endl; + } if (totalChange > 2.0 || slopeUp) { // 2 orders worse or clear rising slope resetCFL = true; NonLinRes_Counter = 0; @@ -1972,6 +2074,13 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, CFLFactor = CFLFactorIncrease; } + /* Debug output for first point every 50 iterations */ + if (iPoint == 0 && config->GetInnerIter() % 50 == 0 && rank == MASTER_NODE) { + cout << "Point " << iPoint << " - underRelax=" << underRelaxation + << " CFLFactor=" << CFLFactor << " CFL=" << CFL + << " reduceCFL=" << reduceCFL << " canIncrease=" << canIncrease << endl; + } + /* Check if we are hitting the min or max and adjust. */ if (CFL*CFLFactor <= CFLMin) { @@ -1984,6 +2093,12 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, //CFLFactor = MGFactor[iMesh]; } + /* Debug output for min/max capping */ + if (iPoint == 0 && config->GetInnerIter() % 50 == 0 && rank == MASTER_NODE) { + cout << "After min/max check - CFL=" << CFL << " (min=" << CFLMin << ", max=" << CFLMax + << ", wanted=" << (CFL*CFLFactor) << ")" << endl; + } + /* If we detect a stalled nonlinear residual, then force the CFL for all points to the minimum temporarily to restart the ramp. */ From 6cfdbf067c127bee4fdf52a9ef10c11fe9d46f26 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 7 Dec 2025 23:02:45 +0100 Subject: [PATCH 28/41] modify cfl on coarser meshes --- SU2_CFD/src/solvers/CSolver.cpp | 40 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index c004a2903f40..997e1eb2ebb5 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1712,7 +1712,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Adapt the CFL number on all multigrid levels using an exponential progression with under-relaxation approach. */ - vector MGFactor(config->GetnMGLevels()+1,1.0); + //vector MGFactor(config->GetnMGLevels()+1,1.0); const su2double CFLFactorDecrease = config->GetCFL_AdaptParam(0); const su2double CFLFactorIncrease = config->GetCFL_AdaptParam(1); const su2double CFLMin = config->GetCFL_AdaptParam(2); @@ -1722,7 +1722,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, const bool fullComms = (config->GetComm_Level() == COMM_FULL); /* Number of iterations considered to check for stagnation. */ - const auto Res_Count = min(400ul, config->GetnInner_Iter()-1); + const auto Res_Count = min(100ul, config->GetnInner_Iter()-1); static bool reduceCFL, resetCFL, canIncrease; @@ -1822,7 +1822,14 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, prev = val; } /* Flip-flop: many sign changes with little net progress */ - reduceCFL |= (signChanges > Res_Count/4) && (totalChange > -0.5); + bool flipFlopDetected = (signChanges > Res_Count/4) && (totalChange > -0.5); + unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + if (rank == MASTER_NODE && iter % 50 == 0) { + cout << "FlipFlop Debug - Iter " << iter << ": signChanges=" << signChanges + << " threshold=" << Res_Count/4 << " totalChange=" << totalChange + << " flipFlopDetected=" << flipFlopDetected << endl; + } + reduceCFL |= flipFlopDetected; if (totalChange > 2.0) { // orders of magnitude divergence resetCFL = true; @@ -1991,7 +1998,12 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, previousOscillation = oscillationDetected; /* Only act on oscillation if it persists for at least 3 checks */ - if (oscillationCounter >= 3) { + bool persistentOscillation = (oscillationCounter >= 3); + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "Persistence Debug - Iter " << currIter << ": oscillationCounter=" << oscillationCounter + << " persistentOscillation=" << persistentOscillation << endl; + } + if (persistentOscillation) { reduceCFL |= true; } @@ -2163,6 +2175,26 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, cout << "imesh="<< iMesh <<"Avg CFL="<SetCFL(iMesh, CFL); + /* Apply the CFL to all points on this coarse mesh. */ + CSolver *solverFlow = solver_container[iMesh][FLOW_SOL]; + CSolver *solverTurb = solver_container[iMesh][TURB_SOL]; + CSolver *solverSpecies = solver_container[iMesh][SPECIES_SOL]; + + const su2double CFLTurbReduction = config->GetCFLRedCoeff_Turb(); + const su2double CFLSpeciesReduction = config->GetCFLRedCoeff_Species(); + + SU2_OMP_FOR_STAT(roundUpDiv(geometry[iMesh]->GetnPointDomain(),omp_get_max_threads())) + for (unsigned long iPoint = 0; iPoint < geometry[iMesh]->GetnPointDomain(); iPoint++) { + solverFlow->GetNodes()->SetLocalCFL(iPoint, CFL); + if (solverTurb) { + solverTurb->GetNodes()->SetLocalCFL(iPoint, CFL * CFLTurbReduction); + } + if (solverSpecies) { + solverSpecies->GetNodes()->SetLocalCFL(iPoint, CFL * CFLSpeciesReduction); + } + } + END_SU2_OMP_FOR + } } From 4cd47843b306bc176ea77076e50fd779aba46c33 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 8 Dec 2025 10:08:34 +0100 Subject: [PATCH 29/41] introduce LSQ slope computation --- SU2_CFD/src/solvers/CSolver.cpp | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 997e1eb2ebb5..1831dc431344 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1908,19 +1908,26 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, auto slopeLSQ = [](const deque>& pts) { if (pts.size() < 2) return 0.0; - if (pts.size() == 2) { - /* Two points: simple slope. */ - const su2double dt = su2double(pts[1].first - pts[0].first); - if (dt < 1e-12) return 0.0; /* Avoid division by zero */ - return (pts[1].second - pts[0].second) / dt; + + /* Compute least-squares linear regression slope: + slope = [n*Σ(xy) - Σ(x)*Σ(y)] / [n*Σ(x²) - (Σ(x))²] */ + su2double n = su2double(pts.size()); + su2double sumX = 0.0, sumY = 0.0, sumXY = 0.0, sumX2 = 0.0; + + for (const auto& pt : pts) { + su2double x = su2double(pt.first); // iteration number + su2double y = pt.second; // log10 residual value + sumX += x; + sumY += y; + sumXY += x * y; + sumX2 += x * x; } - /* Three points: average of two consecutive slopes. */ - const su2double dt1 = su2double(pts[1].first - pts[0].first); - const su2double dt2 = su2double(pts[2].first - pts[1].first); - if (dt1 < 1e-12 || dt2 < 1e-12) return 0.0; /* Avoid division by zero */ - const su2double slope1 = (pts[1].second - pts[0].second) / dt1; - const su2double slope2 = (pts[2].second - pts[1].second) / dt2; - return 0.5 * (slope1 + slope2); + + su2double denominator = n * sumX2 - sumX * sumX; + if (fabs(denominator) < 1e-12) return 0.0; // Avoid division by zero + + su2double slope = (n * sumXY - sumX * sumY) / denominator; + return slope; }; bool peaksStagnant = false, valleysStagnant = false; @@ -2199,11 +2206,6 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, } - //for (unsigned short iMesh = 1; iMesh <= config->GetnMGLevels(); iMesh++) { - // const su2double CFLRatio = config->GetCFL(iMesh)/config->GetCFL(iMesh-1); - // MGFactor[iMesh] = MGFactor[iMesh-1]*CFLRatio; - // cout << " CFL Level " << iMesh << " initial: " << config->GetCFL(iMesh)<< endl; - // cout << " CFL Level " << iMesh-1 << " initial: " << config->GetCFL(iMesh-1) << endl; } From 0699ce43ecc0cb606e7a108fc39e9952b5d98b40 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 8 Dec 2025 10:23:48 +0100 Subject: [PATCH 30/41] add explanation --- Common/src/geometry/CMultiGridGeometry.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 84a32a5196ac..a4a2f1394fd3 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -53,6 +53,9 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un 3rd) One marker ---> Surface (always agglomerate) 4th) No marker ---> Internal Volume (always agglomerate) ---*/ + //note that for MPI, we introduce interfaces and we can choose to have agglomeration over + //the interface or not. + /*--- Set a marker to indicate indirect agglomeration, for quads and hexs, i.e. consider up to neighbors of neighbors of neighbors. For other levels this information is propagated down during their construction. ---*/ From 0657fe25b0ef8f8c0c06329f05605349668b25d2 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sat, 13 Dec 2025 23:25:11 +0100 Subject: [PATCH 31/41] fix CFL min value --- SU2_CFD/src/solvers/CSolver.cpp | 58 ++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 1831dc431344..20ff418bc53a 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -2028,7 +2028,8 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, } /* Stagnation guard: if over a recent window there is essentially no improvement, - lower CFL to escape the stall. */ + lower CFL to escape the stall. Only trigger if we're truly stalled (near zero or + positive change) and not conflicting with other indicators showing good progress. */ const unsigned long STALL_WINDOW = min(100ul, Res_Count); if (config->GetInnerIter() >= STALL_WINDOW) { su2double stallChange = 0.0; @@ -2036,9 +2037,24 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, unsigned long idx = (NonLinRes_Counter + Res_Count - 1 - i) % Res_Count; stallChange += NonLinRes_Series[idx]; } - /* If cumulative change is greater than a small negative tolerance, we are stalled. */ - const su2double STALL_TOL = -0.1; /* about 0.1 log10 improvement threshold over window */ - if (stallChange > STALL_TOL) { + /* Only flag as stalled if change is very small or positive, AND we're not already + seeing good convergence indicators (canIncrease means linear solver is doing well). + Tightened threshold from -0.1 to -0.01 to avoid false positives on slow but steady convergence. */ + const su2double STALL_TOL = -0.01; /* about 0.01 log10 improvement threshold over window */ + bool isStalled = (stallChange > STALL_TOL); + + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "Stagnation Debug - Iter " << currIter << ": stallChange=" << stallChange + << " STALL_TOL=" << STALL_TOL << " isStalled=" << isStalled + << " canIncrease=" << canIncrease << " will_reduce=" + << ((isStalled && !canIncrease) || (stallChange > 0.0)) << endl; + } + + /* Don't override canIncrease unless truly stalled (near zero or positive trend) */ + if (isStalled && !canIncrease) { + reduceCFL = true; + } else if (stallChange > 0.0) { + /* Only force reduction if residuals actually increased */ reduceCFL = true; } } @@ -2100,35 +2116,23 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, << " reduceCFL=" << reduceCFL << " canIncrease=" << canIncrease << endl; } - /* Check if we are hitting the min or max and adjust. */ - - if (CFL*CFLFactor <= CFLMin) { - CFL = CFLMin; - // nijso says: we might need to add this back in again when we have more complex - // cfl scaling for multigrid (happening in CMultiGridGeometry.cpp) - //CFLFactor = MGFactor[iMesh]; - } else if (CFL*CFLFactor >= CFLMax) { - CFL = CFLMax; - //CFLFactor = MGFactor[iMesh]; - } - - /* Debug output for min/max capping */ - if (iPoint == 0 && config->GetInnerIter() % 50 == 0 && rank == MASTER_NODE) { - cout << "After min/max check - CFL=" << CFL << " (min=" << CFLMin << ", max=" << CFLMax - << ", wanted=" << (CFL*CFLFactor) << ")" << endl; - } - /* If we detect a stalled nonlinear residual, then force the CFL for all points to the minimum temporarily to restart the ramp. */ if (resetCFL) { - CFL = CFLMin; - //CFLFactor = MGFactor[iMesh]; - } + CFL = CFLMin; + } else { + /* Apply the adjustment to the CFL first. */ + CFL *= CFLFactor; - /* Apply the adjustment to the CFL and store local values. */ + /* Then clamp to min/max limits. */ + CFL = max(CFLMin, min(CFLMax, CFL)); + } - CFL *= CFLFactor; + /* Debug output for min/max capping */ + if (iPoint == 0 && config->GetInnerIter() % 50 == 0 && rank == MASTER_NODE) { + cout << "After adjustment - CFL=" << CFL << " (min=" << CFLMin << ", max=" << CFLMax << ")" << endl; + } //cout << "CFL = " << CFL << " Factor = " << CFLFactor << endl; From 2b2996a911c394ad02a46afc937ab8d7a020890c Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 14 Dec 2025 16:28:26 +0100 Subject: [PATCH 32/41] make adaptcfl more modular --- SU2_CFD/include/solvers/CSolver.hpp | 2 + SU2_CFD/src/solvers/CSolver.cpp | 207 ++++++++++++++++++++++++---- 2 files changed, 184 insertions(+), 25 deletions(-) diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 9d417fcadb6c..20d6648e6954 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -1431,6 +1431,8 @@ class CSolver { */ void AdaptCFLNumber(CGeometry **geometry, CSolver ***solver_container, CConfig *config); + void ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, + CConfig *config, unsigned short iMesh); /*! * \brief Reset the local CFL adaption variables */ diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 20ff418bc53a..a2fb977e65fc 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1704,6 +1704,29 @@ void CSolver::ResetCFLAdapt() { NonLinRes_Counter = 0; } +void CSolver::ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, + CConfig *config, unsigned short iMesh) { + const su2double factor = 1.5; + const su2double CFL = factor * config->GetCFL(iMesh - 1); + + config->SetCFL(iMesh, CFL); + + CSolver *solverFlow = solver_container[FLOW_SOL]; + CSolver *solverTurb = solver_container[TURB_SOL]; + CSolver *solverSpecies = solver_container[SPECIES_SOL]; + + const su2double CFLTurbReduction = config->GetCFLRedCoeff_Turb(); + const su2double CFLSpeciesReduction = config->GetCFLRedCoeff_Species(); + + SU2_OMP_FOR_STAT(roundUpDiv(geometry->GetnPointDomain(), omp_get_max_threads())) + for (unsigned long iPoint = 0; iPoint < geometry->GetnPointDomain(); iPoint++) { + solverFlow->GetNodes()->SetLocalCFL(iPoint, CFL); + if (solverTurb) solverTurb->GetNodes()->SetLocalCFL(iPoint, CFL * CFLTurbReduction); + if (solverSpecies) solverSpecies->GetNodes()->SetLocalCFL(iPoint, CFL * CFLSpeciesReduction); + } + END_SU2_OMP_FOR +} + void CSolver::AdaptCFLNumber(CGeometry **geometry, CSolver ***solver_container, @@ -1724,7 +1747,15 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Number of iterations considered to check for stagnation. */ const auto Res_Count = min(100ul, config->GetnInner_Iter()-1); - static bool reduceCFL, resetCFL, canIncrease; + /* Damping: require consecutive iterations before acting */ + const unsigned long DAMPING_ITERS_REDUCE = 5; + const unsigned long DAMPING_ITERS_INCREASE = 10; + static unsigned long consecutiveReduceIters = 0; + static unsigned long consecutiveIncreaseIters = 0; + + static bool reduceCFL = false; + static bool resetCFL = false; + static bool canIncrease = false; for (unsigned short iMesh = 0; iMesh <= config->GetnMGLevels(); iMesh++) { if (iMesh == MESH_0) { @@ -1761,13 +1792,29 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); /* only change CFL number when larger than starting iteration */ - reduceCFL = (linRes > 1.2*linTol) && (iter >= startingIter); + bool shouldReduce = (linRes > 1.2*linTol) && (iter >= startingIter); + bool canIncrease_raw = (linRes < linTol) && (iter >= startingIter); + + /* Apply consecutive iteration damping */ + if (shouldReduce) { + consecutiveReduceIters++; + consecutiveIncreaseIters = 0; + } else if (canIncrease_raw) { + consecutiveIncreaseIters++; + consecutiveReduceIters = 0; + } else { + consecutiveReduceIters = 0; + consecutiveIncreaseIters = 0; + } - canIncrease = (linRes < linTol) && (iter >= startingIter); + reduceCFL = (consecutiveReduceIters >= DAMPING_ITERS_REDUCE); + canIncrease = (consecutiveIncreaseIters >= DAMPING_ITERS_INCREASE); if (rank == MASTER_NODE && iter % 50 == 0) { cout << "CFL Debug - Iter " << iter << ": linRes=" << linRes << " linTol=" << linTol - << " reduceCFL=" << reduceCFL << " canIncrease=" << canIncrease << " resetCFL=" << resetCFL << endl; + << " reduceCFL=" << reduceCFL << " (consec=" << consecutiveReduceIters << "/" << DAMPING_ITERS_REDUCE << ")" + << " canIncrease=" << canIncrease << " (consec=" << consecutiveIncreaseIters << "/" << DAMPING_ITERS_INCREASE << ")" + << " resetCFL=" << resetCFL << endl; } if (Res_Count > 0) { @@ -1838,6 +1885,62 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, } } + /* FAST DIVERGENCE DETECTION: Catch issues in 1-10 iterations before they become catastrophic. + These checks run before the longer-term oscillation detection below. */ + + if (config->GetInnerIter() >= 1) { + const unsigned long currIter = config->GetInnerIter(); + + /* 1. SINGLE-ITERATION SPIKE DETECTION: Catch catastrophic jumps immediately */ + su2double singleIterChange = New_Func - Old_Func; + bool spikeDetected = false; + bool catastrophicSpike = false; + + if (singleIterChange > 0.15) { /* 0.15 log10 jump in one iteration */ + spikeDetected = true; + reduceCFL = true; + if (singleIterChange > 0.3) { /* 0.3+ log units = catastrophic */ + catastrophicSpike = true; + resetCFL = true; + } + } + + /* 2. SHORT-WINDOW TREND DETECTION: Catch degradation over 10 iterations */ + bool shortTermDivergence = false; + if (currIter >= 10) { + const unsigned long SHORT_WINDOW = 10; + su2double recentChange = 0.0; + + /* Sum the last 10 delta values */ + for (unsigned long i = 0; i < SHORT_WINDOW; i++) { + unsigned long idx = (NonLinRes_Counter + Res_Count - 1 - i) % Res_Count; + recentChange += NonLinRes_Series[idx]; + } + + /* Average change per iteration over the short window */ + su2double recentSlope = recentChange / su2double(SHORT_WINDOW); + + /* Trigger if: residuals increasing OR stalling after initial convergence */ + if (recentChange > 0.1) { /* Net increase over 10 iterations */ + shortTermDivergence = true; + reduceCFL = true; + } else if (recentSlope > -0.0005 && Old_Func < -3.0) { + /* Convergence rate < 0.0005/iter after reaching -3.0 = stalling */ + shortTermDivergence = true; + reduceCFL = true; + } + } + + /* Debug output for fast detection mechanisms */ + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "FastDivergence Debug - Iter " << currIter + << ": singleIterChange=" << singleIterChange + << " spikeDetected=" << spikeDetected + << " catastrophicSpike=" << catastrophicSpike + << " shortTermDivergence=" << shortTermDivergence << endl; + } + } + /* Detect oscillations and divergence using peak-valley progression plus slope-based guards. This works alongside the flip-flop check above. - Maintain a history window (Res_Count up to 400 iters). @@ -1942,8 +2045,12 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, su2double slopePeaks = 0.0, slopeValleys = 0.0; bool haveSlopePeaks = (peaks.size() >= 2); bool haveSlopeValleys = (valleys.size() >= 2); - if (haveSlopePeaks) slopePeaks = slopeLSQ(peaks); - if (haveSlopeValleys) slopeValleys = slopeLSQ(valleys); + if (haveSlopePeaks && peaks.back().first > peaks.front().first) { + slopePeaks = slopeLSQ(peaks); + } + if (haveSlopeValleys && valleys.back().first > valleys.front().first) { + slopeValleys = slopeLSQ(valleys); + } /* If slope is exactly zero (extrema at same iteration), it's not meaningful - ignore it */ bool slopeFlat = false; @@ -1952,17 +2059,35 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, bool slopeUp = (haveSlopePeaks && slopePeaks > SLOPE_UP) || (haveSlopeValleys && slopeValleys > SLOPE_UP); - /* Check overall trend: even with oscillations, is the average moving down? */ + /* Check overall trend: compute average of ALL peaks and valleys to get robust trend. + In log10 space: convergence means residuals become more negative (decrease). + So we want: (avgNew - avgOld) < 0 for convergence. But we'll compute per-iteration + rate as (avgNew - avgOld)/dt, making NEGATIVE = convergence. */ su2double overallSlope = 0.0; if (peaks.size() >= 2 && valleys.size() >= 2) { - /* Use the oldest peak/valley and newest peak/valley to get overall trend */ - su2double oldestValue = min(peaks.front().second, valleys.front().second); - su2double newestValue = min(peaks.back().second, valleys.back().second); - unsigned long oldestIter = min(peaks.front().first, valleys.front().first); - unsigned long newestIter = max(peaks.back().first, valleys.back().first); - su2double dt = su2double(newestIter - oldestIter); - if (dt > 1e-12) { - overallSlope = (newestValue - oldestValue) / dt; + /* Average all peaks and valleys separately */ + su2double avgOldPeaks = 0.0, avgOldValleys = 0.0; + su2double avgNewPeaks = 0.0, avgNewValleys = 0.0; + unsigned long cntOld = 0, cntNew = 0; + + /* Split extrema into old half and new half */ + unsigned long midIter = (peaks.front().first + peaks.back().first) / 2; + + for (const auto& p : peaks) { + if (p.first <= midIter) { avgOldPeaks += p.second; cntOld++; } + else { avgNewPeaks += p.second; cntNew++; } + } + for (const auto& v : valleys) { + if (v.first <= midIter) { avgOldValleys += v.second; cntOld++; } + else { avgNewValleys += v.second; cntNew++; } + } + + if (cntOld > 0 && cntNew > 0) { + su2double avgOld = (avgOldPeaks + avgOldValleys) / cntOld; + su2double avgNew = (avgNewPeaks + avgNewValleys) / cntNew; + /* Slope = (avgNew - avgOld) / dt: NEGATIVE = convergence (residuals decreasing) */ + unsigned long dt = peaks.back().first - peaks.front().first; + if (dt > 0) overallSlope = (avgNew - avgOld) / su2double(dt); } } @@ -1973,22 +2098,40 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* If all slopes are zero (extrema at same time or too close), don't treat as oscillation */ bool allSlopesZero = (fabs(slopePeaks) < 1e-12 && fabs(slopeValleys) < 1e-12 && fabs(overallSlope) < 1e-12); - /* Oscillation is only bad if slopes are problematic AND overall trend is not improving */ + /* Oscillation is only bad if slopes are problematic AND overall trend is not improving. + overallSlope = (avgNew - avgOld)/dt: NEGATIVE = convergence, POSITIVE = divergence */ bool oscillationDetected = false; if (haveData && !allSlopesZero) { - oscillationDetected = (progressionFail || (slopeFlat && overallSlope >= -1.0e-3) || slopeUp); + oscillationDetected = (progressionFail || (slopeFlat && overallSlope > -0.001) || slopeUp); } if (rank == MASTER_NODE && currIter % 50 == 0 && haveData) { cout << "Oscillation Debug - Iter " << currIter << ": peaks=" << peaks.size() << " valleys=" << valleys.size() << " slopePeaks=" << slopePeaks << " slopeValleys=" << slopeValleys - << " overallSlope=" << overallSlope + << " overallSlope=" << overallSlope << " (neg=converge, pos=diverge)" << " slopeFlat=" << slopeFlat << " progressionFail=" << progressionFail << " oscillationDetected=" << oscillationDetected << endl; } - /* If overall trend is good (decreasing), don't block CFL increase */ - if (overallSlope < -1.0e-3) { + /* If overall trend shows strong convergence (negative slope), don't block CFL increase */ + if (overallSlope < -0.001) { + oscillationDetected = false; + } + + /* Compute totalChange early to use for stronger override logic */ + su2double totalChange = 0.0; + for (const auto& v : NonLinRes_Series) totalChange += v; + + /* STRONG OVERRIDE: If totalChange shows convergence over buffer, ignore oscillation detection. + This prevents false positives from noisy extrema when actual trend is good. + Relaxed from -0.1 to -0.01 to handle slow late-stage convergence. */ + if (totalChange < -0.01) { /* At least 0.01 log10 improvement over buffer */ + oscillationDetected = false; + } + + /* Also disable slopeUp detection if buffer shows net convergence - prevents false positives + from noise in individual valley slopes when overall trend is clearly downward */ + if (totalChange < 0.0 && slopeUp) { oscillationDetected = false; } @@ -2000,12 +2143,26 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, oscillationCounter = 1; } } else { - oscillationCounter = 0; + /* Decay counter aggressively when not detecting oscillation */ + if (oscillationCounter > 0) { + oscillationCounter = max(0ul, oscillationCounter - 5); + } } previousOscillation = oscillationDetected; /* Only act on oscillation if it persists for at least 3 checks */ bool persistentOscillation = (oscillationCounter >= 3); + + /* EMERGENCY RESET: If counter is very high but we have clear convergence, reset completely. + This handles cases where stale detections accumulated and won't decay fast enough. + Use -0.01 threshold to match the main override logic. */ + if (oscillationCounter > 50 && totalChange < -0.01) { + oscillationCounter = 0; + persistentOscillation = false; + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << " Emergency reset: oscillation counter cleared due to strong convergence" << endl; + } + } if (rank == MASTER_NODE && currIter % 50 == 0) { cout << "Persistence Debug - Iter " << currIter << ": oscillationCounter=" << oscillationCounter << " persistentOscillation=" << persistentOscillation << endl; @@ -2014,14 +2171,14 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, reduceCFL |= true; } - /* Strong divergence safety: if the total change over the full buffer is large positive, reset. */ - su2double totalChange = 0.0; - for (const auto& v : NonLinRes_Series) totalChange += v; + /* Strong divergence safety: if the total change over the full buffer is large positive, reset. + totalChange already computed above for override logic. + Only trigger slopeUp reset if also seeing net divergence in buffer. */ if (rank == MASTER_NODE && currIter % 50 == 0) { cout << "Divergence Debug - Iter " << currIter << ": totalChange=" << totalChange << " slopeUp=" << slopeUp << endl; } - if (totalChange > 2.0 || slopeUp) { // 2 orders worse or clear rising slope + if (totalChange > 2.0 || (slopeUp && totalChange > 0.0)) { // 2 orders worse OR slope issues with net divergence resetCFL = true; NonLinRes_Counter = 0; for (auto& val : NonLinRes_Series) val = 0.0; From 15ed48def5d3a23a610275d4bd27216eff557945 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 14 Dec 2025 20:26:28 +0100 Subject: [PATCH 33/41] make adaptcfl more modular 2 --- SU2_CFD/include/solvers/CSolver.hpp | 18 ++ SU2_CFD/src/solvers/CSolver.cpp | 316 +++++++++++++++------------- 2 files changed, 185 insertions(+), 149 deletions(-) diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 20d6648e6954..943abd15dc99 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -1431,8 +1431,26 @@ class CSolver { */ void AdaptCFLNumber(CGeometry **geometry, CSolver ***solver_container, CConfig *config); +private: + + su2double ComputeAdjustedCFL(su2double currentCFL, su2double underRelaxation, + bool reduceCFL, bool resetCFL, bool canIncrease, + unsigned long iter, su2double startingIter, + su2double CFLMin, su2double CFLMax, + su2double CFLFactorDecrease, su2double CFLFactorIncrease); + + void ApplyCFLToAllPoints(CGeometry *geometry, CSolver **solver_container, + CConfig *config, unsigned short iMesh, + bool reduceCFL, bool resetCFL, bool canIncrease, + su2double startingIter); + + void PerformCFLReductions(CGeometry *geometry, CConfig *config, unsigned short iMesh); + void ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh); + +public: + /*! * \brief Reset the local CFL adaption variables */ diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index a2fb977e65fc..8716c3a81279 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1704,6 +1704,152 @@ void CSolver::ResetCFLAdapt() { NonLinRes_Counter = 0; } + + +su2double CSolver::ComputeAdjustedCFL(su2double currentCFL, + su2double underRelaxation, + bool reduceCFL, + bool resetCFL, + bool canIncrease, + unsigned long iter, + su2double startingIter, + su2double CFLMin, + su2double CFLMax, + su2double CFLFactorDecrease, + su2double CFLFactorIncrease) { + /* If we detect a stalled nonlinear residual, force CFL to minimum */ + if (resetCFL) { + return CFLMin; + } + + /* Determine CFL adjustment factor based on under-relaxation and convergence flags. + Only apply under-relaxation based reduction after starting iteration. */ + su2double CFLFactor = 1.0; + if ((iter >= startingIter && underRelaxation < 0.1) || reduceCFL) { + CFLFactor = CFLFactorDecrease; + } else if ((iter >= startingIter && underRelaxation >= 0.1 && underRelaxation < 1.0) || !canIncrease) { + CFLFactor = 1.0; + } else { + CFLFactor = CFLFactorIncrease; + } + + /* Apply the adjustment factor */ + su2double CFL = currentCFL * CFLFactor; + + /* Clamp to min/max limits */ + CFL = max(CFLMin, min(CFLMax, CFL)); + + return CFL; +} + +void CSolver::ApplyCFLToAllPoints(CGeometry *geometry, + CSolver **solver_container, + CConfig *config, + unsigned short iMesh, + bool reduceCFL, + bool resetCFL, + bool canIncrease, + su2double startingIter) { + const bool fullComms = (config->GetComm_Level() == COMM_FULL); + const unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + + CSolver *solverFlow = solver_container[FLOW_SOL]; + CSolver *solverTurb = solver_container[TURB_SOL]; + CSolver *solverSpecies = solver_container[SPECIES_SOL]; + + const su2double CFLMin = config->GetCFL_AdaptParam(2); + const su2double CFLMax = config->GetCFL_AdaptParam(3); + const su2double CFLFactorDecrease = config->GetCFL_AdaptParam(0); + const su2double CFLFactorIncrease = config->GetCFL_AdaptParam(1); + const su2double CFLTurbReduction = config->GetCFLRedCoeff_Turb(); + const su2double CFLSpeciesReduction = config->GetCFLRedCoeff_Species(); + + su2double myCFLMin = 1e30, myCFLMax = 0.0, myCFLSum = 0.0; + + SU2_OMP_MASTER + if (fullComms) { + Min_CFL_Local = 1e30; + Max_CFL_Local = 0.0; + Avg_CFL_Local = 0.0; + } + END_SU2_OMP_MASTER + + SU2_OMP_FOR_STAT(roundUpDiv(geometry->GetnPointDomain(), omp_get_max_threads())) + for (unsigned long iPoint = 0; iPoint < geometry->GetnPointDomain(); iPoint++) { + /* Get current local CFL and under-relaxation parameters */ + su2double currentCFL = solverFlow->GetNodes()->GetLocalCFL(iPoint); + su2double underRelaxationFlow = solverFlow->GetNodes()->GetUnderRelaxation(iPoint); + su2double underRelaxationTurb = 1.0; + if (solverTurb) + underRelaxationTurb = solverTurb->GetNodes()->GetUnderRelaxation(iPoint); + const su2double underRelaxation = min(underRelaxationFlow, underRelaxationTurb); + + /* Compute adjusted CFL using the dedicated function */ + su2double CFL = ComputeAdjustedCFL(currentCFL, underRelaxation, + reduceCFL, resetCFL, canIncrease, + iter, startingIter, + CFLMin, CFLMax, + CFLFactorDecrease, CFLFactorIncrease); + + /* Debug output for first point every 50 iterations or at iteration 1 */ + if (iPoint == 0 && (config->GetInnerIter() % 50 == 0 || config->GetInnerIter() == 1) && rank == MASTER_NODE) { + cout << "Point " << iPoint << " Iter=" << iter << " startingIter=" << startingIter + << " - underRelax=" << underRelaxation + << " CFL_before=" << currentCFL << " CFL_after=" << CFL + << " reduceCFL=" << reduceCFL << " canIncrease=" << canIncrease << endl; + } + + /* Set the adjusted CFL for all solvers */ + solverFlow->GetNodes()->SetLocalCFL(iPoint, CFL); + if (solverTurb) { + solverTurb->GetNodes()->SetLocalCFL(iPoint, CFL * CFLTurbReduction); + } + if (solverSpecies) { + solverSpecies->GetNodes()->SetLocalCFL(iPoint, CFL * CFLSpeciesReduction); + } + + /* Store min/max/sum for reporting */ + if (fullComms) { + myCFLMin = min(CFL, myCFLMin); + myCFLMax = max(CFL, myCFLMax); + myCFLSum += CFL; + } + } + END_SU2_OMP_FOR + + /* Reduce the min/max/avg local CFL numbers */ + if (fullComms) { + SU2_OMP_CRITICAL + { /* OpenMP reduction */ + Min_CFL_Local = min(Min_CFL_Local, myCFLMin); + Max_CFL_Local = max(Max_CFL_Local, myCFLMax); + Avg_CFL_Local += myCFLSum; + } + END_SU2_OMP_CRITICAL + + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + { /* MPI reduction and config update */ + PerformCFLReductions(geometry, config, iMesh); + if (rank == MASTER_NODE) { + cout << "imesh=" << iMesh << " Avg CFL=" << Avg_CFL_Local << endl; + } + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + } +} + +void CSolver::PerformCFLReductions(CGeometry *geometry, CConfig *config, unsigned short iMesh) { + su2double myCFLMin = Min_CFL_Local, myCFLMax = Max_CFL_Local, myCFLSum = Avg_CFL_Local; + + SU2_MPI::Allreduce(&myCFLMin, &Min_CFL_Local, 1, MPI_DOUBLE, MPI_MIN, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(&myCFLMax, &Max_CFL_Local, 1, MPI_DOUBLE, MPI_MAX, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(&myCFLSum, &Avg_CFL_Local, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + + Avg_CFL_Local /= su2double(geometry->GetGlobal_nPointDomain()); + + config->SetCFL(iMesh, Avg_CFL_Local); +} + void CSolver::ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh) { const su2double factor = 1.5; @@ -1735,7 +1881,6 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Adapt the CFL number on all multigrid levels using an exponential progression with under-relaxation approach. */ - //vector MGFactor(config->GetnMGLevels()+1,1.0); const su2double CFLFactorDecrease = config->GetCFL_AdaptParam(0); const su2double CFLFactorIncrease = config->GetCFL_AdaptParam(1); const su2double CFLMin = config->GetCFL_AdaptParam(2); @@ -1787,10 +1932,12 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { /* Only the master thread updates the shared variables. */ - /* Check if we should decrease or if we can increase, the 20% is to avoid flip-flopping. */ - resetCFL = linRes > 0.99; unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + /* Check if we should decrease or if we can increase, the 20% is to avoid flip-flopping. */ + /* Only reset CFL after starting iteration to allow initial convergence analysis */ + resetCFL = (linRes > 0.99) && (iter >= startingIter); + /* only change CFL number when larger than starting iteration */ bool shouldReduce = (linRes > 1.2*linTol) && (iter >= startingIter); bool canIncrease_raw = (linRes < linTol) && (iter >= startingIter); @@ -1886,9 +2033,10 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, } /* FAST DIVERGENCE DETECTION: Catch issues in 1-10 iterations before they become catastrophic. - These checks run before the longer-term oscillation detection below. */ + These checks run before the longer-term oscillation detection below. + Only activate after startingIter to allow initial convergence analysis. */ - if (config->GetInnerIter() >= 1) { + if (iter >= startingIter) { const unsigned long currIter = config->GetInnerIter(); /* 1. SINGLE-ITERATION SPIKE DETECTION: Catch catastrophic jumps immediately */ @@ -1947,9 +2095,10 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, - Extract peaks/valleys with minimum separation to avoid noise. - Require new peaks/valleys to progress downward; otherwise flag. - Use least-squares slope on last 3 peaks/valleys to catch divergence. - - Forget extrema older than a lookback window (300 iters). */ + - Forget extrema older than a lookback window (300 iters). + Only activate after startingIter to allow initial convergence analysis. */ - if (config->GetInnerIter() >= 3) { + if (iter >= startingIter && config->GetInnerIter() >= 3) { const unsigned long EXT_WINDOW = 300; const unsigned long MIN_SEPARATION = 30; /* iters between extrema to avoid noise */ const su2double VALUE_TOL = 0.05; /* log10 residual tolerance for peak/valley improvement */ @@ -2220,155 +2369,24 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, } /* End safe global access, now all threads update the CFL number. */ END_SU2_OMP_SAFE_GLOBAL_ACCESS - /* Loop over all points on this grid and apply CFL adaption. */ - - su2double myCFLMin = 1e30, myCFLMax = 0.0, myCFLSum = 0.0; - const su2double CFLTurbReduction = config->GetCFLRedCoeff_Turb(); - const su2double CFLSpeciesReduction = config->GetCFLRedCoeff_Species(); - - SU2_OMP_MASTER - if (fullComms) { - Min_CFL_Local = 1e30; - Max_CFL_Local = 0.0; - Avg_CFL_Local = 0.0; - } - END_SU2_OMP_MASTER - - SU2_OMP_FOR_STAT(roundUpDiv(geometry[iMesh]->GetnPointDomain(),omp_get_max_threads())) - for (unsigned long iPoint = 0; iPoint < geometry[iMesh]->GetnPointDomain(); iPoint++) { - - /* Get the current local flow CFL number at this point. */ - - su2double CFL = solverFlow->GetNodes()->GetLocalCFL(iPoint); - - /* Get the current under-relaxation parameters that were computed - during the previous nonlinear update. If we have a turbulence model, - take the minimum under-relaxation parameter between the mean flow - and turbulence systems. */ - - su2double underRelaxationFlow = solverFlow->GetNodes()->GetUnderRelaxation(iPoint); - su2double underRelaxationTurb = 1.0; - if (solverTurb) - underRelaxationTurb = solverTurb->GetNodes()->GetUnderRelaxation(iPoint); - const su2double underRelaxation = min(underRelaxationFlow,underRelaxationTurb); - - /* If we apply a small under-relaxation parameter for stability, - then we should reduce the CFL before the next iteration. If we - are able to add the entire nonlinear update (under-relaxation = 1) - then we schedule an increase the CFL number for the next iteration. */ - - su2double CFLFactor = 1.0; - if (underRelaxation < 0.1 || reduceCFL) { - CFLFactor = CFLFactorDecrease; - } else if ((underRelaxation >= 0.1 && underRelaxation < 1.0) || !canIncrease) { - CFLFactor = 1.0; - } else { - CFLFactor = CFLFactorIncrease; - } - - /* Debug output for first point every 50 iterations */ - if (iPoint == 0 && config->GetInnerIter() % 50 == 0 && rank == MASTER_NODE) { - cout << "Point " << iPoint << " - underRelax=" << underRelaxation - << " CFLFactor=" << CFLFactor << " CFL=" << CFL - << " reduceCFL=" << reduceCFL << " canIncrease=" << canIncrease << endl; - } - - /* If we detect a stalled nonlinear residual, then force the CFL - for all points to the minimum temporarily to restart the ramp. */ - - if (resetCFL) { - CFL = CFLMin; - } else { - /* Apply the adjustment to the CFL first. */ - CFL *= CFLFactor; - - /* Then clamp to min/max limits. */ - CFL = max(CFLMin, min(CFLMax, CFL)); - } - - /* Debug output for min/max capping */ - if (iPoint == 0 && config->GetInnerIter() % 50 == 0 && rank == MASTER_NODE) { - cout << "After adjustment - CFL=" << CFL << " (min=" << CFLMin << ", max=" << CFLMax << ")" << endl; - } - - //cout << "CFL = " << CFL << " Factor = " << CFLFactor << endl; - - solverFlow->GetNodes()->SetLocalCFL(iPoint, CFL); - if (solverTurb) { - solverTurb->GetNodes()->SetLocalCFL(iPoint, CFL * CFLTurbReduction); - } - if (solverSpecies) { - solverSpecies->GetNodes()->SetLocalCFL(iPoint, CFL * CFLSpeciesReduction); - } - - /* Store min and max CFL for reporting on the fine grid. */ - - if (fullComms) { - myCFLMin = min(CFL,myCFLMin); - myCFLMax = max(CFL,myCFLMax); - myCFLSum += CFL; - } + /* Apply CFL adaptation to all points on this grid */ + ApplyCFLToAllPoints(geometry[iMesh], solver_container[iMesh], config, iMesh, + reduceCFL, resetCFL, canIncrease, startingIter); + } else { + /* For coarse grids, apply CFL using the dedicated function */ + ApplyCFLToCoarseGrid(geometry[iMesh], solver_container[iMesh], config, iMesh); + if (rank == MASTER_NODE) { + cout << "imesh=" << iMesh << " Avg CFL=" << config->GetCFL(iMesh) << endl; } - END_SU2_OMP_FOR - - /* Reduce the min/max/avg local CFL numbers. */ - - if (fullComms) { - SU2_OMP_CRITICAL - { /* OpenMP reduction. */ - Min_CFL_Local = min(Min_CFL_Local,myCFLMin); - Max_CFL_Local = max(Max_CFL_Local,myCFLMax); - Avg_CFL_Local += myCFLSum; - } - END_SU2_OMP_CRITICAL + } +} +} - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { /* MPI reduction. */ - myCFLMin = Min_CFL_Local; myCFLMax = Max_CFL_Local; myCFLSum = Avg_CFL_Local; - SU2_MPI::Allreduce(&myCFLMin, &Min_CFL_Local, 1, MPI_DOUBLE, MPI_MIN, SU2_MPI::GetComm()); - SU2_MPI::Allreduce(&myCFLMax, &Max_CFL_Local, 1, MPI_DOUBLE, MPI_MAX, SU2_MPI::GetComm()); - SU2_MPI::Allreduce(&myCFLSum, &Avg_CFL_Local, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - Avg_CFL_Local /= su2double(geometry[iMesh]->GetGlobal_nPointDomain()); - - /*--- Update the config CFL for MESH_0 with the average adapted value. ---*/ - cout << "imesh="<< iMesh <<"Avg CFL="<SetCFL(iMesh, Avg_CFL_Local); - } - END_SU2_OMP_SAFE_GLOBAL_ACCESS - } - } else { - const su2double factor = 1.5; - const su2double CFL = factor * config->GetCFL(iMesh - 1); - cout << "imesh="<< iMesh <<"Avg CFL="<SetCFL(iMesh, CFL); - - /* Apply the CFL to all points on this coarse mesh. */ - CSolver *solverFlow = solver_container[iMesh][FLOW_SOL]; - CSolver *solverTurb = solver_container[iMesh][TURB_SOL]; - CSolver *solverSpecies = solver_container[iMesh][SPECIES_SOL]; - - const su2double CFLTurbReduction = config->GetCFLRedCoeff_Turb(); - const su2double CFLSpeciesReduction = config->GetCFLRedCoeff_Species(); - - SU2_OMP_FOR_STAT(roundUpDiv(geometry[iMesh]->GetnPointDomain(),omp_get_max_threads())) - for (unsigned long iPoint = 0; iPoint < geometry[iMesh]->GetnPointDomain(); iPoint++) { - solverFlow->GetNodes()->SetLocalCFL(iPoint, CFL); - if (solverTurb) { - solverTurb->GetNodes()->SetLocalCFL(iPoint, CFL * CFLTurbReduction); - } - if (solverSpecies) { - solverSpecies->GetNodes()->SetLocalCFL(iPoint, CFL * CFLSpeciesReduction); - } - } - END_SU2_OMP_FOR - } -} -} void CSolver::SetResidual_RMS(const CGeometry *geometry, const CConfig *config) { From 0d89d93b94eadc83041d7d7b1f962d27f3532e09 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 14 Dec 2025 21:03:05 +0100 Subject: [PATCH 34/41] make adaptcfl more modular 3 --- SU2_CFD/include/solvers/CSolver.hpp | 16 +++++ SU2_CFD/src/solvers/CSolver.cpp | 103 +++++++++++++++++----------- 2 files changed, 78 insertions(+), 41 deletions(-) diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 943abd15dc99..f28eaa229f47 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -1433,12 +1433,28 @@ class CSolver { private: + /*! \brief Data structures for modular CFL adaptation */ + + struct CFLAdaptParams { + su2double CFLFactorDecrease, CFLFactorIncrease; + su2double CFLMin, CFLMax; + su2double acceptableLinTol, startingIter; + unsigned long Res_Count, stallWindow; + }; + su2double ComputeAdjustedCFL(su2double currentCFL, su2double underRelaxation, bool reduceCFL, bool resetCFL, bool canIncrease, unsigned long iter, su2double startingIter, su2double CFLMin, su2double CFLMax, su2double CFLFactorDecrease, su2double CFLFactorIncrease); + /*! \brief Helper methods for modular CFL adaptation */ + + CFLAdaptParams InitializeCFLAdaptParams(CConfig *config); + + bool DetectFlipFlop(const CFLAdaptParams ¶ms, CConfig *config); + + void ApplyCFLToAllPoints(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh, bool reduceCFL, bool resetCFL, bool canIncrease, diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 8716c3a81279..99bd367c0bde 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1873,6 +1873,44 @@ void CSolver::ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_contain END_SU2_OMP_FOR } +bool CSolver::DetectFlipFlop(const CFLAdaptParams ¶ms, CConfig *config) { + unsigned long signChanges = 0; + su2double totalChange = 0.0; + auto prev = NonLinRes_Series.front(); + + for (const auto& val : NonLinRes_Series) { + totalChange += val; + signChanges += (prev > 0) ^ (val > 0); + prev = val; + } + + bool flipFlopDetected = (signChanges > params.Res_Count/4) && (totalChange > -0.5); + unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + + if (rank == MASTER_NODE && iter % 50 == 0) { + cout << "FlipFlop Debug - Iter " << iter << ": signChanges=" << signChanges + << " threshold=" << params.Res_Count/4 << " totalChange=" << totalChange + << " flipFlopDetected=" << flipFlopDetected << endl; + } + + if (totalChange > 2.0) return true; + + return flipFlopDetected; +} + +CSolver::CFLAdaptParams CSolver::InitializeCFLAdaptParams(CConfig *config) { + CFLAdaptParams params; + params.CFLFactorDecrease = config->GetCFL_AdaptParam(0); + params.CFLFactorIncrease = config->GetCFL_AdaptParam(1); + params.CFLMin = config->GetCFL_AdaptParam(2); + params.CFLMax = config->GetCFL_AdaptParam(3); + params.acceptableLinTol = config->GetCFL_AdaptParam(4); + params.startingIter = config->GetCFL_AdaptParam(5); + params.Res_Count = min(100ul, config->GetnInner_Iter()-1); + params.stallWindow = min(100ul, params.Res_Count); + + return params; + } void CSolver::AdaptCFLNumber(CGeometry **geometry, CSolver ***solver_container, @@ -1881,16 +1919,8 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Adapt the CFL number on all multigrid levels using an exponential progression with under-relaxation approach. */ - const su2double CFLFactorDecrease = config->GetCFL_AdaptParam(0); - const su2double CFLFactorIncrease = config->GetCFL_AdaptParam(1); - const su2double CFLMin = config->GetCFL_AdaptParam(2); - const su2double CFLMax = config->GetCFL_AdaptParam(3); - const su2double acceptableLinTol = config->GetCFL_AdaptParam(4); - const su2double startingIter = config->GetCFL_AdaptParam(5); - const bool fullComms = (config->GetComm_Level() == COMM_FULL); - - /* Number of iterations considered to check for stagnation. */ - const auto Res_Count = min(100ul, config->GetnInner_Iter()-1); + const CFLAdaptParams params = InitializeCFLAdaptParams(config); + const bool fullComms = (config->GetComm_Level() == COMM_FULL); /* Damping: require consecutive iterations before acting */ const unsigned long DAMPING_ITERS_REDUCE = 5; @@ -1923,7 +1953,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, const su2double linRes = max(solverFlow->GetResLinSolver(), max(linResTurb, linResSpecies)); /* Tolerance limited to an acceptable value. */ - const su2double linTol = max(acceptableLinTol, config->GetLinear_Solver_Error()); + const su2double linTol = max(params.acceptableLinTol, config->GetLinear_Solver_Error()); /* Check that we are meeting our nonlinear residual reduction target over time so that we do not get stuck in limit cycles, this is done @@ -1936,11 +1966,11 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Check if we should decrease or if we can increase, the 20% is to avoid flip-flopping. */ /* Only reset CFL after starting iteration to allow initial convergence analysis */ - resetCFL = (linRes > 0.99) && (iter >= startingIter); + resetCFL = (linRes > 0.99) && (iter >= params.startingIter); /* only change CFL number when larger than starting iteration */ - bool shouldReduce = (linRes > 1.2*linTol) && (iter >= startingIter); - bool canIncrease_raw = (linRes < linTol) && (iter >= startingIter); + bool shouldReduce = (linRes > 1.2*linTol) && (iter >= params.startingIter); + bool canIncrease_raw = (linRes < linTol) && (iter >= params.startingIter); /* Apply consecutive iteration damping */ if (shouldReduce) { @@ -1964,9 +1994,9 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, << " resetCFL=" << resetCFL << endl; } - if (Res_Count > 0) { + if (params.Res_Count > 0) { Old_Func = New_Func; - if (NonLinRes_Series.empty()) NonLinRes_Series.resize(Res_Count,0.0); + if (NonLinRes_Series.empty()) NonLinRes_Series.resize(params.Res_Count,0.0); /* Sum the RMS residuals for all equations. */ @@ -2002,29 +2032,19 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Increment the counter, if we hit the max size, then start over. */ NonLinRes_Counter++; - if (NonLinRes_Counter == Res_Count) NonLinRes_Counter = 0; + if (NonLinRes_Counter == params.Res_Count) NonLinRes_Counter = 0; /* Fast flip-flop detection using XOR sign changes (from old code). This catches rapid oscillations that the peak-valley logic might miss. */ - if (config->GetInnerIter() >= Res_Count) { - unsigned long signChanges = 0; + if (config->GetInnerIter() >= params.Res_Count) { + bool flipFlopOrDivergence = DetectFlipFlop(params, config); + reduceCFL |= flipFlopOrDivergence; + + /* Check for catastrophic divergence and reset if needed */ su2double totalChange = 0.0; - auto prev = NonLinRes_Series.front(); for (const auto& val : NonLinRes_Series) { totalChange += val; - signChanges += (prev > 0) ^ (val > 0); - prev = val; } - /* Flip-flop: many sign changes with little net progress */ - bool flipFlopDetected = (signChanges > Res_Count/4) && (totalChange > -0.5); - unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); - if (rank == MASTER_NODE && iter % 50 == 0) { - cout << "FlipFlop Debug - Iter " << iter << ": signChanges=" << signChanges - << " threshold=" << Res_Count/4 << " totalChange=" << totalChange - << " flipFlopDetected=" << flipFlopDetected << endl; - } - reduceCFL |= flipFlopDetected; - if (totalChange > 2.0) { // orders of magnitude divergence resetCFL = true; NonLinRes_Counter = 0; @@ -2036,7 +2056,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, These checks run before the longer-term oscillation detection below. Only activate after startingIter to allow initial convergence analysis. */ - if (iter >= startingIter) { + if (iter >= params.startingIter) { const unsigned long currIter = config->GetInnerIter(); /* 1. SINGLE-ITERATION SPIKE DETECTION: Catch catastrophic jumps immediately */ @@ -2061,7 +2081,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Sum the last 10 delta values */ for (unsigned long i = 0; i < SHORT_WINDOW; i++) { - unsigned long idx = (NonLinRes_Counter + Res_Count - 1 - i) % Res_Count; + unsigned long idx = (NonLinRes_Counter + params.Res_Count - 1 - i) % params.Res_Count; recentChange += NonLinRes_Series[idx]; } @@ -2088,6 +2108,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, << " shortTermDivergence=" << shortTermDivergence << endl; } } + /*--- End of fast divergence detection ---*/ /* Detect oscillations and divergence using peak-valley progression plus slope-based guards. This works alongside the flip-flop check above. @@ -2098,7 +2119,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, - Forget extrema older than a lookback window (300 iters). Only activate after startingIter to allow initial convergence analysis. */ - if (iter >= startingIter && config->GetInnerIter() >= 3) { + if (iter >= params.startingIter && config->GetInnerIter() >= 3) { const unsigned long EXT_WINDOW = 300; const unsigned long MIN_SEPARATION = 30; /* iters between extrema to avoid noise */ const su2double VALUE_TOL = 0.05; /* log10 residual tolerance for peak/valley improvement */ @@ -2111,11 +2132,11 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Reconstruct New_Func history from the delta buffer. */ vector funcHistory; - funcHistory.reserve(Res_Count); + funcHistory.reserve(params.Res_Count); su2double curr = New_Func; funcHistory.push_back(curr); - for (unsigned long i = 1; i < Res_Count; i++) { - unsigned long idx = (NonLinRes_Counter + Res_Count - i) % Res_Count; + for (unsigned long i = 1; i < params.Res_Count; i++) { + unsigned long idx = (NonLinRes_Counter + params.Res_Count - i) % params.Res_Count; curr -= NonLinRes_Series[idx]; funcHistory.push_back(curr); } @@ -2336,11 +2357,11 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Stagnation guard: if over a recent window there is essentially no improvement, lower CFL to escape the stall. Only trigger if we're truly stalled (near zero or positive change) and not conflicting with other indicators showing good progress. */ - const unsigned long STALL_WINDOW = min(100ul, Res_Count); + const unsigned long STALL_WINDOW = min(100ul, params.Res_Count); if (config->GetInnerIter() >= STALL_WINDOW) { su2double stallChange = 0.0; for (unsigned long i = 0; i < STALL_WINDOW; i++) { - unsigned long idx = (NonLinRes_Counter + Res_Count - 1 - i) % Res_Count; + unsigned long idx = (NonLinRes_Counter + params.Res_Count - 1 - i) % params.Res_Count; stallChange += NonLinRes_Series[idx]; } /* Only flag as stalled if change is very small or positive, AND we're not already @@ -2371,7 +2392,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, /* Apply CFL adaptation to all points on this grid */ ApplyCFLToAllPoints(geometry[iMesh], solver_container[iMesh], config, iMesh, - reduceCFL, resetCFL, canIncrease, startingIter); + reduceCFL, resetCFL, canIncrease, params.startingIter); } else { /* For coarse grids, apply CFL using the dedicated function */ ApplyCFLToCoarseGrid(geometry[iMesh], solver_container[iMesh], config, iMesh); From d2df3c796a7811f7e1d04791413d7d98a2372fc1 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 14 Dec 2025 21:54:00 +0100 Subject: [PATCH 35/41] make adaptcfl more modular 4 --- SU2_CFD/include/solvers/CSolver.hpp | 15 + SU2_CFD/src/solvers/CSolver.cpp | 862 ++++++++++++++-------------- 2 files changed, 461 insertions(+), 416 deletions(-) diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index f28eaa229f47..a997c84b5236 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -1454,6 +1454,21 @@ class CSolver { bool DetectFlipFlop(const CFLAdaptParams ¶ms, CConfig *config); + void DetermineLinearSolverBasedCFLFlags(const CFLAdaptParams ¶ms, CConfig *config, + su2double linRes, su2double linTol, + bool &reduceCFL, bool &resetCFL, bool &canIncrease); + + void TrackResidualHistory(const CFLAdaptParams ¶ms, CConfig *config, + CSolver **solver_container, unsigned short iMesh, + su2double &New_Func, su2double &Old_Func, + bool &reduceCFL, bool &resetCFL); + + void DetectFastDivergence(const CFLAdaptParams ¶ms, CConfig *config, + su2double New_Func, su2double Old_Func, + bool &reduceCFL, bool &resetCFL); + + void DetectPeakValley(const CFLAdaptParams ¶ms, CConfig *config, + su2double New_Func, bool &reduceCFL, bool &resetCFL); void ApplyCFLToAllPoints(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh, diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 99bd367c0bde..d41fcacc9296 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1898,6 +1898,61 @@ bool CSolver::DetectFlipFlop(const CFLAdaptParams ¶ms, CConfig *config) { return flipFlopDetected; } +void CSolver::DetectFastDivergence(const CFLAdaptParams ¶ms, CConfig *config, + su2double New_Func, su2double Old_Func, + bool &reduceCFL, bool &resetCFL) { + const unsigned long currIter = config->GetInnerIter(); + + /* 1. SINGLE-ITERATION SPIKE DETECTION: Catch catastrophic jumps immediately */ + su2double singleIterChange = New_Func - Old_Func; + bool spikeDetected = false; + bool catastrophicSpike = false; + + if (singleIterChange > 0.15) { /* 0.15 log10 jump in one iteration */ + spikeDetected = true; + reduceCFL = true; + if (singleIterChange > 0.3) { /* 0.3+ log units = catastrophic */ + catastrophicSpike = true; + resetCFL = true; + } + } + + /* 2. SHORT-WINDOW TREND DETECTION: Catch degradation over 10 iterations */ + bool shortTermDivergence = false; + if (currIter >= 10) { + const unsigned long SHORT_WINDOW = 10; + su2double recentChange = 0.0; + + /* Sum the last 10 delta values */ + for (unsigned long i = 0; i < SHORT_WINDOW; i++) { + unsigned long idx = (NonLinRes_Counter + params.Res_Count - 1 - i) % params.Res_Count; + recentChange += NonLinRes_Series[idx]; + } + + /* Average change per iteration over the short window */ + su2double recentSlope = recentChange / su2double(SHORT_WINDOW); + + /* Trigger if: residuals increasing OR stalling after initial convergence */ + if (recentChange > 0.1) { /* Net increase over 10 iterations */ + shortTermDivergence = true; + reduceCFL = true; + } else if (recentSlope > -0.0005 && Old_Func < -3.0) { + /* Convergence rate < 0.0005/iter after reaching -3.0 = stalling */ + shortTermDivergence = true; + reduceCFL = true; + } + } + + /* Debug output for fast detection mechanisms */ + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "FastDivergence Debug - Iter " << currIter + << ": singleIterChange=" << singleIterChange + << " spikeDetected=" << spikeDetected + << " catastrophicSpike=" << catastrophicSpike + << " shortTermDivergence=" << shortTermDivergence << endl; + } +} + CSolver::CFLAdaptParams CSolver::InitializeCFLAdaptParams(CConfig *config) { CFLAdaptParams params; params.CFLFactorDecrease = config->GetCFL_AdaptParam(0); @@ -1910,7 +1965,388 @@ CSolver::CFLAdaptParams CSolver::InitializeCFLAdaptParams(CConfig *config) { params.stallWindow = min(100ul, params.Res_Count); return params; +} + +void CSolver::DetermineLinearSolverBasedCFLFlags(const CFLAdaptParams ¶ms, CConfig *config, + su2double linRes, su2double linTol, + bool &reduceCFL, bool &resetCFL, bool &canIncrease) { + /* Determine CFL adjustment flags based on linear solver performance with consecutive iteration damping. */ + + const unsigned long DAMPING_ITERS_REDUCE = 5; + const unsigned long DAMPING_ITERS_INCREASE = 10; + static unsigned long consecutiveReduceIters = 0; + static unsigned long consecutiveIncreaseIters = 0; + + unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + + /* Check if we should decrease or if we can increase, the 20% is to avoid flip-flopping. */ + /* Only reset CFL after starting iteration to allow initial convergence analysis */ + resetCFL = (linRes > 0.99) && (iter >= params.startingIter); + + /* only change CFL number when larger than starting iteration */ + bool shouldReduce = (linRes > 1.2*linTol) && (iter >= params.startingIter); + bool canIncrease_raw = (linRes < linTol) && (iter >= params.startingIter); + + /* Apply consecutive iteration damping */ + if (shouldReduce) { + consecutiveReduceIters++; + consecutiveIncreaseIters = 0; + } else if (canIncrease_raw) { + consecutiveIncreaseIters++; + consecutiveReduceIters = 0; + } else { + consecutiveReduceIters = 0; + consecutiveIncreaseIters = 0; + } + + reduceCFL = (consecutiveReduceIters >= DAMPING_ITERS_REDUCE); + canIncrease = (consecutiveIncreaseIters >= DAMPING_ITERS_INCREASE); + + if (rank == MASTER_NODE && iter % 50 == 0) { + cout << "CFL Debug - Iter " << iter << ": linRes=" << linRes << " linTol=" << linTol + << " reduceCFL=" << reduceCFL << " (consec=" << consecutiveReduceIters << "/" << DAMPING_ITERS_REDUCE << ")" + << " canIncrease=" << canIncrease << " (consec=" << consecutiveIncreaseIters << "/" << DAMPING_ITERS_INCREASE << ")" + << " resetCFL=" << resetCFL << endl; + } +} + +void CSolver::TrackResidualHistory(const CFLAdaptParams ¶ms, CConfig *config, + CSolver **solver_container, unsigned short iMesh, + su2double &New_Func, su2double &Old_Func, + bool &reduceCFL, bool &resetCFL) { + /* Track residual history and detect catastrophic divergence and flip-flop oscillations. */ + + Old_Func = New_Func; + if (NonLinRes_Series.empty()) NonLinRes_Series.resize(params.Res_Count, 0.0); + + /* Get pointers to solvers on this mesh level */ + CSolver *solverFlow = solver_container[FLOW_SOL]; + CSolver *solverTurb = (iMesh == MESH_0) ? solver_container[TURB_SOL] : nullptr; + CSolver *solverSpecies = (iMesh == MESH_0) ? solver_container[SPECIES_SOL] : nullptr; + + /* Sum the RMS residuals for all equations. */ + New_Func = 0.0; + unsigned short totalVars = 0; + for (unsigned short iVar = 0; iVar < solverFlow->GetnVar(); iVar++) { + New_Func += log10(solverFlow->GetRes_RMS(iVar)); + ++totalVars; + } + if (solverTurb) { + for (unsigned short iVar = 0; iVar < solverTurb->GetnVar(); iVar++) { + New_Func += log10(solverTurb->GetRes_RMS(iVar)); + ++totalVars; + } + } + if (solverSpecies) { + for (unsigned short iVar = 0; iVar < solverSpecies->GetnVar(); iVar++) { + New_Func += log10(solverSpecies->GetRes_RMS(iVar)); + ++totalVars; + } + } + New_Func /= totalVars; + + /* Compute the difference in the nonlinear residuals between the + current and previous iterations, taking care with very low initial + residuals (due to initialization). */ + if ((config->GetInnerIter() == 1) && (New_Func - Old_Func > 10)) { + Old_Func = New_Func; + } + NonLinRes_Series[NonLinRes_Counter] = New_Func - Old_Func; + + /* Increment the counter, if we hit the max size, then start over. */ + NonLinRes_Counter++; + if (NonLinRes_Counter == params.Res_Count) NonLinRes_Counter = 0; + + /* Fast flip-flop detection using XOR sign changes (from old code). + This catches rapid oscillations that the peak-valley logic might miss. */ + if (config->GetInnerIter() >= params.Res_Count) { + bool flipFlopOrDivergence = DetectFlipFlop(params, config); + reduceCFL |= flipFlopOrDivergence; + + /* Check for catastrophic divergence and reset if needed */ + su2double totalChange = 0.0; + for (const auto& val : NonLinRes_Series) { + totalChange += val; + } + if (totalChange > 2.0) { // orders of magnitude divergence + resetCFL = true; + NonLinRes_Counter = 0; + for (auto& val : NonLinRes_Series) val = 0.0; + } + } +} + +void CSolver::DetectPeakValley(const CFLAdaptParams ¶ms, CConfig *config, + su2double New_Func, bool &reduceCFL, bool &resetCFL) { + /* Detect oscillations and divergence using peak-valley progression + plus slope-based guards. This works alongside the flip-flop check above. + - Maintain a history window (Res_Count up to 400 iters). + - Extract peaks/valleys with minimum separation to avoid noise. + - Require new peaks/valleys to progress downward; otherwise flag. + - Use least-squares slope on last 3 peaks/valleys to catch divergence. + - Forget extrema older than a lookback window (300 iters). */ + + const unsigned long EXT_WINDOW = 300; + const unsigned long MIN_SEPARATION = 30; /* iters between extrema to avoid noise */ + const su2double VALUE_TOL = 0.05; /* log10 residual tolerance for peak/valley improvement */ + const su2double SLOPE_FLAT = -1.0e-3; /* slope >= this => stagnation */ + const su2double SLOPE_UP = 0.1; /* slope > this => divergence (log10 per iter) */ + + /* Counter to avoid reacting to every single iteration */ + static unsigned long oscillationCounter = 0; + static bool previousOscillation = false; + + /* Reconstruct New_Func history from the delta buffer. */ + vector funcHistory; + funcHistory.reserve(params.Res_Count); + su2double curr = New_Func; + funcHistory.push_back(curr); + for (unsigned long i = 1; i < params.Res_Count; i++) { + unsigned long idx = (NonLinRes_Counter + params.Res_Count - i) % params.Res_Count; + curr -= NonLinRes_Series[idx]; + funcHistory.push_back(curr); + } + reverse(funcHistory.begin(), funcHistory.end()); + + /* Map history index to iteration number. */ + const unsigned long histSize = funcHistory.size(); + const unsigned long currIter = config->GetInnerIter(); + const unsigned long startIter = currIter - histSize + 1; + + /* Find peaks and valleys using deques for automatic size management. */ + static deque> peaks; + static deque> valleys; + + /* Clear old extrema that are outside the lookback window */ + while (!peaks.empty() && currIter > EXT_WINDOW && peaks.front().first + EXT_WINDOW < currIter) { + peaks.pop_front(); + } + while (!valleys.empty() && currIter > EXT_WINDOW && valleys.front().first + EXT_WINDOW < currIter) { + valleys.pop_front(); + } + + unsigned long lastExtIdx = 0; + for (unsigned long i = 1; i + 1 < histSize; i++) { + if (i - lastExtIdx < MIN_SEPARATION) continue; + su2double a = funcHistory[i-1]; + su2double b = funcHistory[i]; + su2double c = funcHistory[i+1]; + unsigned long iter_i = startIter + i; + /* Drop extrema that fall outside lookback window. */ + if (currIter > EXT_WINDOW && iter_i + EXT_WINDOW < currIter) continue; + if (b > a && b > c) { + peaks.push_back(make_pair(iter_i, b)); + if (peaks.size() > 3) peaks.pop_front(); + lastExtIdx = i; + } else if (b < a && b < c) { + valleys.push_back(make_pair(iter_i, b)); + if (valleys.size() > 3) valleys.pop_front(); + lastExtIdx = i; + } + } + + auto slopeLSQ = [](const deque>& pts) { + if (pts.size() < 2) return 0.0; + + /* Compute least-squares linear regression slope: + slope = [n*Σ(xy) - Σ(x)*Σ(y)] / [n*Σ(x²) - (Σ(x))²] */ + su2double n = su2double(pts.size()); + su2double sumX = 0.0, sumY = 0.0, sumXY = 0.0, sumX2 = 0.0; + + for (const auto& pt : pts) { + su2double x = su2double(pt.first); // iteration number + su2double y = pt.second; // log10 residual value + sumX += x; + sumY += y; + sumXY += x * y; + sumX2 += x * x; + } + + su2double denominator = n * sumX2 - sumX * sumX; + if (fabs(denominator) < 1e-12) return 0.0; // Avoid division by zero + + su2double slope = (n * sumXY - sumX * sumY) / denominator; + return slope; + }; + + bool peaksStagnant = false, valleysStagnant = false; + if (peaks.size() >= 2) { + peaksStagnant = peaks.back().second >= peaks[peaks.size()-2].second - VALUE_TOL; + } + if (valleys.size() >= 2) { + valleysStagnant = valleys.back().second >= valleys[valleys.size()-2].second - VALUE_TOL; + } + + /* Slope of last 3 peaks/valleys (only if enough points). */ + su2double slopePeaks = 0.0, slopeValleys = 0.0; + bool haveSlopePeaks = (peaks.size() >= 2); + bool haveSlopeValleys = (valleys.size() >= 2); + if (haveSlopePeaks && peaks.back().first > peaks.front().first) { + slopePeaks = slopeLSQ(peaks); + } + if (haveSlopeValleys && valleys.back().first > valleys.front().first) { + slopeValleys = slopeLSQ(valleys); + } + + /* If slope is exactly zero (extrema at same iteration), it's not meaningful - ignore it */ + bool slopeFlat = false; + if (haveSlopePeaks && fabs(slopePeaks) > 1e-12 && slopePeaks >= SLOPE_FLAT) slopeFlat = true; + if (haveSlopeValleys && fabs(slopeValleys) > 1e-12 && slopeValleys >= SLOPE_FLAT) slopeFlat = true; + + bool slopeUp = (haveSlopePeaks && slopePeaks > SLOPE_UP) || (haveSlopeValleys && slopeValleys > SLOPE_UP); + + /* Check overall trend: compute average of ALL peaks and valleys to get robust trend. + In log10 space: convergence means residuals become more negative (decrease). + So we want: (avgNew - avgOld) < 0 for convergence. But we'll compute per-iteration + rate as (avgNew - avgOld)/dt, making NEGATIVE = convergence. */ + su2double overallSlope = 0.0; + if (peaks.size() >= 2 && valleys.size() >= 2) { + /* Average all peaks and valleys separately */ + su2double avgOldPeaks = 0.0, avgOldValleys = 0.0; + su2double avgNewPeaks = 0.0, avgNewValleys = 0.0; + unsigned long cntOld = 0, cntNew = 0; + + /* Split extrema into old half and new half */ + unsigned long midIter = (peaks.front().first + peaks.back().first) / 2; + + for (const auto& p : peaks) { + if (p.first <= midIter) { avgOldPeaks += p.second; cntOld++; } + else { avgNewPeaks += p.second; cntNew++; } + } + for (const auto& v : valleys) { + if (v.first <= midIter) { avgOldValleys += v.second; cntOld++; } + else { avgNewValleys += v.second; cntNew++; } + } + + if (cntOld > 0 && cntNew > 0) { + su2double avgOld = (avgOldPeaks + avgOldValleys) / cntOld; + su2double avgNew = (avgNewPeaks + avgNewValleys) / cntNew; + /* Slope = (avgNew - avgOld) / dt: NEGATIVE = convergence (residuals decreasing) */ + unsigned long dt = peaks.back().first - peaks.front().first; + if (dt > 0) overallSlope = (avgNew - avgOld) / su2double(dt); + } + } + + /* Only act when we have at least two peaks and two valleys to avoid premature locking. */ + bool haveData = (peaks.size() >= 2 && valleys.size() >= 2); + bool progressionFail = haveData && peaksStagnant && valleysStagnant; + + /* If all slopes are zero (extrema at same time or too close), don't treat as oscillation */ + bool allSlopesZero = (fabs(slopePeaks) < 1e-12 && fabs(slopeValleys) < 1e-12 && fabs(overallSlope) < 1e-12); + + /* Oscillation is only bad if slopes are problematic AND overall trend is not improving. + overallSlope = (avgNew - avgOld)/dt: NEGATIVE = convergence, POSITIVE = divergence */ + bool oscillationDetected = false; + if (haveData && !allSlopesZero) { + oscillationDetected = (progressionFail || (slopeFlat && overallSlope > -0.001) || slopeUp); + } + + if (rank == MASTER_NODE && currIter % 50 == 0 && haveData) { + cout << "Oscillation Debug - Iter " << currIter << ": peaks=" << peaks.size() << " valleys=" << valleys.size() + << " slopePeaks=" << slopePeaks << " slopeValleys=" << slopeValleys + << " overallSlope=" << overallSlope << " (neg=converge, pos=diverge)" + << " slopeFlat=" << slopeFlat << " progressionFail=" << progressionFail + << " oscillationDetected=" << oscillationDetected << endl; + } + + /* If overall trend shows strong convergence (negative slope), don't block CFL increase */ + if (overallSlope < -0.001) { + oscillationDetected = false; + } + + /* Compute totalChange early to use for stronger override logic */ + su2double totalChange = 0.0; + for (const auto& v : NonLinRes_Series) totalChange += v; + + /* STRONG OVERRIDE: If totalChange shows convergence over buffer, ignore oscillation detection. + This prevents false positives from noisy extrema when actual trend is good. + Relaxed from -0.1 to -0.01 to handle slow late-stage convergence. */ + if (totalChange < -0.01) { /* At least 0.01 log10 improvement over buffer */ + oscillationDetected = false; + } + + /* Also disable slopeUp detection if buffer shows net convergence - prevents false positives + from noise in individual valley slopes when overall trend is clearly downward */ + if (totalChange < 0.0 && slopeUp) { + oscillationDetected = false; + } + + /* Damping: only reduce CFL if oscillation persists for multiple iterations */ + if (oscillationDetected) { + if (previousOscillation) { + oscillationCounter++; + } else { + oscillationCounter = 1; + } + } else { + /* Decay counter aggressively when not detecting oscillation */ + if (oscillationCounter > 0) { + oscillationCounter = max(0ul, oscillationCounter - 5); + } + } + previousOscillation = oscillationDetected; + + /* Only act on oscillation if it persists for at least 3 checks */ + bool persistentOscillation = (oscillationCounter >= 3); + + /* EMERGENCY RESET: If counter is very high but we have clear convergence, reset completely. + This handles cases where stale detections accumulated and won't decay fast enough. + Use -0.01 threshold to match the main override logic. */ + if (oscillationCounter > 50 && totalChange < -0.01) { + oscillationCounter = 0; + persistentOscillation = false; + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << " Emergency reset: oscillation counter cleared due to strong convergence" << endl; + } + } + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "Persistence Debug - Iter " << currIter << ": oscillationCounter=" << oscillationCounter + << " persistentOscillation=" << persistentOscillation << endl; + } + if (persistentOscillation) { + reduceCFL |= true; + } + + /* Strong divergence safety: if the total change over the full buffer is large positive, reset. */ + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "Divergence Debug - Iter " << currIter << ": totalChange=" << totalChange + << " slopeUp=" << slopeUp << endl; + } + if (totalChange > 2.0 || (slopeUp && totalChange > 0.0)) { // 2 orders worse OR slope issues with net divergence + resetCFL = true; + NonLinRes_Counter = 0; + for (auto& val : NonLinRes_Series) val = 0.0; + } + + /* Stagnation guard: if over a recent window there is essentially no improvement, + lower CFL to escape the stall. Only trigger if we're truly stalled (near zero or + positive change) and not conflicting with other indicators showing good progress. */ + const unsigned long STALL_WINDOW = min(100ul, params.Res_Count); + if (config->GetInnerIter() >= STALL_WINDOW) { + su2double stallChange = 0.0; + for (unsigned long i = 0; i < STALL_WINDOW; i++) { + unsigned long idx = (NonLinRes_Counter + params.Res_Count - 1 - i) % params.Res_Count; + stallChange += NonLinRes_Series[idx]; + } + /* Only flag as stalled if change is very small or positive, AND we're not already + seeing good convergence indicators (canIncrease means linear solver is doing well). + Tightened threshold from -0.1 to -0.01 to avoid false positives on slow but steady convergence. */ + const su2double STALL_TOL = -0.01; /* about 0.01 log10 improvement threshold over window */ + bool isStalled = (stallChange > STALL_TOL); + + /* Note: canIncrease not passed to this function, so we remove the canIncrease check from stagnation logic. + The stagnation guard will now trigger based solely on the stallChange metric. */ + if (rank == MASTER_NODE && currIter % 50 == 0) { + cout << "Stagnation Debug - Iter " << currIter << ": stallChange=" << stallChange + << " STALL_TOL=" << STALL_TOL << " isStalled=" << isStalled + << " will_reduce=" << (isStalled || (stallChange > 0.0)) << endl; + } + + if (isStalled || stallChange > 0.0) { + reduceCFL = true; + } } +} void CSolver::AdaptCFLNumber(CGeometry **geometry, CSolver ***solver_container, @@ -1920,13 +2356,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, exponential progression with under-relaxation approach. */ const CFLAdaptParams params = InitializeCFLAdaptParams(config); - const bool fullComms = (config->GetComm_Level() == COMM_FULL); - - /* Damping: require consecutive iterations before acting */ - const unsigned long DAMPING_ITERS_REDUCE = 5; - const unsigned long DAMPING_ITERS_INCREASE = 10; - static unsigned long consecutiveReduceIters = 0; - static unsigned long consecutiveIncreaseIters = 0; + //const bool fullComms = (config->GetComm_Level() == COMM_FULL); static bool reduceCFL = false; static bool resetCFL = false; @@ -1964,427 +2394,27 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); - /* Check if we should decrease or if we can increase, the 20% is to avoid flip-flopping. */ - /* Only reset CFL after starting iteration to allow initial convergence analysis */ - resetCFL = (linRes > 0.99) && (iter >= params.startingIter); - - /* only change CFL number when larger than starting iteration */ - bool shouldReduce = (linRes > 1.2*linTol) && (iter >= params.startingIter); - bool canIncrease_raw = (linRes < linTol) && (iter >= params.startingIter); - - /* Apply consecutive iteration damping */ - if (shouldReduce) { - consecutiveReduceIters++; - consecutiveIncreaseIters = 0; - } else if (canIncrease_raw) { - consecutiveIncreaseIters++; - consecutiveReduceIters = 0; - } else { - consecutiveReduceIters = 0; - consecutiveIncreaseIters = 0; - } - - reduceCFL = (consecutiveReduceIters >= DAMPING_ITERS_REDUCE); - canIncrease = (consecutiveIncreaseIters >= DAMPING_ITERS_INCREASE); - - if (rank == MASTER_NODE && iter % 50 == 0) { - cout << "CFL Debug - Iter " << iter << ": linRes=" << linRes << " linTol=" << linTol - << " reduceCFL=" << reduceCFL << " (consec=" << consecutiveReduceIters << "/" << DAMPING_ITERS_REDUCE << ")" - << " canIncrease=" << canIncrease << " (consec=" << consecutiveIncreaseIters << "/" << DAMPING_ITERS_INCREASE << ")" - << " resetCFL=" << resetCFL << endl; - } + /* Determine CFL adjustment flags based on linear solver performance */ + DetermineLinearSolverBasedCFLFlags(params, config, linRes, linTol, reduceCFL, resetCFL, canIncrease); if (params.Res_Count > 0) { - Old_Func = New_Func; - if (NonLinRes_Series.empty()) NonLinRes_Series.resize(params.Res_Count,0.0); - - /* Sum the RMS residuals for all equations. */ - - New_Func = 0.0; - unsigned short totalVars = 0; - for (unsigned short iVar = 0; iVar < solverFlow->GetnVar(); iVar++) { - New_Func += log10(solverFlow->GetRes_RMS(iVar)); - ++totalVars; - } - if (solverTurb) { - for (unsigned short iVar = 0; iVar < solverTurb->GetnVar(); iVar++) { - New_Func += log10(solverTurb->GetRes_RMS(iVar)); - ++totalVars; - } - } - if (solverSpecies) { - for (unsigned short iVar = 0; iVar < solverSpecies->GetnVar(); iVar++) { - New_Func += log10(solverSpecies->GetRes_RMS(iVar)); - ++totalVars; - } - } - New_Func /= totalVars; - - /* Compute the difference in the nonlinear residuals between the - current and previous iterations, taking care with very low initial - residuals (due to initialization). */ - - if ((config->GetInnerIter() == 1) && (New_Func - Old_Func > 10)) { - Old_Func = New_Func; - } - NonLinRes_Series[NonLinRes_Counter] = New_Func - Old_Func; - - /* Increment the counter, if we hit the max size, then start over. */ - - NonLinRes_Counter++; - if (NonLinRes_Counter == params.Res_Count) NonLinRes_Counter = 0; - - /* Fast flip-flop detection using XOR sign changes (from old code). - This catches rapid oscillations that the peak-valley logic might miss. */ - if (config->GetInnerIter() >= params.Res_Count) { - bool flipFlopOrDivergence = DetectFlipFlop(params, config); - reduceCFL |= flipFlopOrDivergence; - - /* Check for catastrophic divergence and reset if needed */ - su2double totalChange = 0.0; - for (const auto& val : NonLinRes_Series) { - totalChange += val; - } - if (totalChange > 2.0) { // orders of magnitude divergence - resetCFL = true; - NonLinRes_Counter = 0; - for (auto& val : NonLinRes_Series) val = 0.0; - } - } + /* Track residual history and detect flip-flop oscillations */ + TrackResidualHistory(params, config, solver_container[iMesh], iMesh, + New_Func, Old_Func, reduceCFL, resetCFL); /* FAST DIVERGENCE DETECTION: Catch issues in 1-10 iterations before they become catastrophic. These checks run before the longer-term oscillation detection below. Only activate after startingIter to allow initial convergence analysis. */ if (iter >= params.startingIter) { - const unsigned long currIter = config->GetInnerIter(); - - /* 1. SINGLE-ITERATION SPIKE DETECTION: Catch catastrophic jumps immediately */ - su2double singleIterChange = New_Func - Old_Func; - bool spikeDetected = false; - bool catastrophicSpike = false; - - if (singleIterChange > 0.15) { /* 0.15 log10 jump in one iteration */ - spikeDetected = true; - reduceCFL = true; - if (singleIterChange > 0.3) { /* 0.3+ log units = catastrophic */ - catastrophicSpike = true; - resetCFL = true; - } - } - - /* 2. SHORT-WINDOW TREND DETECTION: Catch degradation over 10 iterations */ - bool shortTermDivergence = false; - if (currIter >= 10) { - const unsigned long SHORT_WINDOW = 10; - su2double recentChange = 0.0; - - /* Sum the last 10 delta values */ - for (unsigned long i = 0; i < SHORT_WINDOW; i++) { - unsigned long idx = (NonLinRes_Counter + params.Res_Count - 1 - i) % params.Res_Count; - recentChange += NonLinRes_Series[idx]; - } - - /* Average change per iteration over the short window */ - su2double recentSlope = recentChange / su2double(SHORT_WINDOW); - - /* Trigger if: residuals increasing OR stalling after initial convergence */ - if (recentChange > 0.1) { /* Net increase over 10 iterations */ - shortTermDivergence = true; - reduceCFL = true; - } else if (recentSlope > -0.0005 && Old_Func < -3.0) { - /* Convergence rate < 0.0005/iter after reaching -3.0 = stalling */ - shortTermDivergence = true; - reduceCFL = true; - } - } - - /* Debug output for fast detection mechanisms */ - if (rank == MASTER_NODE && currIter % 50 == 0) { - cout << "FastDivergence Debug - Iter " << currIter - << ": singleIterChange=" << singleIterChange - << " spikeDetected=" << spikeDetected - << " catastrophicSpike=" << catastrophicSpike - << " shortTermDivergence=" << shortTermDivergence << endl; - } + DetectFastDivergence(params, config, New_Func, Old_Func, reduceCFL, resetCFL); } - /*--- End of fast divergence detection ---*/ - - /* Detect oscillations and divergence using peak-valley progression - plus slope-based guards. This works alongside the flip-flop check above. - - Maintain a history window (Res_Count up to 400 iters). - - Extract peaks/valleys with minimum separation to avoid noise. - - Require new peaks/valleys to progress downward; otherwise flag. - - Use least-squares slope on last 3 peaks/valleys to catch divergence. - - Forget extrema older than a lookback window (300 iters). + + /* Detect oscillations and divergence using peak-valley progression. Only activate after startingIter to allow initial convergence analysis. */ if (iter >= params.startingIter && config->GetInnerIter() >= 3) { - const unsigned long EXT_WINDOW = 300; - const unsigned long MIN_SEPARATION = 30; /* iters between extrema to avoid noise */ - const su2double VALUE_TOL = 0.05; /* log10 residual tolerance for peak/valley improvement */ - const su2double SLOPE_FLAT = -1.0e-3; /* slope >= this => stagnation */ - const su2double SLOPE_UP = 0.1; /* slope > this => divergence (log10 per iter) */ - - /* Counter to avoid reacting to every single iteration */ - static unsigned long oscillationCounter = 0; - static bool previousOscillation = false; - - /* Reconstruct New_Func history from the delta buffer. */ - vector funcHistory; - funcHistory.reserve(params.Res_Count); - su2double curr = New_Func; - funcHistory.push_back(curr); - for (unsigned long i = 1; i < params.Res_Count; i++) { - unsigned long idx = (NonLinRes_Counter + params.Res_Count - i) % params.Res_Count; - curr -= NonLinRes_Series[idx]; - funcHistory.push_back(curr); - } - reverse(funcHistory.begin(), funcHistory.end()); - - /* Map history index to iteration number. */ - const unsigned long histSize = funcHistory.size(); - const unsigned long currIter = config->GetInnerIter(); - const unsigned long startIter = currIter - histSize + 1; - - /* Find peaks and valleys using deques for automatic size management. */ - static deque> peaks; - static deque> valleys; - - /* Clear old extrema that are outside the lookback window */ - while (!peaks.empty() && currIter > EXT_WINDOW && peaks.front().first + EXT_WINDOW < currIter) { - peaks.pop_front(); - } - while (!valleys.empty() && currIter > EXT_WINDOW && valleys.front().first + EXT_WINDOW < currIter) { - valleys.pop_front(); - } - - unsigned long lastExtIdx = 0; - for (unsigned long i = 1; i + 1 < histSize; i++) { - if (i - lastExtIdx < MIN_SEPARATION) continue; - su2double a = funcHistory[i-1]; - su2double b = funcHistory[i]; - su2double c = funcHistory[i+1]; - unsigned long iter_i = startIter + i; - /* Drop extrema that fall outside lookback window. */ - if (currIter > EXT_WINDOW && iter_i + EXT_WINDOW < currIter) continue; - if (b > a && b > c) { - peaks.push_back(make_pair(iter_i, b)); - if (peaks.size() > 3) peaks.pop_front(); - lastExtIdx = i; - } else if (b < a && b < c) { - valleys.push_back(make_pair(iter_i, b)); - if (valleys.size() > 3) valleys.pop_front(); - lastExtIdx = i; - } - } - - auto slopeLSQ = [](const deque>& pts) { - if (pts.size() < 2) return 0.0; - - /* Compute least-squares linear regression slope: - slope = [n*Σ(xy) - Σ(x)*Σ(y)] / [n*Σ(x²) - (Σ(x))²] */ - su2double n = su2double(pts.size()); - su2double sumX = 0.0, sumY = 0.0, sumXY = 0.0, sumX2 = 0.0; - - for (const auto& pt : pts) { - su2double x = su2double(pt.first); // iteration number - su2double y = pt.second; // log10 residual value - sumX += x; - sumY += y; - sumXY += x * y; - sumX2 += x * x; - } - - su2double denominator = n * sumX2 - sumX * sumX; - if (fabs(denominator) < 1e-12) return 0.0; // Avoid division by zero - - su2double slope = (n * sumXY - sumX * sumY) / denominator; - return slope; - }; - - bool peaksStagnant = false, valleysStagnant = false; - if (peaks.size() >= 2) { - peaksStagnant = peaks.back().second >= peaks[peaks.size()-2].second - VALUE_TOL; - } - if (valleys.size() >= 2) { - valleysStagnant = valleys.back().second >= valleys[valleys.size()-2].second - VALUE_TOL; - } - - /* Slope of last 3 peaks/valleys (only if enough points). */ - su2double slopePeaks = 0.0, slopeValleys = 0.0; - bool haveSlopePeaks = (peaks.size() >= 2); - bool haveSlopeValleys = (valleys.size() >= 2); - if (haveSlopePeaks && peaks.back().first > peaks.front().first) { - slopePeaks = slopeLSQ(peaks); - } - if (haveSlopeValleys && valleys.back().first > valleys.front().first) { - slopeValleys = slopeLSQ(valleys); - } - - /* If slope is exactly zero (extrema at same iteration), it's not meaningful - ignore it */ - bool slopeFlat = false; - if (haveSlopePeaks && fabs(slopePeaks) > 1e-12 && slopePeaks >= SLOPE_FLAT) slopeFlat = true; - if (haveSlopeValleys && fabs(slopeValleys) > 1e-12 && slopeValleys >= SLOPE_FLAT) slopeFlat = true; - - bool slopeUp = (haveSlopePeaks && slopePeaks > SLOPE_UP) || (haveSlopeValleys && slopeValleys > SLOPE_UP); - - /* Check overall trend: compute average of ALL peaks and valleys to get robust trend. - In log10 space: convergence means residuals become more negative (decrease). - So we want: (avgNew - avgOld) < 0 for convergence. But we'll compute per-iteration - rate as (avgNew - avgOld)/dt, making NEGATIVE = convergence. */ - su2double overallSlope = 0.0; - if (peaks.size() >= 2 && valleys.size() >= 2) { - /* Average all peaks and valleys separately */ - su2double avgOldPeaks = 0.0, avgOldValleys = 0.0; - su2double avgNewPeaks = 0.0, avgNewValleys = 0.0; - unsigned long cntOld = 0, cntNew = 0; - - /* Split extrema into old half and new half */ - unsigned long midIter = (peaks.front().first + peaks.back().first) / 2; - - for (const auto& p : peaks) { - if (p.first <= midIter) { avgOldPeaks += p.second; cntOld++; } - else { avgNewPeaks += p.second; cntNew++; } - } - for (const auto& v : valleys) { - if (v.first <= midIter) { avgOldValleys += v.second; cntOld++; } - else { avgNewValleys += v.second; cntNew++; } - } - - if (cntOld > 0 && cntNew > 0) { - su2double avgOld = (avgOldPeaks + avgOldValleys) / cntOld; - su2double avgNew = (avgNewPeaks + avgNewValleys) / cntNew; - /* Slope = (avgNew - avgOld) / dt: NEGATIVE = convergence (residuals decreasing) */ - unsigned long dt = peaks.back().first - peaks.front().first; - if (dt > 0) overallSlope = (avgNew - avgOld) / su2double(dt); - } - } - - /* Only act when we have at least two peaks and two valleys to avoid premature locking. */ - bool haveData = (peaks.size() >= 2 && valleys.size() >= 2); - bool progressionFail = haveData && peaksStagnant && valleysStagnant; - - /* If all slopes are zero (extrema at same time or too close), don't treat as oscillation */ - bool allSlopesZero = (fabs(slopePeaks) < 1e-12 && fabs(slopeValleys) < 1e-12 && fabs(overallSlope) < 1e-12); - - /* Oscillation is only bad if slopes are problematic AND overall trend is not improving. - overallSlope = (avgNew - avgOld)/dt: NEGATIVE = convergence, POSITIVE = divergence */ - bool oscillationDetected = false; - if (haveData && !allSlopesZero) { - oscillationDetected = (progressionFail || (slopeFlat && overallSlope > -0.001) || slopeUp); - } - - if (rank == MASTER_NODE && currIter % 50 == 0 && haveData) { - cout << "Oscillation Debug - Iter " << currIter << ": peaks=" << peaks.size() << " valleys=" << valleys.size() - << " slopePeaks=" << slopePeaks << " slopeValleys=" << slopeValleys - << " overallSlope=" << overallSlope << " (neg=converge, pos=diverge)" - << " slopeFlat=" << slopeFlat << " progressionFail=" << progressionFail - << " oscillationDetected=" << oscillationDetected << endl; - } - - /* If overall trend shows strong convergence (negative slope), don't block CFL increase */ - if (overallSlope < -0.001) { - oscillationDetected = false; - } - - /* Compute totalChange early to use for stronger override logic */ - su2double totalChange = 0.0; - for (const auto& v : NonLinRes_Series) totalChange += v; - - /* STRONG OVERRIDE: If totalChange shows convergence over buffer, ignore oscillation detection. - This prevents false positives from noisy extrema when actual trend is good. - Relaxed from -0.1 to -0.01 to handle slow late-stage convergence. */ - if (totalChange < -0.01) { /* At least 0.01 log10 improvement over buffer */ - oscillationDetected = false; - } - - /* Also disable slopeUp detection if buffer shows net convergence - prevents false positives - from noise in individual valley slopes when overall trend is clearly downward */ - if (totalChange < 0.0 && slopeUp) { - oscillationDetected = false; - } - - /* Damping: only reduce CFL if oscillation persists for multiple iterations */ - if (oscillationDetected) { - if (previousOscillation) { - oscillationCounter++; - } else { - oscillationCounter = 1; - } - } else { - /* Decay counter aggressively when not detecting oscillation */ - if (oscillationCounter > 0) { - oscillationCounter = max(0ul, oscillationCounter - 5); - } - } - previousOscillation = oscillationDetected; - - /* Only act on oscillation if it persists for at least 3 checks */ - bool persistentOscillation = (oscillationCounter >= 3); - - /* EMERGENCY RESET: If counter is very high but we have clear convergence, reset completely. - This handles cases where stale detections accumulated and won't decay fast enough. - Use -0.01 threshold to match the main override logic. */ - if (oscillationCounter > 50 && totalChange < -0.01) { - oscillationCounter = 0; - persistentOscillation = false; - if (rank == MASTER_NODE && currIter % 50 == 0) { - cout << " Emergency reset: oscillation counter cleared due to strong convergence" << endl; - } - } - if (rank == MASTER_NODE && currIter % 50 == 0) { - cout << "Persistence Debug - Iter " << currIter << ": oscillationCounter=" << oscillationCounter - << " persistentOscillation=" << persistentOscillation << endl; - } - if (persistentOscillation) { - reduceCFL |= true; - } - - /* Strong divergence safety: if the total change over the full buffer is large positive, reset. - totalChange already computed above for override logic. - Only trigger slopeUp reset if also seeing net divergence in buffer. */ - if (rank == MASTER_NODE && currIter % 50 == 0) { - cout << "Divergence Debug - Iter " << currIter << ": totalChange=" << totalChange - << " slopeUp=" << slopeUp << endl; - } - if (totalChange > 2.0 || (slopeUp && totalChange > 0.0)) { // 2 orders worse OR slope issues with net divergence - resetCFL = true; - NonLinRes_Counter = 0; - for (auto& val : NonLinRes_Series) val = 0.0; - } - - /* Stagnation guard: if over a recent window there is essentially no improvement, - lower CFL to escape the stall. Only trigger if we're truly stalled (near zero or - positive change) and not conflicting with other indicators showing good progress. */ - const unsigned long STALL_WINDOW = min(100ul, params.Res_Count); - if (config->GetInnerIter() >= STALL_WINDOW) { - su2double stallChange = 0.0; - for (unsigned long i = 0; i < STALL_WINDOW; i++) { - unsigned long idx = (NonLinRes_Counter + params.Res_Count - 1 - i) % params.Res_Count; - stallChange += NonLinRes_Series[idx]; - } - /* Only flag as stalled if change is very small or positive, AND we're not already - seeing good convergence indicators (canIncrease means linear solver is doing well). - Tightened threshold from -0.1 to -0.01 to avoid false positives on slow but steady convergence. */ - const su2double STALL_TOL = -0.01; /* about 0.01 log10 improvement threshold over window */ - bool isStalled = (stallChange > STALL_TOL); - - if (rank == MASTER_NODE && currIter % 50 == 0) { - cout << "Stagnation Debug - Iter " << currIter << ": stallChange=" << stallChange - << " STALL_TOL=" << STALL_TOL << " isStalled=" << isStalled - << " canIncrease=" << canIncrease << " will_reduce=" - << ((isStalled && !canIncrease) || (stallChange > 0.0)) << endl; - } - - /* Don't override canIncrease unless truly stalled (near zero or positive trend) */ - if (isStalled && !canIncrease) { - reduceCFL = true; - } else if (stallChange > 0.0) { - /* Only force reduction if residuals actually increased */ - reduceCFL = true; - } - } + DetectPeakValley(params, config, New_Func, reduceCFL, resetCFL); } } } /* End safe global access, now all threads update the CFL number. */ From 94b48fc4b119335531054752bdb13d5c8d619720 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 14 Dec 2025 21:57:31 +0100 Subject: [PATCH 36/41] make adaptcfl more modular 5 --- SU2_CFD/include/solvers/CSolver.hpp | 2 ++ SU2_CFD/src/solvers/CSolver.cpp | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index a997c84b5236..8249e7146011 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -1452,6 +1452,8 @@ class CSolver { CFLAdaptParams InitializeCFLAdaptParams(CConfig *config); + su2double ComputeMaxLinearResidual(CSolver *solverFlow, CSolver *solverTurb, CSolver *solverSpecies); + bool DetectFlipFlop(const CFLAdaptParams ¶ms, CConfig *config); void DetermineLinearSolverBasedCFLFlags(const CFLAdaptParams ¶ms, CConfig *config, diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index d41fcacc9296..a8d38daada97 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1967,6 +1967,17 @@ CSolver::CFLAdaptParams CSolver::InitializeCFLAdaptParams(CConfig *config) { return params; } +su2double CSolver::ComputeMaxLinearResidual(CSolver *solverFlow, CSolver *solverTurb, CSolver *solverSpecies) { + /* Compute the maximum linear residual across all active solvers. */ + + su2double linResTurb = 0.0; + su2double linResSpecies = 0.0; + if (solverTurb) linResTurb = solverTurb->GetResLinSolver(); + if (solverSpecies) linResSpecies = solverSpecies->GetResLinSolver(); + + return max(solverFlow->GetResLinSolver(), max(linResTurb, linResSpecies)); +} + void CSolver::DetermineLinearSolverBasedCFLFlags(const CFLAdaptParams ¶ms, CConfig *config, su2double linRes, su2double linTol, bool &reduceCFL, bool &resetCFL, bool &canIncrease) { @@ -2371,16 +2382,9 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, CSolver *solverTurb = solver_container[iMesh][TURB_SOL]; CSolver *solverSpecies = solver_container[iMesh][SPECIES_SOL]; - /* Check whether we achieved the requested reduction in the linear - solver residual within the specified number of linear iterations. */ - - su2double linResTurb = 0.0; - su2double linResSpecies = 0.0; - if (solverTurb) linResTurb = solverTurb->GetResLinSolver(); - if (solverSpecies) linResSpecies = solverSpecies->GetResLinSolver(); - - /* Max linear residual between flow and turbulence/species transport. */ - const su2double linRes = max(solverFlow->GetResLinSolver(), max(linResTurb, linResSpecies)); + /* Check whether we achieved the requested reduction in the linear + solver residual within the specified number of linear iterations. */ + const su2double linRes = ComputeMaxLinearResidual(solverFlow, solverTurb, solverSpecies); /* Tolerance limited to an acceptable value. */ const su2double linTol = max(params.acceptableLinTol, config->GetLinear_Solver_Error()); From fd7a201ca90aa1c2b110a54d243da3ace586e394 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 14 Dec 2025 22:15:44 +0100 Subject: [PATCH 37/41] make adaptcfl more modular 6 --- SU2_CFD/src/solvers/CSolver.cpp | 59 +++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index a8d38daada97..2b9ef3d9a3c9 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -2035,26 +2035,57 @@ void CSolver::TrackResidualHistory(const CFLAdaptParams ¶ms, CConfig *config CSolver *solverTurb = (iMesh == MESH_0) ? solver_container[TURB_SOL] : nullptr; CSolver *solverSpecies = (iMesh == MESH_0) ? solver_container[SPECIES_SOL] : nullptr; - /* Sum the RMS residuals for all equations. */ - New_Func = 0.0; + /* Compute aggregate residual metric. Handle communication level to ensure consistency across MPI ranks. + In COMM_FULL mode, residuals are already globally reduced, so we can use them directly. + In COMM_REDUCED mode, residuals are local only, so we need to synchronize across ranks. */ + const bool useGlobalResiduals = (config->GetComm_Level() == COMM_FULL); unsigned short totalVars = 0; - for (unsigned short iVar = 0; iVar < solverFlow->GetnVar(); iVar++) { - New_Func += log10(solverFlow->GetRes_RMS(iVar)); - ++totalVars; - } - if (solverTurb) { - for (unsigned short iVar = 0; iVar < solverTurb->GetnVar(); iVar++) { - New_Func += log10(solverTurb->GetRes_RMS(iVar)); + + if (useGlobalResiduals) { + /* COMM_FULL: Residuals already globally averaged - use directly */ + New_Func = 0.0; + for (unsigned short iVar = 0; iVar < solverFlow->GetnVar(); iVar++) { + New_Func += log10(solverFlow->GetRes_RMS(iVar)); ++totalVars; } - } - if (solverSpecies) { - for (unsigned short iVar = 0; iVar < solverSpecies->GetnVar(); iVar++) { - New_Func += log10(solverSpecies->GetRes_RMS(iVar)); + if (solverTurb) { + for (unsigned short iVar = 0; iVar < solverTurb->GetnVar(); iVar++) { + New_Func += log10(solverTurb->GetRes_RMS(iVar)); + ++totalVars; + } + } + if (solverSpecies) { + for (unsigned short iVar = 0; iVar < solverSpecies->GetnVar(); iVar++) { + New_Func += log10(solverSpecies->GetRes_RMS(iVar)); + ++totalVars; + } + } + New_Func /= totalVars; + } else { + /* COMM_REDUCED: Residuals are local only - need MPI synchronization for consistent CFL decisions */ + su2double New_Func_Local = 0.0; + for (unsigned short iVar = 0; iVar < solverFlow->GetnVar(); iVar++) { + New_Func_Local += log10(solverFlow->GetRes_RMS(iVar)); ++totalVars; } + if (solverTurb) { + for (unsigned short iVar = 0; iVar < solverTurb->GetnVar(); iVar++) { + New_Func_Local += log10(solverTurb->GetRes_RMS(iVar)); + ++totalVars; + } + } + if (solverSpecies) { + for (unsigned short iVar = 0; iVar < solverSpecies->GetnVar(); iVar++) { + New_Func_Local += log10(solverSpecies->GetRes_RMS(iVar)); + ++totalVars; + } + } + New_Func_Local /= totalVars; + + /* Synchronize across MPI ranks to ensure all ranks make identical CFL decisions */ + SU2_MPI::Allreduce(&New_Func_Local, &New_Func, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + New_Func /= su2double(size); } - New_Func /= totalVars; /* Compute the difference in the nonlinear residuals between the current and previous iterations, taking care with very low initial From 29eecfcec4fccb664c97dd27cb3952642e243af9 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Sun, 14 Dec 2025 23:54:03 +0100 Subject: [PATCH 38/41] some MG MPI changes --- Common/src/geometry/CMultiGridGeometry.cpp | 174 +++++++++++++++++---- Common/src/geometry/dual_grid/CPoint.cpp | 3 +- 2 files changed, 149 insertions(+), 28 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index a4a2f1394fd3..04273d819e70 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -54,7 +54,7 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un 4th) No marker ---> Internal Volume (always agglomerate) ---*/ //note that for MPI, we introduce interfaces and we can choose to have agglomeration over - //the interface or not. + //the interface or not. Nishikawa chooses not to agglomerate over interfaces. /*--- Set a marker to indicate indirect agglomeration, for quads and hexs, i.e. consider up to neighbors of neighbors of neighbors. @@ -314,20 +314,6 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } } - /* --- Note that we can check index_coarse_cv for nr of children ---*/ - // for (auto icoarse_cv = 0u; icoarse_cv < Index_CoarseCV; icoarse_cv++) { - // cout << "coarse node " << icoarse_cv << ", children = " << nodes->GetnChildren_CV(icoarse_cv) << " " << endl; - // } - - /*--- Check all agglomerations and see if there is a 1-node agglomeration ---*/ - // for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { - // for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { - // const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); - // cout << "ipoint = " << iPoint << " , " << nodes->GetnChildren_CV(iPoint) << " " << - // fine_grid->nodes->GetParent_CV(iPoint) << endl; - // } - // } - /*--- Update the queue with the results from the boundary agglomeration ---*/ for (auto iPoint = 0ul; iPoint < fine_grid->GetnPoint(); iPoint++) { @@ -551,15 +537,28 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un nodes->ResetPoints(); #ifdef HAVE_MPI + /*--- Reset halo point parents before MPI agglomeration. + This is critical for multi-level multigrid: when creating level N from level N-1, + the fine grid (level N-1) already has Parent_CV set from when it was created from level N-2. + Those parent indices point to level N, but when creating level N+1, they would be + incorrectly interpreted as level N+1 indices. Resetting ensures clean agglomeration. ---*/ + + for (auto iPoint = fine_grid->GetnPointDomain(); iPoint < fine_grid->GetnPoint(); iPoint++) { + fine_grid->nodes->SetParent_CV(iPoint, ULONG_MAX); + } + /*--- Dealing with MPI parallelization, the objective is that the received nodes must be agglomerated in the same way as the donor (send) nodes. Send the node agglomeration information of the donor (parent and children). The agglomerated halos of this rank are set according to the rank where they are domain points. ---*/ for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++) { + cout << " marker name = " << config->GetMarker_All_TagBound(iMarker) << endl; + cout << " marker type = " << config->GetMarker_All_KindBC(iMarker) << endl; + cout << " send/recv = " << config->GetMarker_All_SendRecv(iMarker) << endl; if ((config->GetMarker_All_KindBC(iMarker) == SEND_RECEIVE) && (config->GetMarker_All_SendRecv(iMarker) > 0)) { - const auto MarkerS = iMarker; - const auto MarkerR = iMarker + 1; + const auto MarkerS = iMarker; // sending marker + const auto MarkerR = iMarker + 1; // receiving marker const auto send_to = config->GetMarker_All_SendRecv(MarkerS) - 1; const auto receive_from = abs(config->GetMarker_All_SendRecv(MarkerR)) - 1; @@ -606,37 +605,158 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un vector Parent_Local(nVertexR); vector Children_Local(nVertexR); + /*--- First pass: Determine which parents will actually be used (have non-skipped children). + This prevents creating orphaned halo CVs that have coordinates (0,0,0). ---*/ + vector parent_used(Aux_Parent.size(), false); + vector parent_local_index(Aux_Parent.size(), ULONG_MAX); + for (auto iVertex = 0ul; iVertex < nVertexR; iVertex++) { - /*--- We use the same sorting as in the donor domain, i.e. the local parents - are numbered according to their order in the remote rank. ---*/ + const auto iPoint_Fine = fine_grid->vertex[MarkerR][iVertex]->GetNode(); + auto existing_parent = fine_grid->nodes->GetParent_CV(iPoint_Fine); + + /*--- Skip if already agglomerated (first-wins policy) ---*/ + if (existing_parent != ULONG_MAX) continue; + /*--- Find which parent this vertex maps to ---*/ for (auto jVertex = 0ul; jVertex < Aux_Parent.size(); jVertex++) { if (Parent_Remote[iVertex] == Aux_Parent[jVertex]) { - Parent_Local[iVertex] = jVertex + Index_CoarseCV; + parent_used[jVertex] = true; break; } } + } + + /*--- Assign local indices only to used parents ---*/ + unsigned long nUsedParents = 0; + for (auto jVertex = 0ul; jVertex < Aux_Parent.size(); jVertex++) { + if (parent_used[jVertex]) { + parent_local_index[jVertex] = Index_CoarseCV + nUsedParents; + nUsedParents++; + } + } + + /*--- Now map each received vertex to its local parent ---*/ + for (auto iVertex = 0ul; iVertex < nVertexR; iVertex++) { + Parent_Local[iVertex] = ULONG_MAX; + for (auto jVertex = 0ul; jVertex < Aux_Parent.size(); jVertex++) { + if (Parent_Remote[iVertex] == Aux_Parent[jVertex]) { + Parent_Local[iVertex] = parent_local_index[jVertex]; + break; + } + } + + /*--- Validate that parent mapping was found (only matters if not skipped later) ---*/ + if (Parent_Local[iVertex] == ULONG_MAX) { + SU2_MPI::Error(string("MPI agglomeration failed to map parent index ") + + to_string(Parent_Remote[iVertex]) + + string(" for vertex ") + to_string(iVertex), + CURRENT_FUNCTION); + } + Children_Local[iVertex] = fine_grid->vertex[MarkerR][iVertex]->GetNode(); } - Index_CoarseCV += Aux_Parent.size(); + /*--- Debug: Track state before updating Index_CoarseCV ---*/ + auto Index_CoarseCV_Before = Index_CoarseCV; + + /*--- Only increment by the number of parents that will actually be used ---*/ + Index_CoarseCV += nUsedParents; - vector nChildren_MPI(Index_CoarseCV, 0); + /*--- Debug counters ---*/ + unsigned long nConflicts = 0, nSkipped = 0, nOutOfBounds = 0, nSuccess = 0; /*--- Create the final structure ---*/ for (auto iVertex = 0ul; iVertex < nVertexR; iVertex++) { const auto iPoint_Coarse = Parent_Local[iVertex]; const auto iPoint_Fine = Children_Local[iVertex]; - /*--- Be careful, it is possible that a node changes the agglomeration configuration, - the priority is always when receiving the information. ---*/ + /*--- Debug: Check for out-of-bounds access ---*/ + if (iPoint_Coarse >= Index_CoarseCV) { + cout << "ERROR [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << "]: Out-of-bounds coarse CV index " << iPoint_Coarse + << " >= " << Index_CoarseCV + << " (vertex " << iVertex << ", fine point " << iPoint_Fine << ")" << endl; + nOutOfBounds++; + continue; + } + + /*--- Solution 1: Skip if this halo point was already agglomerated ---*/ + auto existing_parent = fine_grid->nodes->GetParent_CV(iPoint_Fine); + if (existing_parent != ULONG_MAX) { + if (existing_parent != iPoint_Coarse) { + /*--- Conflict detected: different parent from different interface ---*/ + nConflicts++; + + /*--- Only print detailed info for first few conflicts or if suspicious ---*/ + if (nConflicts <= 5 || existing_parent < nPointDomain) { + cout << "INFO [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << "]: Halo point " << iPoint_Fine + << " already agglomerated to parent " << existing_parent + << (existing_parent < nPointDomain ? " (DOMAIN CV!)" : " (halo CV)") + << ", skipping reassignment to " << iPoint_Coarse + << " (from rank " << receive_from << ")" << endl; + } + } else { + /*--- Same parent from different interface (duplicate) - just skip silently ---*/ + nSkipped++; + } + continue; // First-wins: keep existing assignment + } + + /*--- First assignment for this halo point - proceed with agglomeration ---*/ + + /*--- Critical fix: Append to existing children, don't overwrite ---*/ + auto existing_children_count = nodes->GetnChildren_CV(iPoint_Coarse); + fine_grid->nodes->SetParent_CV(iPoint_Fine, iPoint_Coarse); - nodes->SetChildren_CV(iPoint_Coarse, nChildren_MPI[iPoint_Coarse], iPoint_Fine); - nChildren_MPI[iPoint_Coarse]++; - nodes->SetnChildren_CV(iPoint_Coarse, nChildren_MPI[iPoint_Coarse]); + nodes->SetChildren_CV(iPoint_Coarse, existing_children_count, iPoint_Fine); + nodes->SetnChildren_CV(iPoint_Coarse, existing_children_count + 1); nodes->SetDomain(iPoint_Coarse, false); + nSuccess++; + } + + /*--- Debug: Report statistics for this marker pair ---*/ + cout << "MPI Agglomeration [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << " (rank " << send_to << " <-> " << receive_from << ")]: " + << nSuccess << " assigned, " << nSkipped << " duplicates, " + << nConflicts << " conflicts"; + if (nOutOfBounds > 0) { + cout << ", " << nOutOfBounds << " OUT-OF-BOUNDS (CRITICAL!)"; + } + cout << endl; + + if (nConflicts > 5) { + cout << " Note: Only first 5 conflicts shown in detail, total conflicts = " << nConflicts << endl; + } + + /*--- Debug: Validate buffer size assumption ---*/ + if (nVertexS != nVertexR) { + cout << "WARNING [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << "]: Asymmetric interface - nVertexS=" << nVertexS + << " != nVertexR=" << nVertexR << endl; + } + } + } + + /*--- Post-process: Check for orphaned coarse CVs (should be none with new logic) ---*/ + + if (size > SINGLE_NODE) { + unsigned long nOrphaned = 0; + + /*--- Count orphaned CVs for reporting ---*/ + for (auto iCoarse = nPointDomain; iCoarse < Index_CoarseCV; iCoarse++) { + if (nodes->GetnChildren_CV(iCoarse) == 0) { + nOrphaned++; + /*--- This shouldn't happen with the new parent prefiltering logic ---*/ + cout << "WARNING [Rank " << rank << "]: Orphaned halo CV " << iCoarse + << " detected (should not occur with current logic)" << endl; } } + + if (nOrphaned > 0) { + cout << "WARNING [Rank " << rank << "]: " << nOrphaned + << " orphaned halo coarse CVs found - this indicates a logic error!" << endl; + } } #endif // HAVE_MPI diff --git a/Common/src/geometry/dual_grid/CPoint.cpp b/Common/src/geometry/dual_grid/CPoint.cpp index 2d234b88c5e6..0851de19f6eb 100644 --- a/Common/src/geometry/dual_grid/CPoint.cpp +++ b/Common/src/geometry/dual_grid/CPoint.cpp @@ -25,6 +25,7 @@ * License along with SU2. If not, see . */ +#include #include "../../../include/geometry/dual_grid/CPoint.hpp" #include "../../../include/CConfig.hpp" #include "../../../include/parallelization/omp_structure.hpp" @@ -73,7 +74,7 @@ void CPoint::FullAllocation(unsigned short imesh, const CConfig* config) { /*--- Multigrid structures. ---*/ if (config->GetnMGLevels() > 0) { - Parent_CV.resize(npoint) = 0; + Parent_CV.resize(npoint) = ULONG_MAX; // Initialize to ULONG_MAX to indicate unagglomerated Agglomerate.resize(npoint) = false; Agglomerate_Indirect.resize(npoint) = false; /*--- The finest grid does not have children CV's. ---*/ From 2468017ca01899662cc525138e08ce8796ca78e4 Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 15 Dec 2025 12:22:59 +0100 Subject: [PATCH 39/41] some more MG MPI fixes --- Common/include/CConfig.hpp | 7 + .../include/geometry/CMultiGridGeometry.hpp | 7 + Common/src/CConfig.cpp | 3 + Common/src/geometry/CMultiGridGeometry.cpp | 202 ++++++++++++++++++ SU2_CFD/include/solvers/CSolver.hpp | 4 +- SU2_CFD/src/drivers/CDriver.cpp | 5 + SU2_CFD/src/solvers/CSolver.cpp | 96 +++++++-- 7 files changed, 307 insertions(+), 17 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index a7d5d03a8683..14c95de66cc2 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -503,6 +503,7 @@ class CConfig { bool MG_Smooth_Output; /*!< \brief Output per-iteration multigrid smoothing info. */ bool MG_Implicit_Lines; /*!< \\brief Enable implicit-lines agglomeration from walls. */ bool MG_Implicit_Debug; /*!< \brief Enable debug output for implicit-lines agglomeration. */ + bool MG_DebugHaloCoordinates; /*!< \brief Enable halo CV coordinate validation for multigrid. */ su2double MG_Smooth_Coeff; /*!< \brief Smoothing coefficient for multigrid correction smoothing. */ su2double *LocationStations; /*!< \brief Airfoil sections in wing slicing subroutine. */ @@ -3875,6 +3876,12 @@ class CConfig { */ bool GetMG_Implicit_Debug() const { return MG_Implicit_Debug; } + /*!\ + * \brief Get whether halo CV coordinate validation is enabled for multigrid. + * \return True if halo coordinate validation is enabled. + */ + bool GetMG_DebugHaloCoordinates() const { return MG_DebugHaloCoordinates; } + /*!\ * \brief Get the minimum mesh size threshold used to compute effective MG levels. * \return Minimum mesh size per coarsest level. diff --git a/Common/include/geometry/CMultiGridGeometry.hpp b/Common/include/geometry/CMultiGridGeometry.hpp index 32578cf58083..b46e6b183572 100644 --- a/Common/include/geometry/CMultiGridGeometry.hpp +++ b/Common/include/geometry/CMultiGridGeometry.hpp @@ -181,4 +181,11 @@ class CMultiGridGeometry final : public CGeometry { * \param[in] val_marker - Index of the boundary marker. */ void SetMultiGridWallTemperature(const CGeometry* fine_grid, unsigned short val_marker) override; + + /*! + * \brief Validate that halo CV coordinates match corresponding domain CVs on remote ranks (debug feature). + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Multigrid level for reporting. + */ + void ValidateHaloCoordinates(const CConfig* config, unsigned short iMesh) const; }; diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index cfe014879eb5..ebaccd49a0f4 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1969,6 +1969,9 @@ void CConfig::SetConfig_Options() { /*!\brief MG_IMPLICIT_DEBUG\n DESCRIPTION: Enable debug output for implicit-lines agglomeration. DEFAULT: NO \ingroup Config*/ addBoolOption("MG_IMPLICIT_DEBUG", MG_Implicit_Debug, false); + /*!\brief MG_DEBUG_HALO_COORDINATES\n DESCRIPTION: Enable halo CV coordinate validation for multigrid (expensive MPI check). DEFAULT: NO \ingroup Config*/ + addBoolOption("MG_DEBUG_HALO_COORDINATES", MG_DebugHaloCoordinates, false); + /*!\brief MG_MIN_MESHSIZE \ DESCRIPTION: Minimum global mesh size (points) to allow another multigrid level. DEFAULT: 1000 \ingroup Config*/ addUnsignedLongOption("MG_MIN_MESHSIZE", MG_Min_MeshSize, 1000); diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 04273d819e70..5f2f71573445 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -758,6 +758,107 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un << " orphaned halo coarse CVs found - this indicates a logic error!" << endl; } } + + /*--- Debug validation: Verify halo CV coordinates match domain CVs on remote ranks ---*/ + /*--- Note: This validation is deferred until after SetVertex() is called, as vertices ---*/ + /*--- are not yet initialized at the end of the constructor. The validation is performed ---*/ + /*--- by calling ValidateHaloCoordinates() after SetVertex() in the driver. ---*/ + + /*--- For now, we skip this validation in the constructor to avoid segfaults. ---*/ + /*--- TODO: Move this to a separate validation function called after SetVertex(). ---*/ + +#if 0 // Disabled - causes segfault as vertex array not yet initialized + if (size > SINGLE_NODE && config->GetMG_DebugHaloCoordinates()) { + + if (rank == MASTER_NODE) { + cout << "\n--- MG Halo Coordinate Validation (Level " << iMesh << ") ---" << endl; + } + + /*--- For each SEND_RECEIVE marker pair, exchange coordinates and validate ---*/ + for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++) { + if ((config->GetMarker_All_KindBC(iMarker) == SEND_RECEIVE) && (config->GetMarker_All_SendRecv(iMarker) > 0)) { + const auto MarkerS = iMarker; + const auto MarkerR = iMarker + 1; + + const auto send_to = config->GetMarker_All_SendRecv(MarkerS) - 1; + const auto receive_from = abs(config->GetMarker_All_SendRecv(MarkerR)) - 1; + + const auto nVertexS = nVertex[MarkerS]; + const auto nVertexR = nVertex[MarkerR]; + + /*--- Allocate buffers for coordinate exchange ---*/ + vector Buffer_Send_Coord(nVertexS * nDim); + vector Buffer_Receive_Coord(nVertexR * nDim); + + /*--- Pack SEND coordinates (domain CVs being sent) ---*/ + for (auto iVertex = 0ul; iVertex < nVertexS; iVertex++) { + const auto iPoint = vertex[MarkerS][iVertex]->GetNode(); + const auto* Coord = nodes->GetCoord(iPoint); + for (auto iDim = 0u; iDim < nDim; iDim++) { + Buffer_Send_Coord[iVertex * nDim + iDim] = Coord[iDim]; + } + } + + /*--- Exchange coordinates ---*/ + SU2_MPI::Sendrecv(Buffer_Send_Coord.data(), nVertexS * nDim, MPI_DOUBLE, send_to, 0, + Buffer_Receive_Coord.data(), nVertexR * nDim, MPI_DOUBLE, receive_from, 0, + SU2_MPI::GetComm(), MPI_STATUS_IGNORE); + + /*--- Validate RECEIVE coordinates against local halo CVs ---*/ + unsigned long nMismatch = 0; + su2double maxError = 0.0; + su2double tolerance = 1e-10; + + for (auto iVertex = 0ul; iVertex < nVertexR; iVertex++) { + const auto iPoint = vertex[MarkerR][iVertex]->GetNode(); + const auto* Coord_Local = nodes->GetCoord(iPoint); + + su2double error = 0.0; + for (auto iDim = 0u; iDim < nDim; iDim++) { + su2double coord_remote = Buffer_Receive_Coord[iVertex * nDim + iDim]; + su2double diff = fabs(Coord_Local[iDim] - coord_remote); + error += diff * diff; + } + error = sqrt(error); + + if (error > tolerance) { + nMismatch++; + maxError = max(maxError, error); + + if (nMismatch <= 5) { // Only print first 5 mismatches + cout << "COORD MISMATCH [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << ", Vertex " << iVertex << ", Point " << iPoint << "]: "; + cout << "Local=("; + for (auto iDim = 0u; iDim < nDim; iDim++) { + cout << Coord_Local[iDim]; + if (iDim < nDim - 1) cout << ", "; + } + cout << "), Remote=("; + for (auto iDim = 0u; iDim < nDim; iDim++) { + cout << Buffer_Receive_Coord[iVertex * nDim + iDim]; + if (iDim < nDim - 1) cout << ", "; + } + cout << "), Error=" << error << endl; + } + } + } + + if (nMismatch > 0) { + cout << "WARNING [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << "]: " << nMismatch << " coordinate mismatches detected (max error: " + << maxError << ")" << endl; + } else if (nVertexR > 0) { + cout << "INFO [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << "]: All " << nVertexR << " halo CV coordinates match (tol=" << tolerance << ")" << endl; + } + } + } + + if (rank == MASTER_NODE) { + cout << "--- End MG Halo Coordinate Validation ---\n" << endl; + } + } +#endif // Disabled validation code #endif // HAVE_MPI /*--- Update the number of points after the MPI agglomeration ---*/ @@ -1835,3 +1936,104 @@ su2double CMultiGridGeometry::ComputeLocalCurvature(const CGeometry* fine_grid, return max_angle; } + +void CMultiGridGeometry::ValidateHaloCoordinates(const CConfig* config, unsigned short iMesh) const { +#ifdef HAVE_MPI + + int size = SU2_MPI::GetSize(); + int rank = SU2_MPI::GetRank(); + + if (size == SINGLE_NODE || !config->GetMG_DebugHaloCoordinates()) { + return; // Skip if single-node or debug option disabled + } + + if (rank == MASTER_NODE) { + cout << "\n--- MG Halo Coordinate Validation (Level " << iMesh << ") ---" << endl; + } + + /*--- For each SEND_RECEIVE marker pair, exchange coordinates and validate ---*/ + for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++) { + if ((config->GetMarker_All_KindBC(iMarker) == SEND_RECEIVE) && (config->GetMarker_All_SendRecv(iMarker) > 0)) { + const auto MarkerS = iMarker; + const auto MarkerR = iMarker + 1; + + const auto send_to = config->GetMarker_All_SendRecv(MarkerS) - 1; + const auto receive_from = abs(config->GetMarker_All_SendRecv(MarkerR)) - 1; + + const auto nVertexS = nVertex[MarkerS]; + const auto nVertexR = nVertex[MarkerR]; + + /*--- Allocate buffers for coordinate exchange ---*/ + vector Buffer_Send_Coord(nVertexS * nDim); + vector Buffer_Receive_Coord(nVertexR * nDim); + + /*--- Pack SEND coordinates (domain CVs being sent) ---*/ + for (auto iVertex = 0ul; iVertex < nVertexS; iVertex++) { + const auto iPoint = vertex[MarkerS][iVertex]->GetNode(); + const auto* Coord = nodes->GetCoord(iPoint); + for (auto iDim = 0u; iDim < nDim; iDim++) { + Buffer_Send_Coord[iVertex * nDim + iDim] = Coord[iDim]; + } + } + + /*--- Exchange coordinates ---*/ + SU2_MPI::Sendrecv(Buffer_Send_Coord.data(), nVertexS * nDim, MPI_DOUBLE, send_to, 0, + Buffer_Receive_Coord.data(), nVertexR * nDim, MPI_DOUBLE, receive_from, 0, + SU2_MPI::GetComm(), MPI_STATUS_IGNORE); + + /*--- Validate RECEIVE coordinates against local halo CVs ---*/ + unsigned long nMismatch = 0; + su2double maxError = 0.0; + su2double tolerance = 1e-10; + + for (auto iVertex = 0ul; iVertex < nVertexR; iVertex++) { + const auto iPoint = vertex[MarkerR][iVertex]->GetNode(); + const auto* Coord_Local = nodes->GetCoord(iPoint); + + su2double error = 0.0; + for (auto iDim = 0u; iDim < nDim; iDim++) { + su2double coord_remote = Buffer_Receive_Coord[iVertex * nDim + iDim]; + su2double diff = fabs(Coord_Local[iDim] - coord_remote); + error += diff * diff; + } + error = sqrt(error); + + if (error > tolerance) { + nMismatch++; + maxError = max(maxError, error); + + if (nMismatch <= 5) { // Only print first 5 mismatches + cout << "COORD MISMATCH [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << ", Vertex " << iVertex << ", Point " << iPoint << "]: "; + cout << "Local=("; + for (auto iDim = 0u; iDim < nDim; iDim++) { + cout << Coord_Local[iDim]; + if (iDim < nDim - 1) cout << ", "; + } + cout << "), Remote=("; + for (auto iDim = 0u; iDim < nDim; iDim++) { + cout << Buffer_Receive_Coord[iVertex * nDim + iDim]; + if (iDim < nDim - 1) cout << ", "; + } + cout << "), Error=" << error << endl; + } + } + } + + if (nMismatch > 0) { + cout << "WARNING [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << "]: " << nMismatch << " coordinate mismatches detected (max error: " + << maxError << ")" << endl; + } else if (nVertexR > 0) { + cout << "INFO [Rank " << rank << ", Marker " << MarkerS << "/" << MarkerR + << "]: All " << nVertexR << " halo CV coordinates match (tol=" << tolerance << ")" << endl; + } + } + } + + if (rank == MASTER_NODE) { + cout << "--- End MG Halo Coordinate Validation ---\n" << endl; + } + +#endif // HAVE_MPI +} diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 8249e7146011..d4c7e49f0964 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -1480,7 +1480,9 @@ class CSolver { void PerformCFLReductions(CGeometry *geometry, CConfig *config, unsigned short iMesh); void ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, - CConfig *config, unsigned short iMesh); + CConfig *config, unsigned short iMesh, + bool reduceCFL, bool resetCFL, bool canIncrease, + su2double startingIter); public: diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index c8cafb7f6933..01d7048580cb 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -881,6 +881,11 @@ void CDriver::InitializeGeometryFVM(CConfig *config, CGeometry **&geometry) { geometry[iMGlevel]->SetEdges(); geometry[iMGlevel]->SetVertex(geometry[iMGlevel-1], config); + /*--- Validate halo CV coordinates if debug option enabled ---*/ + if (auto* mg_geometry = dynamic_cast(geometry[iMGlevel])) { + mg_geometry->ValidateHaloCoordinates(config, iMGlevel); + } + /*--- Create the control volume structures ---*/ geometry[iMGlevel]->SetControlVolume(geometry[iMGlevel-1], ALLOCATE); diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 2b9ef3d9a3c9..9c8b817f42c8 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1851,9 +1851,35 @@ void CSolver::PerformCFLReductions(CGeometry *geometry, CConfig *config, unsigne } void CSolver::ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, - CConfig *config, unsigned short iMesh) { - const su2double factor = 1.5; - const su2double CFL = factor * config->GetCFL(iMesh - 1); + CConfig *config, unsigned short iMesh, + bool reduceCFL, bool resetCFL, bool canIncrease, + su2double startingIter) { + const unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + + /* Base strategy: use 1.5x multiplier from fine grid */ + const su2double baseFactor = 1.5; + su2double CFL_base = baseFactor * config->GetCFL(iMesh - 1); + + /* Apply same adaptation logic as fine grid to respond to stability issues. + This prevents coarse grids from being too aggressive when fine grid struggles. */ + const su2double CFLMin = config->GetCFL_AdaptParam(2); + const su2double CFLMax = config->GetCFL_AdaptParam(3); + const su2double CFLFactorDecrease = config->GetCFL_AdaptParam(0); + const su2double CFLFactorIncrease = config->GetCFL_AdaptParam(1); + + su2double CFL = CFL_base; + + /* Apply fine grid stability flags to coarse grid */ + if (resetCFL && iter >= startingIter) { + CFL = CFLMin; + } else if (reduceCFL && iter >= startingIter) { + CFL = max(CFLMin, CFL_base * CFLFactorDecrease); + } else if (canIncrease && iter >= startingIter) { + CFL = min(CFLMax, CFL_base * CFLFactorIncrease); + } + + /* Clamp to bounds */ + CFL = max(CFLMin, min(CFLMax, CFL)); config->SetCFL(iMesh, CFL); @@ -1871,6 +1897,12 @@ void CSolver::ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_contain if (solverSpecies) solverSpecies->GetNodes()->SetLocalCFL(iPoint, CFL * CFLSpeciesReduction); } END_SU2_OMP_FOR + + if (rank == MASTER_NODE && iter % 50 == 0) { + cout << "Coarse Grid " << iMesh << " - CFL=" << CFL + << " (base=" << CFL_base << ", reduceCFL=" << reduceCFL + << ", resetCFL=" << resetCFL << ", canIncrease=" << canIncrease << ")" << endl; + } } bool CSolver::DetectFlipFlop(const CFLAdaptParams ¶ms, CConfig *config) { @@ -1919,9 +1951,11 @@ void CSolver::DetectFastDivergence(const CFLAdaptParams ¶ms, CConfig *config /* 2. SHORT-WINDOW TREND DETECTION: Catch degradation over 10 iterations */ bool shortTermDivergence = false; + su2double recentChange = 0.0; + su2double recentSlope = 0.0; + if (currIter >= 10) { const unsigned long SHORT_WINDOW = 10; - su2double recentChange = 0.0; /* Sum the last 10 delta values */ for (unsigned long i = 0; i < SHORT_WINDOW; i++) { @@ -1930,17 +1964,15 @@ void CSolver::DetectFastDivergence(const CFLAdaptParams ¶ms, CConfig *config } /* Average change per iteration over the short window */ - su2double recentSlope = recentChange / su2double(SHORT_WINDOW); + recentSlope = recentChange / su2double(SHORT_WINDOW); - /* Trigger if: residuals increasing OR stalling after initial convergence */ + /* Trigger if: residuals increasing (actual divergence) */ if (recentChange > 0.1) { /* Net increase over 10 iterations */ shortTermDivergence = true; reduceCFL = true; - } else if (recentSlope > -0.0005 && Old_Func < -3.0) { - /* Convergence rate < 0.0005/iter after reaching -3.0 = stalling */ - shortTermDivergence = true; - reduceCFL = true; } + /* Note: Removed stall detection here - it was too aggressive for late-stage convergence. + The stagnation guard in TrackResidualHistory handles this more robustly over a longer window. */ } /* Debug output for fast detection mechanisms */ @@ -1949,6 +1981,8 @@ void CSolver::DetectFastDivergence(const CFLAdaptParams ¶ms, CConfig *config << ": singleIterChange=" << singleIterChange << " spikeDetected=" << spikeDetected << " catastrophicSpike=" << catastrophicSpike + << " recentChange(10iter)=" << recentChange + << " recentSlope=" << recentSlope << " shortTermDivergence=" << shortTermDivergence << endl; } } @@ -2452,6 +2486,37 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, DetectPeakValley(params, config, New_Func, reduceCFL, resetCFL); } } + + /* Synchronize CFL decision flags across all MPI ranks to ensure consistent global strategy. + This prevents different ranks from making conflicting CFL decisions which would cause: + - Inconsistent CFL values for corresponding points across domain decomposition + - Potential divergence when some ranks reduce CFL while others increase + - Multigrid issues when coarse levels don't respond to fine grid instabilities + + Logic: + - reduceCFL: Use logical OR - if ANY rank needs to reduce, ALL should reduce (safety) + - resetCFL: Use logical OR - if ANY rank detects catastrophic divergence, ALL should reset + - canIncrease: Use logical AND - only increase if ALL ranks are stable (conservative) */ + + int reduceCFL_int = reduceCFL ? 1 : 0; + int resetCFL_int = resetCFL ? 1 : 0; + int canIncrease_int = canIncrease ? 1 : 0; + + SU2_MPI::Allreduce(MPI_IN_PLACE, &reduceCFL_int, 1, MPI_INT, MPI_LOR, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(MPI_IN_PLACE, &resetCFL_int, 1, MPI_INT, MPI_LOR, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(MPI_IN_PLACE, &canIncrease_int, 1, MPI_INT, MPI_LAND, SU2_MPI::GetComm()); + + reduceCFL = (reduceCFL_int != 0); + resetCFL = (resetCFL_int != 0); + canIncrease = (canIncrease_int != 0); + + if (rank == MASTER_NODE && iter % 50 == 0) { + cout << "MPI-Synchronized CFL Flags - Iter " << iter + << ": reduceCFL=" << reduceCFL + << " resetCFL=" << resetCFL + << " canIncrease=" << canIncrease << endl; + } + } /* End safe global access, now all threads update the CFL number. */ END_SU2_OMP_SAFE_GLOBAL_ACCESS @@ -2459,12 +2524,11 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, ApplyCFLToAllPoints(geometry[iMesh], solver_container[iMesh], config, iMesh, reduceCFL, resetCFL, canIncrease, params.startingIter); } else { - /* For coarse grids, apply CFL using the dedicated function */ - ApplyCFLToCoarseGrid(geometry[iMesh], solver_container[iMesh], config, iMesh); - - if (rank == MASTER_NODE) { - cout << "imesh=" << iMesh << " Avg CFL=" << config->GetCFL(iMesh) << endl; - } + /* For coarse grids, apply CFL respecting the fine grid stability flags. + This ensures that if fine grid detected divergence/oscillation, coarse grids + also reduce CFL instead of blindly using an aggressive multiplier. */ + ApplyCFLToCoarseGrid(geometry[iMesh], solver_container[iMesh], config, iMesh, + reduceCFL, resetCFL, canIncrease, params.startingIter); } } } From e3beb484bcbcd8dfda3a488ee931ad217be2b39e Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 15 Dec 2025 12:58:55 +0100 Subject: [PATCH 40/41] mpi global cfl --- SU2_CFD/include/solvers/CSolver.hpp | 2 ++ SU2_CFD/src/solvers/CSolver.cpp | 52 +++++++++++++++++------------ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index d4c7e49f0964..b8cb2e06d909 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -1479,6 +1479,8 @@ class CSolver { void PerformCFLReductions(CGeometry *geometry, CConfig *config, unsigned short iMesh); + void SynchronizeCFLFlags(bool &reduceCFL, bool &resetCFL, bool &canIncrease); + void ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh, bool reduceCFL, bool resetCFL, bool canIncrease, diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 9c8b817f42c8..3376b52829c1 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1850,6 +1850,34 @@ void CSolver::PerformCFLReductions(CGeometry *geometry, CConfig *config, unsigne config->SetCFL(iMesh, Avg_CFL_Local); } +void CSolver::SynchronizeCFLFlags(bool &reduceCFL, bool &resetCFL, bool &canIncrease) { +#ifdef HAVE_MPI + /* Synchronize CFL decision flags across all MPI ranks to ensure consistent global strategy. + This prevents different ranks from making conflicting CFL decisions which would cause: + - Inconsistent CFL values for corresponding points across domain decomposition + - Potential divergence when some ranks reduce CFL while others increase + - Multigrid issues when coarse levels don't respond to fine grid instabilities + + Logic: + - reduceCFL: Use logical OR - if ANY rank needs to reduce, ALL should reduce (safety) + - resetCFL: Use logical OR - if ANY rank detects catastrophic divergence, ALL should reset + - canIncrease: Use logical AND - only increase if ALL ranks are stable (conservative) */ + + int reduceCFL_int = reduceCFL ? 1 : 0; + int resetCFL_int = resetCFL ? 1 : 0; + int canIncrease_int = canIncrease ? 1 : 0; + + SU2_MPI::Allreduce(MPI_IN_PLACE, &reduceCFL_int, 1, MPI_INT, MPI_LOR, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(MPI_IN_PLACE, &resetCFL_int, 1, MPI_INT, MPI_LOR, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(MPI_IN_PLACE, &canIncrease_int, 1, MPI_INT, MPI_LAND, SU2_MPI::GetComm()); + + reduceCFL = (reduceCFL_int != 0); + resetCFL = (resetCFL_int != 0); + canIncrease = (canIncrease_int != 0); +#endif + /* No synchronization needed for serial builds - flags remain as computed locally */ +} + void CSolver::ApplyCFLToCoarseGrid(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh, bool reduceCFL, bool resetCFL, bool canIncrease, @@ -2487,28 +2515,8 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, } } - /* Synchronize CFL decision flags across all MPI ranks to ensure consistent global strategy. - This prevents different ranks from making conflicting CFL decisions which would cause: - - Inconsistent CFL values for corresponding points across domain decomposition - - Potential divergence when some ranks reduce CFL while others increase - - Multigrid issues when coarse levels don't respond to fine grid instabilities - - Logic: - - reduceCFL: Use logical OR - if ANY rank needs to reduce, ALL should reduce (safety) - - resetCFL: Use logical OR - if ANY rank detects catastrophic divergence, ALL should reset - - canIncrease: Use logical AND - only increase if ALL ranks are stable (conservative) */ - - int reduceCFL_int = reduceCFL ? 1 : 0; - int resetCFL_int = resetCFL ? 1 : 0; - int canIncrease_int = canIncrease ? 1 : 0; - - SU2_MPI::Allreduce(MPI_IN_PLACE, &reduceCFL_int, 1, MPI_INT, MPI_LOR, SU2_MPI::GetComm()); - SU2_MPI::Allreduce(MPI_IN_PLACE, &resetCFL_int, 1, MPI_INT, MPI_LOR, SU2_MPI::GetComm()); - SU2_MPI::Allreduce(MPI_IN_PLACE, &canIncrease_int, 1, MPI_INT, MPI_LAND, SU2_MPI::GetComm()); - - reduceCFL = (reduceCFL_int != 0); - resetCFL = (resetCFL_int != 0); - canIncrease = (canIncrease_int != 0); + /* Synchronize CFL decision flags across all MPI ranks */ + SynchronizeCFLFlags(reduceCFL, resetCFL, canIncrease); if (rank == MASTER_NODE && iter % 50 == 0) { cout << "MPI-Synchronized CFL Flags - Iter " << iter From 5328e9cefd3c2bd92fbb5505a067ecda64a1e59a Mon Sep 17 00:00:00 2001 From: bigfooted Date: Mon, 15 Dec 2025 21:52:48 +0100 Subject: [PATCH 41/41] some mpi mg cleanup - this also has no agglomeration on the rank interface --- Common/src/geometry/CMultiGridGeometry.cpp | 18 ++++++++---- SU2_CFD/include/solvers/CSolver.hpp | 10 +++++++ SU2_CFD/src/solvers/CSolver.cpp | 33 ++++++++++++++++------ 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 5f2f71573445..455f169bf119 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -171,6 +171,10 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un } /*--- Note that if the marker is a SEND_RECEIVE, then the node is actually an interior point. In that case it can only be agglomerated with another interior point. ---*/ + /*--- Temporarily don't agglomerate SEND_RECEIVE markers---*/ + if (config->GetMarker_All_KindBC(marker_seed[0]) == SEND_RECEIVE) { + agglomerate_seed = false; + } } /*--- If there are two markers, we will agglomerate if any of the @@ -179,9 +183,13 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un /*--- Note that in 2D, this is a corner and we do not agglomerate. ---*/ /*--- In 3D, we agglomerate if the 2 markers are the same. ---*/ if (counter == 2) { + /*--- Only agglomerate if one of the 2 markers are MPI markers. ---*/ - agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || - (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); + //agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) == SEND_RECEIVE) || + // (config->GetMarker_All_KindBC(copy_marker[1]) == SEND_RECEIVE); + /*--- Do not agglomerate if one of the 2 markers are MPI markers. ---*/ + agglomerate_seed = (config->GetMarker_All_KindBC(copy_marker[0]) != SEND_RECEIVE) && + (config->GetMarker_All_KindBC(copy_marker[1]) != SEND_RECEIVE); /*--- Euler walls: check curvature-based agglomeration criterion for both markers ---*/ bool euler_wall_rejected_here = false; @@ -866,11 +874,11 @@ CMultiGridGeometry::CMultiGridGeometry(CGeometry* fine_grid, CConfig* config, un nPoint = Index_CoarseCV; /*--- Console output with the summary of the agglomeration ---*/ - - unsigned long nPointFine = fine_grid->GetnPoint(); + // nijso: do not include halo points in the count + unsigned long nPointFine = fine_grid->GetnPointDomain(); unsigned long Global_nPointCoarse, Global_nPointFine; - SU2_MPI::Allreduce(&nPoint, &Global_nPointCoarse, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(&nPointDomain, &Global_nPointCoarse, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); SU2_MPI::Allreduce(&nPointFine, &Global_nPointFine, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); SetGlobal_nPointDomain(Global_nPointCoarse); diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index b8cb2e06d909..03bda3dfdebd 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include #include @@ -79,6 +81,14 @@ class CSolver { vector NonLinRes_Series; /*!< \brief Vector holding the nonlinear residual indicator series. */ su2double Old_Func, /*!< \brief Old value of the nonlinear residual indicator. */ New_Func; /*!< \brief Current value of the nonlinear residual indicator. */ + + /*--- Adaptive CFL State Variables ---*/ + unsigned long consecutiveReduceIters; /*!< \brief Counter for consecutive iterations requiring CFL reduction. */ + unsigned long consecutiveIncreaseIters; /*!< \brief Counter for consecutive iterations allowing CFL increase. */ + unsigned long oscillationCounter; /*!< \brief Counter for oscillation detection. */ + bool previousOscillation; /*!< \brief Flag for previous oscillation state. */ + std::deque> peaks; /*!< \brief History of residual peaks. */ + std::deque> valleys; /*!< \brief History of residual valleys. */ unsigned short nVar, /*!< \brief Number of variables of the problem. */ nPrimVar, /*!< \brief Number of primitive variables of the problem. */ nPrimVarGrad, /*!< \brief Number of primitive variables of the problem in the gradient computation. */ diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 3376b52829c1..fe182a401500 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -95,6 +95,15 @@ CSolver::CSolver(LINEAR_SOLVER_MODE linear_solver_mode) : System(linear_solver_m IterLinSolver = 0; + /*--- Initialize Adaptive CFL state ---*/ + consecutiveReduceIters = 0; + consecutiveIncreaseIters = 0; + oscillationCounter = 0; + previousOscillation = false; + NonLinRes_Counter = 0; + Old_Func = 0.0; + New_Func = 0.0; + /*--- Initialize pointer for any verification solution. ---*/ VerificationSolution = nullptr; @@ -1702,6 +1711,12 @@ void CSolver::ResetCFLAdapt() { Old_Func = 0; New_Func = 0; NonLinRes_Counter = 0; + consecutiveReduceIters = 0; + consecutiveIncreaseIters = 0; + oscillationCounter = 0; + previousOscillation = false; + peaks.clear(); + valleys.clear(); } @@ -2047,8 +2062,8 @@ void CSolver::DetermineLinearSolverBasedCFLFlags(const CFLAdaptParams ¶ms, C const unsigned long DAMPING_ITERS_REDUCE = 5; const unsigned long DAMPING_ITERS_INCREASE = 10; - static unsigned long consecutiveReduceIters = 0; - static unsigned long consecutiveIncreaseIters = 0; + // static unsigned long consecutiveReduceIters = 0; // Removed static + // static unsigned long consecutiveIncreaseIters = 0; // Removed static unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); @@ -2197,8 +2212,8 @@ void CSolver::DetectPeakValley(const CFLAdaptParams ¶ms, CConfig *config, const su2double SLOPE_UP = 0.1; /* slope > this => divergence (log10 per iter) */ /* Counter to avoid reacting to every single iteration */ - static unsigned long oscillationCounter = 0; - static bool previousOscillation = false; + // static unsigned long oscillationCounter = 0; // Removed static + // static bool previousOscillation = false; // Removed static /* Reconstruct New_Func history from the delta buffer. */ vector funcHistory; @@ -2218,8 +2233,8 @@ void CSolver::DetectPeakValley(const CFLAdaptParams ¶ms, CConfig *config, const unsigned long startIter = currIter - histSize + 1; /* Find peaks and valleys using deques for automatic size management. */ - static deque> peaks; - static deque> valleys; + // static deque> peaks; // Removed static + // static deque> valleys; // Removed static /* Clear old extrema that are outside the lookback window */ while (!peaks.empty() && currIter > EXT_WINDOW && peaks.front().first + EXT_WINDOW < currIter) { @@ -2462,9 +2477,9 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, const CFLAdaptParams params = InitializeCFLAdaptParams(config); //const bool fullComms = (config->GetComm_Level() == COMM_FULL); - static bool reduceCFL = false; - static bool resetCFL = false; - static bool canIncrease = false; + bool reduceCFL = false; + bool resetCFL = false; + bool canIncrease = false; for (unsigned short iMesh = 0; iMesh <= config->GetnMGLevels(); iMesh++) { if (iMesh == MESH_0) {