From 4323aa527f03af553a6232a582c636cfb2c6d093 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 5 Dec 2025 10:35:33 -0800 Subject: [PATCH 1/4] petsc solver: Catch exceptions from run_rhs If run_rhs throws an exception then it should be caught, and SNES notified that an error occurred. --- src/solver/impls/petsc/petsc.cxx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/solver/impls/petsc/petsc.cxx b/src/solver/impls/petsc/petsc.cxx index ed48d87312..98e58d7afa 100644 --- a/src/solver/impls/petsc/petsc.cxx +++ b/src/solver/impls/petsc/petsc.cxx @@ -903,8 +903,19 @@ PetscErrorCode PetscSolver::rhs(BoutReal t, Vec udata, Vec dudata, bool linear) load_vars(const_cast(udata_array)); VecRestoreArrayRead(udata, &udata_array); - // Call RHS function - run_rhs(t, linear); + try { + // Call RHS function + run_rhs(t, linear); + } catch (BoutException& e) { + // Simulation might fail, e.g. negative densities + // if timestep too large + output_warn.write("WARNING: BoutException thrown: {}\n", e.what()); + + // Tell SNES that the input was out of domain + SNESSetFunctionDomainError(snes); + // Note: Returning non-zero error here leaves vectors in locked state + return 0; + } // Save derivatives to PETSc BoutReal* dudata_array; From 210ce59f09a782a58cc95d4797b451bc4558bb3a Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 5 Dec 2025 12:18:12 -0800 Subject: [PATCH 2/4] petsc solver: Add diagnostics compatible with snes solver If solver:diagnose is set then a line will be printed for each internal (TS) step. The format is the same as the `snes` solver, so the same post-processing scripts can be used. --- src/solver/impls/petsc/petsc.cxx | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/solver/impls/petsc/petsc.cxx b/src/solver/impls/petsc/petsc.cxx index 98e58d7afa..e7e0296677 100644 --- a/src/solver/impls/petsc/petsc.cxx +++ b/src/solver/impls/petsc/petsc.cxx @@ -165,6 +165,39 @@ PetscErrorCode PetscMonitor(TS ts, PetscInt UNUSED(step), PetscReal t, Vec X, vo PetscFunctionBegin; auto* s = static_cast(ctx); + + if (s->diagnose) { + // Print diagnostic information. + // Using the same format at the SNES solver + + SNESConvergedReason reason; + SNESGetConvergedReason(s->snes, &reason); + + PetscReal timestep; + TSGetTimeStep(s->ts, ×tep); + + // SNES failure counter is only reset when TSSolve is called + static PetscInt prev_snes_failures = 0; + PetscInt snes_failures; + TSGetSNESFailures(s->ts, &snes_failures); + snes_failures -= prev_snes_failures; + prev_snes_failures += snes_failures; + + // Get number of iterations + int nl_its; + SNESGetIterationNumber(s->snes, &nl_its); + int lin_its; + SNESGetLinearSolveIterations(s->snes, &lin_its); + + output.print("\r"); // Carriage return for printing to screen + output.write("Time: {}, timestep: {}, nl iter: {}, lin iter: {}, reason: {}", + t, timestep, nl_its, lin_its, static_cast(reason)); + if (snes_failures > 0) { + output.write(", SNES failures: {}", snes_failures); + } + output.write("\n"); + } + if (t < s->next_output) { // Not reached output time yet => return PetscFunctionReturn(0); From 1ddf5b8cff07e67ea47ffc30f83570190aafd390 Mon Sep 17 00:00:00 2001 From: bendudson <219233+bendudson@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:24:44 +0000 Subject: [PATCH 3/4] Apply clang-format changes --- src/solver/impls/petsc/petsc.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solver/impls/petsc/petsc.cxx b/src/solver/impls/petsc/petsc.cxx index e7e0296677..d0535f069d 100644 --- a/src/solver/impls/petsc/petsc.cxx +++ b/src/solver/impls/petsc/petsc.cxx @@ -190,8 +190,8 @@ PetscErrorCode PetscMonitor(TS ts, PetscInt UNUSED(step), PetscReal t, Vec X, vo SNESGetLinearSolveIterations(s->snes, &lin_its); output.print("\r"); // Carriage return for printing to screen - output.write("Time: {}, timestep: {}, nl iter: {}, lin iter: {}, reason: {}", - t, timestep, nl_its, lin_its, static_cast(reason)); + output.write("Time: {}, timestep: {}, nl iter: {}, lin iter: {}, reason: {}", t, + timestep, nl_its, lin_its, static_cast(reason)); if (snes_failures > 0) { output.write(", SNES failures: {}", snes_failures); } From 50764ef185a55314f3a8adc7e6a3f7196c55e684 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 19 Dec 2025 08:43:13 -0800 Subject: [PATCH 4/4] snes solver: Wrap all run_rhs calls in try...catch Calls to run_rhs may throw an exception if given out of domain values. Catch and return error code if this happens. --- src/solver/impls/snes/snes.cxx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/solver/impls/snes/snes.cxx b/src/solver/impls/snes/snes.cxx index c10095af53..23b82982f4 100644 --- a/src/solver/impls/snes/snes.cxx +++ b/src/solver/impls/snes/snes.cxx @@ -993,7 +993,13 @@ int SNESSolver::run() { ierr = VecRestoreArrayRead(snes_x, &xdata); CHKERRQ(ierr); } - run_rhs(simtime); + + try { + run_rhs(simtime); + } catch (BoutException& e) { + output_error.write("ERROR: BoutException thrown: {}\n", e.what()); + return 1; + } // Copy derivatives back { @@ -1168,7 +1174,12 @@ int SNESSolver::run() { ierr = VecRestoreArrayRead(output_x, &xdata); CHKERRQ(ierr); } - run_rhs(target); // Run RHS to calculate auxilliary variables + + try { + run_rhs(target); // Run RHS to calculate auxilliary variables + } catch (BoutException& e) { + output_error.write("ERROR: BoutException thrown: {}\n", e.what()); + } if (call_monitors(target, s, getNumberOutputSteps()) != 0) { break; // User signalled to quit