|
PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
|
This page tracks momentum-solver options accepted by the current configuration and their runtime implementation status.
Runtime selection is controlled by -mom_solver_type, produced from solver.yml (strategy.momentum_solver). Dispatch currently happens in function FlowSolver within the step orchestrator.
Accepted YAML values:
Explicit RK4 -> EXPLICIT_RKDual Time Picard Jameson RK -> DUALTIME_PICARD_JAMESON_RKNewton Krylov -> newton_krylovOnly implemented values are exposed in the enum, parser, and dispatcher. New solver values should be added only with a real implementation plus matching parser, docs, and test updates.
For compatibility, the former Dual Time Picard RK4 YAML display name and DUALTIME_PICARD_RK4 C CLI value still select the Jameson solver. New configuration and code must use the Jameson names.
Beyond the explicit RK4 verification path, PICurv provides two distinct implicit-in-physical-time momentum-solution approaches. They are genuinely different algorithms, not two names for the same method, and they have different numerical behavior, controls, and maturity. Choose one with strategy.momentum_solver.
Dual-time Picard–Jameson (Dual Time Picard Jameson RK) — the established, comparatively robust default. It advances the implicit BDF2 update with a fixed-point / pseudo-time iteration using staged Jameson RK smoothing. It is controlled through pseudo-CFL and pseudo-iteration settings, may need conservative pseudo-CFL values, and can converge slowly in demanding high-Reynolds-number or near-inviscid regimes. It does not use SNES/GMRES matrix-free Newton linearizations. Full details: Dual-Time Picard Jameson RK Momentum Solver.
Newton–Krylov (Newton Krylov) — a newer matrix-free nonlinear solver. It solves the momentum residual with PETSc SNES, using matrix-free Jacobian–vector products (finite-difference Jv), an inner GMRES Krylov solve, and a backtracking line search. It exposes nonlinear, line-search, GMRES, and preconditioner controls. The supported baseline is unpreconditioned matrix-free differencing; the supported alternative is a provisional frozen-momentum point-block preconditioner, whose PETSc block-Jacobi backend is chosen internally. It requires a deterministic residual (its Cartesian boundary state is reconstructed from the current trial vector before boundary conditions are applied). Its convergence diagnostics and failure modes (SNES/KSP reasons) differ from the Picard solver. It has a narrower, explicitly validated scope — see its dedicated page: Newton–Krylov Momentum Solver.
Selection guidance (within the evidence available today):
SNES/KSP-style diagnostics, keeping its scope restrictions (Section 1 of Newton–Krylov Momentum Solver) in mind.EXPLICIT_RK: implemented by MomentumSolver_Explicit_RungeKutta4DUALTIME_PICARD_JAMESON_RK: implemented by MomentumSolver_DualTime_Picard_JamesonRKnewton_krylov: implemented by MomentumSolver_NewtonKrylov (matrix-free PETSc SNES/GMRES; none or frozen-momentum/point-block preconditioning; narrow validated version-one scope — see Newton–Krylov Momentum Solver)Main controls consumed by implemented solvers:
-mom_max_pseudo_steps-mom_atol-mom_rtol-mom_resid_atol, -mom_resid_rtol-pseudo_cfl, -min_pseudo_cfl, -max_pseudo_cfl-pseudo_cfl_growth_factor, -pseudo_cfl_reduction_factor-mom_dt_jameson_residual_norm_noise_allowance_factor-mom_ratio_ema_alphaDefaults and final option ingestion are in function CreateSimulationContext during startup parsing.
For the dual-time Jameson solver, max_iterations bounds accepted pseudo-iterations. A separate hard cap of 3 × max_iterations limits total attempts (accepted plus rejected) to prevent infinite rejection loops. Convergence is decided by the residual: residual_abs_pass OR (residual_rel_pass AND update_pass). The absolute residual test (|R| ≤ residual_absolute_tol · resid_ref, dimensionless tolerance) is sufficient on its own; the relative test is paired with the relative_tol update guard. absolute_tol takes no part while a residual tolerance is set. Both residual tolerances default to enabled; setting both non-positive selects the legacy update-only branch. See 3. Convergence and Adaptive Rollback.
The dual-time controller uses one global pseudo-CFL and globally accepts or rolls back a complete four-stage trial. The selected next pseudo-CFL is carried directly into the next physical timestep. step_tol/-imp_stol remains accepted only as a deprecated compatibility input and is unused by active momentum solvers.
pseudo_cfl.* values are dimensionless Courant numbers (Phase 3+), not fractions of the physical timestep dt. The solver computes dtau = pseudo_cfl / lambda_max where lambda_max is the global maximum convective spectral radius of the current velocity field. This makes pseudo_cfl independent of dt, grid size, and flow speed. The stable range for the 4-stage Jameson RK smoother is pseudo_cfl ≈ 0–2.83; the shipped defaults are initial: 0.5, maximum: 2.0.
Current testing is uneven by solver path:
FlowSolver-side unit testsMomentumSolver_DualTime_Picard_JamesonRK is exercised mainly through smoke and runtime orchestrationMomentumSolver_Explicit_RungeKutta4 still needs a direct positive-path harnessThat means the momentum stack is currently a stronger regression gate than bespoke debugging surface.
Both momentum solvers are built on one residual implementation:
Picard reaches it once per Jameson RK stage; Newton–Krylov reaches it once per residual evaluation, including every finite-difference probe of the matrix-free Jacobian. Neither calls it once per physical timestep, and the ratio is not fixed: it varies with pseudo-iteration count, line-search backtracks, and Krylov iterations.
That makes the shared RHS a hazardous place to keep state. Anything advanced on each call - a filter, a ramp, a moving average, an integral controller term, a relaxation counter - becomes a function of how many evaluations happened before it rather than of the solution. Two things break:
MomentumNewtonKrylov_FormResidual() requires F(X) to be a deterministic function of the trial vector alone; otherwise the finite-difference Jacobian action (F(X+hv) - F(X))/h is inconsistent.The rule is to gate every such update on simCtx->step and reuse the resolved value for the rest of the step. The same hazard applies to boundary handlers, because ApplyBoundaryConditions() runs each handler's PreStep three times per call and is itself called from inside both solvers' iteration loops.
Worked example. The driven periodic flow controller had this defect in two places at once, and fixing only the first was not enough:
| State | Location | Symptom when ungated |
|---|---|---|
bulkVelocityCorrection | PreStep in src/BC_Handlers.c | source recomputed from the trial vector mid-solve |
| smoothing EMA on the force | ComputeDrivenChannelFlowSource(), src/BodyForces.c | applied force walked 0.5, 0.75, 0.875 ... toward target within one step |
Measured on a 4-step run: 379 force evaluations produced 42 distinct force values before the fix and 3 after - one per timestep. tests/smoke/run_driven_periodic_regression.sh asserts the force is piecewise constant per step. See 5.2 Update cadence, and why it matters and the contract in include/BodyForces.h.
Required steps:
src/momentumsolvers.c,variables.h, setup.c, picurv_cli/core.py),For user-facing contract updates, also update:
This page describes Momentum Solver Implementations within the PICurv workflow. For CFD users, the most reliable reading strategy is to map the page content to a concrete run decision: what is configured, what runtime stage it influences, and which diagnostics should confirm expected behavior.
Treat this page as both a conceptual reference and a runbook. If you are debugging, pair the method/procedure described here with monitor output, generated runtime artifacts under runs/<run_id>/config, and the associated solver/post logs so numerical intent and implementation behavior stay aligned.