Skip to content

LINELET segfaults, no LINELET regression tests #2877

Open

Description

@bigfooted

Describe the bug
There are no regression tests for LINELET and there are reports about LINELET crashing with a segmentation fault for certain testcases. The crashes need to be confirmed preferably with an existing testcase using LINELET.

Activity

  1. Syphonicc commented on Aug 26, 2026

    @Syphonicc

    I'd like to look at this. Starting by enabling LINELET on some existing wall-bounded cases (s809, SST flat plate, laminar NACA0012) to try to reproduce a crash.

  2. Syphonicc commented on Aug 26, 2026

    @Syphonicc

    I had a look at this. No segfault so far, but LINELET does cause a clean divergence on one case and degrades convergence on another.

    Three wall-bounded cases, 500 iterations, only LINEAR_SOLVER_PREC changed (all other settings identical):

    Case Default prec LINELET
    rans/s809/turb_S809 (unstructured) −5.22 −1.14, with 226 non-physical points
    rans/flatplate/turb_SST_flatplate (structured) −7.15 −6.10
    navierstokes/naca0012/lam_NACA0012 (structured) −8.19 −8.66

    So it helps slightly on laminar NACA0012, hurts on the SST flat plate, and is bad on s809.

    Running s809 further, it diverges outright at iteration 1140:

    Error in "void CSolver::SetResidual_RMS(const CGeometry*, const CConfig*)":
    SU2 has diverged (Residual > 10^20 detected).
    

    The same config without LINELET converges to −9.02 at 10k iterations.

    One thing that might be relevant: with multigrid, the linelets become very short on coarse levels. SU2 reports:

    lam_NACA0012:        23, 3, 4 points per line (avg) across MG levels
    turb_SST_flatplate:  32, 13, 3 points per line (avg)
    turb_S809:           32 points per line (avg)
    

    Three or four points per line seems close to degenerate - I'd guess the linelet structure isn't being rebuilt meaningfully on the coarse grids.

    I also confirmed there are currently no configs in TestCases that set LINEAR_SOLVER_PREC= LINELET - the only occurrence is a comment listing the available options in turbomachinery/Aachen_turbine/aachen_3D_MP_restart.cfg. So there's no existing coverage at all.

    Happy to add regression tests for LINELET once there's a case that behaves sensibly with it - lam_NACA0012 is the only one of the three I'd currently trust. Also happy to test other cases if you know which ones the segfault reports came from.

    Built from develop, serial (-Dwith-mpi=disabled), GCC 15.2.1, Fedora 42.

  3. SiddDart commented on Aug 28, 2026

    @SiddDart

    The coarse-grid behavior is particularly interesting. If the linelets are collapsing to only a few points on the coarser MG levels, that could definitely explain why the preconditioner behaves so differently across cases.

    lam_NACA0012 seems like a good starting point for regression coverage given that it currently behaves sensibly with LINELET. I’ll report back if I find any other cases where LINELET gives stable convergence

  4. Syphonicc commented on Aug 28, 2026

    @Syphonicc

    The coarse-grid behavior is particularly interesting. If the linelets are collapsing to only a few points on the coarser MG levels, that could definitely explain why the preconditioner behaves so differently across cases.

    lam_NACA0012 seems like a good starting point for regression coverage given that it currently behaves sensibly with LINELET. I’ll report back if I find any other cases where LINELET gives stable convergence

    Thanks that'd be useful. I'll start putting together a LINELET regression case based on lam_NACA0012 in the meantime. If you turn up other cases where it's stable, especially anything without multigrid, that'd help narrow down whether the coarse-level linelets are actually the cause

  5. bigfooted commented on Sep 9, 2026

    @bigfooted
    ContributorAuthor

    This might be an MPI problem... to be continued...

  6. Syphonicc commented on Sep 16, 2026

    @Syphonicc

    I've opened #2899, which adds a LINELET regression case: laminar NACA0012, serial, the existing config plus LINEAR_SOLVER_PREC= LINELET, compared against the default ILU(0). It covers the missing-tests half of this issue only, not the segfault reports. It is also a multigrid case, so it doesn't address the no-multigrid question from earlier.

    A correction to my earlier table: those numbers came from incremental builds that I no longer trust across a develop merge. On a clean build, lam_NACA0012 at 500 iterations gives:

    Default (ILU(0)) LINELET
    rms[Rho] −8.276 −8.263

    So LINELET does not help on this case, contrary to what I wrote before. It is essentially level. Run to −12, ILU(0) needs 5909 iterations and LINELET 6108. I'll re-take the turb_S809 and turb_SST_flatplate rows on a clean build before citing them again.

    On the coarse levels: the config sets MGLEVEL= 3, but SU2 reduces it to 2 because level 3 has only 233 CVs (below MG_MIN_MESHSIZE=500), so three grids are used. The linelets average 23, 4 and 3 points per line on those three grids (I listed them as 23, 3, 4 earlier).

    My build is serial only, so I can't check the possible MPI cause at the moment.

  7. rois1995 commented on Oct 1, 2026

    @rois1995
    Contributor

    I can reproduce a LINELET segfault, and I think it is the MPI problem suspected above.

    Reproduction (current develop code, MPI build): TestCases/navierstokes/naca0012/lam_NACA0012.cfg (hybrid mesh v1, 3 multigrid levels), ITER= 5, plus LINEAR_SOLVER_PREC= LINELET.

    ranks result
    1, 2 runs
    4, 8 segmentation fault (address 0x8) in CSysMatrix::BuildLineletPreconditioner, called from CSysSolve::Solve

    The same case with the default preconditioner runs on 4 ranks. This is why serial regression tests cannot see it.

    Cause. CGeometry::GetLineletInfo uses linelets.empty() as its "already computed" flag. A rank that owns no solid-wall vertex (common with a few partitions) never gets a non-empty list, so:

    1. BuildLineletPreconditioner only resizes LineletUpper/Vector/InvDiag when linelets is non-empty, but then loops over LineletUpper[iThread]. On such a rank that indexes an empty vector.
    2. Even without the crash, GetLineletInfo recomputes on every preconditioner application on those ranks, including its two MPI_Allreduce calls. The other ranks do not call them, so the collectives do not match.

    Fix I tested (about 10 lines): add bool computed to CLineletInfo, return on it in GetLineletInfo, and make BuildLineletPreconditioner always call GetLineletInfo and always resize the per-thread vectors. With it the same case runs on 2, 4 and 8 ranks; after 5 iterations the residuals on 4 ranks match the default preconditioner closely (rms[rho] -4.966 vs -4.968).

    I have not opened a PR. Happy to do so, together with the regression test from #2899 run on 4 ranks, if that is useful.

    Diff:

    diff --git a/Common/include/geometry/CGeometry.hpp b/Common/include/geometry/CGeometry.hpp
    index 9d3e574b43..a5ec356e1b 100644
    --- a/Common/include/geometry/CGeometry.hpp
    +++ b/Common/include/geometry/CGeometry.hpp
    @@ -226,6 +226,8 @@ class CGeometry {
         static passivedouble ALPHA_ISOTROPIC() { return 0.8; }
         enum : unsigned long { MAX_LINELET_POINTS = 32 }; /*!< \brief Maximum points per linelet. */
     
    +    bool computed = false; /*!< \brief Whether the structure was computed (ranks may have no linelets). */
    +
         std::vector<std::vector<unsigned long>> linelets; /*!< \brief Point indices for each linelet. */
     
         /*!< \brief Index of the linelet of each point ("linelets" transfered to points). */
    diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp
    index 360da1aaaa..f6976bc99c 100644
    --- a/Common/src/geometry/CGeometry.cpp
    +++ b/Common/src/geometry/CGeometry.cpp
    @@ -4339,7 +4339,8 @@ void CGeometry::ColorMGLevels(unsigned short nMGLevels, const CGeometry* const*
     
     const CGeometry::CLineletInfo& CGeometry::GetLineletInfo(const CConfig* config) const {
       auto& li = lineletInfo;
    -  if (!li.linelets.empty() || nPoint == 0) return li;
    +  if (li.computed || nPoint == 0) return li;
    +  li.computed = true;
     
       li.lineletIdx.resize(nPoint, CLineletInfo::NO_LINELET);
     
    diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp
    index 503e2ad489..03796fee76 100644
    --- a/Common/src/linear_algebra/CSysMatrix.cpp
    +++ b/Common/src/linear_algebra/CSysMatrix.cpp
    @@ -1461,12 +1461,11 @@ void CSysMatrix<ScalarType>::BuildLineletPreconditioner(const CGeometry* geometr
       const auto nThreads = omp_get_max_threads();
     
       BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS {
    -    const auto& li = geometry->GetLineletInfo(config);
    -    if (!li.linelets.empty()) {
    -      LineletUpper.resize(nThreads);
    -      LineletVector.resize(nThreads);
    -      LineletInvDiag.resize(nThreads);
    -    }
    +    /*--- All ranks must call this (it communicates), even those without linelets. ---*/
    +    geometry->GetLineletInfo(config);
    +    LineletUpper.resize(nThreads);
    +    LineletVector.resize(nThreads);
    +    LineletInvDiag.resize(nThreads);
       }
       END_SU2_OMP_SAFE_GLOBAL_ACCESS
     
  8. Syphonicc commented on Oct 1, 2026

    @Syphonicc

    Thanks @rois1995, that matches what I found. The same fix is already in #2899 (commit d4851e8, since Sep 27), together with lam_naca0012_linelet in parallel_regression.py on 4 ranks (2 ranks doesn't trigger it). One small difference: #2899 also drops the || nPoint == 0 early return in GetLineletInfo, since a rank with no points would otherwise skip the Allreduce/Allgather the other ranks call, and it guards the average-linelet-size print against a divide by zero when there are no linelets anywhere. A review on #2899 would be very welcome.

  9. rois1995 commented on Oct 1, 2026

    @rois1995
    Contributor

    Sure, I'll look into that over the next few days!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions