{"id": "solver_convergence", "title": "Nonlinear and linear solver convergence", "description": "Residual histories of the pseudo-transient Newton\u2013Krylov solver from a uniform free stream, with element-block-Jacobi and with p-multigrid preconditioned FGMRES. The linear systems are solved to a fixed relative tolerance (inexact Newton), so once the pseudo-time step is large the residual is expected to fall by about that factor per step: linear convergence at the forcing rate, not quadratic convergence. The checks require a residual reduction of ten orders, a terminal contraction per step within a factor five of the forcing term (the signature of an exact Jacobian), and fewer than half the Krylov iterations with p-multigrid. What those iterations cost is measured and reported without a threshold: operator applications counted from the definition of the multigrid cycle, and the wall time of each solve repeated with compiled kernels.", "passed": true, "checks": [{"name": "Euler NACA 0012, p = 2, block Jacobi: converged 10 orders (final / reference residual)", "value": 2.8556789626667722e-12, "expected": "<= 1e-10", "passed": true}, {"name": "Euler NACA 0012, p = 2, block Jacobi: terminal residual contraction per step (geometric mean)", "value": 0.01092349080764064, "expected": "<= 0.05 (5 x forcing term lin_tol = 0.01)", "passed": true}, {"name": "Euler NACA 0012, p = 2, p-multigrid: converged 10 orders (final / reference residual)", "value": 1.9269714042731163e-12, "expected": "<= 1e-10", "passed": true}, {"name": "Euler NACA 0012, p = 2, p-multigrid: terminal residual contraction per step (geometric mean)", "value": 0.010202973779147107, "expected": "<= 0.05 (5 x forcing term lin_tol = 0.01)", "passed": true}, {"name": "Euler NACA 0012, p = 2: Krylov iterations, p-MG / block Jacobi", "value": 0.11774461028192372, "expected": "< 0.5", "passed": true}, {"name": "Laminar cylinder Re = 40, p = 2, block Jacobi: converged 10 orders (final / reference residual)", "value": 1.5243051477556068e-12, "expected": "<= 1e-10", "passed": true}, {"name": "Laminar cylinder Re = 40, p = 2, block Jacobi: terminal residual contraction per step (geometric mean)", "value": 0.009804965437628396, "expected": "<= 0.05 (5 x forcing term lin_tol = 0.01)", "passed": true}, {"name": "Laminar cylinder Re = 40, p = 2, p-multigrid: converged 10 orders (final / reference residual)", "value": 8.206225189067135e-11, "expected": "<= 1e-10", "passed": true}, {"name": "Laminar cylinder Re = 40, p = 2, p-multigrid: terminal residual contraction per step (geometric mean)", "value": 0.007632799462010629, "expected": "<= 0.05 (5 x forcing term lin_tol = 0.01)", "passed": true}, {"name": "Laminar cylinder Re = 40, p = 2: Krylov iterations, p-MG / block Jacobi", "value": 0.0837727006444054, "expected": "< 0.5", "passed": true}, {"name": "Euler NACA 0012, p = 3, block Jacobi: converged 10 orders (final / reference residual)", "value": 3.3017212093569666e-12, "expected": "<= 1e-10", "passed": true}, {"name": "Euler NACA 0012, p = 3, block Jacobi: terminal residual contraction per step (geometric mean)", "value": 0.011794497915098887, "expected": "<= 0.05 (5 x forcing term lin_tol = 0.01)", "passed": true}, {"name": "Euler NACA 0012, p = 3, p-multigrid: converged 10 orders (final / reference residual)", "value": 5.904232699718599e-11, "expected": "<= 1e-10", "passed": true}, {"name": "Euler NACA 0012, p = 3, p-multigrid: terminal residual contraction per step (geometric mean)", "value": 0.005823041105637618, "expected": "<= 0.05 (5 x forcing term lin_tol = 0.01)", "passed": true}, {"name": "Euler NACA 0012, p = 3: Krylov iterations, p-MG / block Jacobi", "value": 0.11323529411764706, "expected": "< 0.5", "passed": true}, {"name": "cost of the two preconditioners measured: the repeated, compiled solves converged", "value": true, "expected": "true", "passed": true}], "charts": [{"id": "residual_history", "type": "history", "title": "Residual history", "xlabel": "nonlinear iteration", "ylabel": "|Rw|", "xscale": "linear", "yscale": "log", "series": [{"name": "Euler NACA 0012, p = 2: block Jacobi", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "y": [0.36631730062126405, 0.23826584172228052, 0.15370117035149228, 0.12817596569622133, 0.10752573028973587, 0.09112613018468585, 0.07089769550949979, 0.050733660955148044, 0.03057220054841528, 0.014031204111450991, 0.005399242779977114, 0.0009968754506499277, 6.0153508166752416e-05, 8.025691646237864e-07, 7.8548554441374e-09, 7.794821885368372e-11, 1.0460846090450235e-12]}, {"name": "Euler NACA 0012, p = 2: p-multigrid", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "y": [0.36631730062126405, 0.23844581596133768, 0.15374004395307636, 0.12781485790877178, 0.10725028759274766, 0.09105235633002108, 0.07071343453830017, 0.050729701266445104, 0.030612331050425066, 0.014101187143796821, 0.005443632265224679, 0.0010003265927669108, 6.027670461860158e-05, 6.645878365818233e-07, 6.451663079122225e-09, 4.5688887107967934e-11, 7.058829631876945e-13]}, {"name": "Laminar cylinder Re = 40, p = 2: block Jacobi", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "y": [20.211642498760146, 13.142313100908119, 9.478568192018562, 6.108694776471968, 4.092793322206127, 3.389472341201512, 1.9724596669693568, 0.48374537164425707, 0.14189247427393722, 0.07457498780155894, 0.019916585806238268, 0.0024486950952824793, 3.2684009104552386e-05, 3.255688528382319e-07, 3.212086630811475e-09, 3.080871070545609e-11]}, {"name": "Laminar cylinder Re = 40, p = 2: p-multigrid", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "y": [20.211642498760146, 13.132315996555882, 9.448050230328043, 6.067779028209372, 4.067506669859229, 3.344893392320446, 1.9407210901513492, 0.4771406756175777, 0.1413829289348074, 0.0735676685185911, 0.019653801198282316, 0.0024503723804434456, 2.8469335720249287e-05, 1.9200170692938138e-07, 1.6586128978574531e-09]}, {"name": "Euler NACA 0012, p = 3: block Jacobi", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "y": [0.3117162568223455, 0.31332490966754845, 0.138704436975788, 0.09708390007073502, 0.07495802609085085, 0.06173875712230483, 0.04727163909448835, 0.03364377749888053, 0.019713069910744684, 0.008617195709468017, 0.003528644242962642, 0.0006818045505373294, 4.686420383661007e-05, 6.272800538624894e-07, 6.151199052370397e-09, 6.001095399011799e-11, 1.0292001764517014e-12]}, {"name": "Euler NACA 0012, p = 3: p-multigrid", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "y": [0.3117162568223455, 0.3137271030664038, 0.1387804317970415, 0.09678753784836279, 0.07477902637818376, 0.0616540979281185, 0.04715955260258479, 0.033634653260928, 0.019712330077802726, 0.008626472475620747, 0.003526073970871693, 0.0006747064039711829, 4.621566456722808e-05, 5.427792123494718e-07, 3.3127143142568714e-09, 1.840445316564373e-11]}]}], "tables": [{"id": "effort", "title": "Solver effort", "columns": ["case", "preconditioner", "levels", "Newton steps", "Krylov iterations", "max Krylov / step", "converged", "final / reference residual", "terminal contraction (mean)", "terminal contraction (worst step)", "wall time [s]"], "rows": [["Euler NACA 0012, p = 2", "block Jacobi", "2", 16, 1206, 182, true, 2.8556789626667722e-12, 0.01092349080764064, 0.013420250320390573, 58.548890113830566], ["Euler NACA 0012, p = 2", "p-multigrid", "2, 1, 0", 16, 142, 17, true, 1.9269714042731163e-12, 0.010202973779147107, 0.015449773629189398, 104.26196789741516], ["Laminar cylinder Re = 40, p = 2", "block Jacobi", "2", 15, 1707, 320, true, 1.5243051477556068e-12, 0.009804965437628396, 0.00996110519357569, 48.19896745681763], ["Laminar cylinder Re = 40, p = 2", "p-multigrid", "2, 1, 0", 14, 143, 19, true, 8.206225189067135e-11, 0.007632799462010629, 0.008638532044235911, 73.45884323120117], ["Euler NACA 0012, p = 3", "block Jacobi", "3", 16, 1360, 202, true, 3.3017212093569666e-12, 0.011794497915098887, 0.017150205221219777, 74.66162467002869], ["Euler NACA 0012, p = 3", "p-multigrid", "3, 1, 0", 15, 154, 23, true, 5.904232699718599e-11, 0.005823041105637618, 0.006103244632228029, 79.46926188468933]]}, {"id": "cost", "title": "What the Krylov iterations cost", "columns": ["case", "preconditioner", "Krylov iterations", "fine-level operator applications per Krylov iteration", "fine-level operator applications, total", "block solves per iteration", "wall time, first solve [s]", "wall time, repeated solve (compiled) [s]", "repeated solve converged", "Krylov iterations, repeated solve"], "rows": [["Euler NACA 0012, p = 2", "block Jacobi", 1206, 1.0, 1206.0, 1, 58.548890113830566, 81.52042198181152, true, 1206], ["Euler NACA 0012, p = 2", "p-multigrid", 142, 7.555555555555555, 1072.888888888889, 16, 104.26196789741516, 71.39364409446716, true, 142], ["Euler NACA 0012, p = 2", "p-MG / block Jacobi", 0.11774461028192372, null, 0.8896259443523126, null, null, 0.8757761841615123, null, null], ["Laminar cylinder Re = 40, p = 2", "block Jacobi", 1707, 1.0, 1707.0, 1, 48.19896745681763, 64.33114123344421, true, 1707], ["Laminar cylinder Re = 40, p = 2", "p-multigrid", 143, 7.555555555555555, 1080.4444444444443, 16, 73.45884323120117, 60.264970779418945, true, 143], ["Laminar cylinder Re = 40, p = 2", "p-MG / block Jacobi", 0.0837727006444054, null, 0.6329492937577296, null, null, 0.9367931241998336, null, null], ["Euler NACA 0012, p = 3", "block Jacobi", 1360, 1.0, 1360.0, 1, 74.66162467002869, 71.86597466468811, true, 1360], ["Euler NACA 0012, p = 3", "p-multigrid", 154, 6.4375, 991.375, 16, 79.46926188468933, 99.467520236969, true, 154], ["Euler NACA 0012, p = 3", "p-MG / block Jacobi", 0.11323529411764706, null, 0.728952205882353, null, null, 1.3840697312054004, null, null]]}], "provenance": {"version": "0.4.0", "commit": "39979fcf902ad6116eb550b881b4278d4599c8e2", "dirty": false, "describe": "39979fcf90", "device": {"platform": "cpu", "device": "cpu:0", "device_kind": "cpu", "jax": "0.11.2", "x64": true}, "timestamp": "2026-11-12T10:42:49-0500"}, "wall_time": 890.13, "notes": "Measured in this run: below 0.0001 of the reference residual the residual fell by a factor 5.8e-03 to 1.2e-02 per step (geometric means; worst single step 1.7e-02) with a forcing term of 0.01. p-multigrid needed 0.08 to 0.12 of the Krylov iterations of block Jacobi. That is a ratio of iteration counts, not of work: one multigrid-preconditioned iteration applies the operator several times (smoothing sweeps on every level). In fine-level operator applications the ratio was 0.63 to 0.89, and in wall time of the repeated solve, with every kernel compiled, 0.88 to 1.38 (below 1: p-multigrid cheaper). The wall times of the first solves in the effort table include one-off JIT compilation. Machine load while the study ran: The timings were taken on a busy machine (other processes used 51 % of the capacity of the 8 CPUs while the study ran).", "level": "quick"}