QuantEcon / QuantEcon/ContinuousDPs.jl

ENH: Accept a function for v_init, evaluated at the solver's basis nodes

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

Nobody has claimed this yet.

enhancement
Dominant language
Julia
Stars
17
Forks
11
PR merge metrics
No merged PRs in 30d

Description

Motivation

A good v_init is naturally a function of the state, but the keyword takes a vector of values at the solver's collocation nodes, which couples the caller to two pieces of solver-internal information: the node count (length(basis)) and the node ordering (the gridmake convention, first dimension fastest). The canonical construction is boilerplate that every user has to rediscover:

S = nodes(basis)[1]
v_init = [v_guess(S[i, :]) for i in 1:size(S, 1)]

The need is not hypothetical: for the monetary policy model (MF §9.7.5), PFI from the default v_init = zeros(...) fails — the reward is independent of x, so with a constant value guess the inner objective is flat and the first greedy policy is indeterminate, after which Newton-style policy iteration cycles between corner policies (unstable uncontrolled dynamics + out-of-domain extrapolation in the policy evaluation). Any reasonable non-constant shape (e.g. a rough quadratic $-|s - s^*|^2$, or the LQA solution) fixes it. Users should be able to express such a guess directly as a function, robustly to later changes of the basis size.

Proposal

Let v_init also accept a callable, evaluated internally at the solver's nodes:

res = solve(cdp, solver; v_init = s -> -norm(s - s_target)^2)
  • The callable receives one state point per node, following the same convention as f and g (a scalar for N == 1, a length-N vector for N >= 2), and must return a real number.
  • The vector form stays as is (values at nodes(solver.basis)[1], in order), including its length validation and error message.
  • Applies to both CollocationSolver and LQASolver entrances (they share the same v_init handling).

Implementation sketch

A new _check_v_init(v_init, interp::Interp) method for the callable case: evaluate at _row(interp.S, i) for i in 1:interp.length into a Vector{Float64}; dispatch keeps nothing (zeros) and AbstractVector behavior untouched. Non-breaking 0.3.x addition.

Tests

  • Callable vs. manually evaluated vector give identical results (N = 1 and N = 2).
  • The flat-degeneracy case: a non-constant callable v_init as the documented remedy (cf. the monetary policy example).
  • Wrong-length vector error unchanged; a callable returning a non-real value errors informatively.

🤖 Generated with Claude Code (Claude Fable 5)

Contributor guide

No contributing guide indexed for this repository

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 at the shared v_init handling and the proposed _check_v_init(v_init, interp::Interp) entry point, then trace how CollocationSolver and LQASolver pass through it. Run the existing solver tests first, add coverage for N = 1 and N = 2 callable inputs, and verify vector behavior and invalid callable results remain clear and unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
julia
Domain
backend
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.