QuantEcon / QuantEcon/QuantEcon.py

DiscreteDP: opt-in iterative (Krylov) solver for sparse evaluate_policy

Open
#858 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

discuss enhancement
Dominant language
Python
Stars
2.4k
Forks
2.3k
Avg merge
3d 3h
Merged PRs (30d)
3

Description

Summary

For the sparse formulation, DiscreteDP.evaluate_policy solves (I - beta Q_sigma) v = R_sigma with scipy.sparse.linalg.spsolve (SuperLU). On sparsity patterns without exploitable structure, the LU factorization suffers from severe fill-in, making policy evaluation — and hence policy iteration — the bottleneck of the sparse formulation. Krylov methods are immune to fill-in and exploit the structure of this particular system extremely well; this issue proposes adding an opt-in iterative solver option, with the direct solve remaining the default.

Measurements

On a random-pattern instance with n = 3000 states and 5 nonzeros per row of Q_sigma (beta = 0.95, rtol=1e-12):

solver time max abs deviation from spsolve
spsolve (default) 522 ms
splu, best permc_spec (MMD_AT_PLUS_A) 364 ms
gmres 2.6 ms ~7e-12
bicgstab 0.8 ms ~3e-12

SuperLU tuning yields at most ~1.4x; the iterative solvers yield ~200-650x on this pattern.

Why Krylov methods work so well here

A = I - beta Q_sigma is strictly (row-)diagonally dominant — each off-diagonal row sums to at most beta < 1 in absolute value — with condition number bounded by (1 + beta) / (1 - beta) independently of the sparsity pattern. Krylov iterations therefore converge in a small, essentially pattern-independent number of steps, each costing one O(nnz) matvec, while the direct factorization's cost is governed by fill-in, which for scattered patterns approaches dense complexity.

The practical consequence shows up in method rankings: benchmarking the three Q representations across methods shows that for sparse Q with a random pattern, policy iteration — the fastest method in the dense world — is often the slowest method (behind even value iteration at low fill), purely because of the LU fill-in in evaluate_policy. An iterative option would restore policy iteration's expected ranking on such models. (Where the pattern is fill-friendly, e.g. k = 1, the direct solve is already fast; the proposal changes nothing there by default.)

Why opt-in rather than a default change

An iterative solve makes policy evaluation tolerance-based rather than exact-up-to-roundoff, which touches policy iteration's finite-termination/exactness property: near-ties in compute_greedy could in principle resolve differently. The measured deviations are ~1e-12 at rtol=1e-12, but the semantic change should be the user's explicit choice.

Proposed API (for discussion)

A solver option on the constructor or solve, e.g. DiscreteDP(..., lineq_solver='direct') with 'direct' (current behavior, default) and 'bicgstab' (or a callable for full flexibility), used by evaluate_policy in the sparse case. Alternatives welcome — the main design questions are where the option lives, whether to expose the tolerance, and whether to fall back to the direct solve on non-convergence.

For cross-implementation reference: QuantEcon.jl is deliberately not adding this for now — its sparse evaluate_policy dispatches to UMFPACK, which handles these patterns ~19x better than SuperLU, and an iterative option there would require adding a Krylov package dependency. If this proposal proves its worth here, the Julia side can revisit.

Context: this came out of the performance work in #855/#857 and QuantEcon/QuantEcon.jl#388; the measurement scripts are reproducible from the description in #857.

🤖 Generated with Claude Code (Claude Fable 5)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the sparse path in DiscreteDP.evaluate_policy and review the reproducible measurement scripts described in #857. Resolve where the opt-in solver and tolerance belong, preserve the direct solver as the default, and compare iterative results and convergence behavior against spsolve on the described sparse benchmarks.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.