Skip to content

Add NonsmoothNewton descent strategy - #123

Open
Kaiyao-Ge wants to merge 3 commits into
polyfem:mainfrom
Kaiyao-Ge:nonsmooth-newton
Open

Kaiyao-Ge wants to merge 3 commits into
polyfem:mainfrom
Kaiyao-Ge:nonsmooth-newton

Conversation

@Kaiyao-Ge

@Kaiyao-Ge Kaiyao-Ge commented Oct 25, 2026 •

Copy link
Copy Markdown

NonsmoothNewton descent strategy

Adds NonsmoothNewton, a Newton variant for objectives that split as f = s + n, with s smooth and n a stiff nonsmooth term such as a contact barrier.

When the current iterate has active nonsmooth terms, the strategy minimizes a reduced model: a frozen quadratic of s plus the exact n. It does this with Levenberg–Marquardt steps in a growing subspace. Every segment of the resulting path is certified with max_step_size. The end point, or a retraction along the path, is accepted by its true decrease. On other iterates the Newton direction is line searched as usual. If the strategy fails, the solver falls back to the existing Newton chain.

Near convergence the true decrease of a step falls below what the energy can resolve in floating point, so the acceptance test can no longer verify the path. From then on the strategy hands the rest of the solve to the Newton chain, whose line search compares gradient norms instead of energies once the gradient is small (line_search/use_grad_norm_tol). On a pin-cushion contact scene (steps 11–20), the handoff and the energy resolution cut the solve time from 205 s to 131 s.

Changes

  • Problem: optional nonsmooth-split callbacks (has_nonsmooth_term, smooth_*, nonsmooth_*, metric_diagonal) and HessianBlock. The defaults report that the problem has no split, so existing problems are unaffected.
  • DescentStrategy::take_step: a hook that lets a strategy return a step it has already verified. Solver::minimize then applies x += step and skips the line search. The default returns false, so behaviour is unchanged for all existing strategies.
  • Solver::minimize evaluated the objective at the start point twice (once only for a debug message). It now evaluates it once.
  • Solver::create("NonsmoothNewton"): builds the chain NonsmoothNewton → Newton chain (Newton::create_solver(true, ...)) → GD.
  • nonlinear-solver-spec.json: the NonsmoothNewton options (max_inner, relative_stop, fraction_to_boundary, rho_min, max_columns, handoff, resolution, guard/*) and internal debug/* switches.
    • handoff (off / first / any, default any): hand the solve to the Newton chain after an outer iteration whose inner loop stopped at the noise floor of the energy, or whose path is below the energy resolution (first: only if no inner step was accepted before). Except for off, also when the gradient norm is below line_search/use_grad_norm_tol.
    • resolution (default true): count 32 eps |E| as the resolution of the true energy E, in the inner noise floor and in the acceptance. A path whose model decrease does not exceed it is not verified by the true energy; the Newton step is line searched instead.
  • New linear solver Eigen::SimplicialLLT (Eigen's simplicial Cholesky; in the factory, available_solvers and linear-solver-spec.json). Like Eigen::CholmodSupernodalLLT, it reports a non-positive pivot, so NonsmoothNewton also runs in builds without CHOLMOD.
  • Tests: tests/test_nonsmooth_newton.cpp. They cover option parsing, a log-barrier problem (with and without the handoff, and the resolution on an energy with a large constant), and the no-split case, where the result is bitwise equal to Newton. The log-barrier test uses Eigen::CholmodSupernodalLLT when CHOLMOD is available and Eigen::SimplicialLLT otherwise, and also checks a solve with Eigen::SimplicialLLT.

When the problem provides the split, the linear solver must be an LLT that reports indefinite matrices: Eigen::CholmodSupernodalLLT or Eigen::SimplicialLLT. This is checked on first use.

Near convergence the true decrease of a path falls below what the energy
can resolve, so the acceptance test can no longer verify it. Two options
(spec and Options):

- handoff (off | first | any, default any): after an outer iteration whose
  inner loop stopped at the noise floor or whose path is below the
  resolution (first: only if no inner step was accepted before), and when
  the gradient norm is below line_search/use_grad_norm_tol, the rest of the
  solve is left to the Newton chain. A declined direction is not counted
  as a failure. Counters handoff_noise_floor, handoff_grad_norm and
  handoff_iterations in the solver info.
- resolution (default true): 32 eps |E| counts in the inner noise floor,
  and a path whose model decrease does not exceed it is not verified by
  the true energy (the Newton step is line searched instead).

Tests: option parsing; the log-barrier problem with and without the
handoff; fewer retractions and true energy evaluations with the
resolution on an energy with a large constant.
Kaiyao-Ge added a commit to Kaiyao-Ge/polyfem that referenced this pull request Oct 30, 2026
Pin polysolve to f39cb83 (polyfem/polysolve#123): NonsmoothNewton hands
the rest of a solve to the Newton chain once the true energy no longer
verifies the path (handoff "any" and the energy resolution by default).
The five-cubes test now checks that Newton chain iterations of a reduced
solve only follow a handoff or a failed direction, instead of requiring
that the chain is never used.
NonsmoothNewton needs an LLT that reports indefinite matrices, which so far
meant Eigen::CholmodSupernodalLLT only, so builds without CHOLMOD could not
run the strategy at all.

Eigen's SimplicialLLT reports a non-positive pivot as NumericalIssue, which
EigenDirect turns into the same runtime_error the strategy already catches.
Add it as a linear solver (factory, available_solvers, spec) and let
NonsmoothNewton accept it. The log-term test now runs in every build, with
CHOLMOD when available and SimplicialLLT otherwise, and also checks a solve
with SimplicialLLT.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant