Troubleshooting#
Use this page when something fails, or when the script runs but the output does not look trustworthy.
If you are still in the first-time setup stage, go back to Installation and Environment. The purpose here is separation and diagnosis, not initial setup.
The best troubleshooting habit in FinHJB is to separate three layers:
environment problems,
modeling mistakes,
numerical configuration problems.
If you mix them together, it becomes hard to tell whether the problem is import-related, equation-related, or solver-related.
Quick Triage#
Symptom |
Most likely layer |
First page to pair with this one |
|---|---|---|
import fails before solving starts |
environment |
|
example runs but boundary diagnostics look wrong |
numerical workflow |
|
your custom model raises key or shape errors |
model implementation |
|
you are unsure whether to use |
workflow choice |
Environment Failures#
ModuleNotFoundError: No module named 'finhjb'#
Cause:
you are running
pythondirectly instead of the project environment,or the package was not installed.
First fix:
uv sync
uv run python -c "import finhjb as fjb; print(fjb.__all__[:5])"
ModuleNotFoundError: No module named 'jax'#
Cause:
dependencies are missing in the active environment.
First fix:
uv sync
If you were using plain python, switch to uv run python.
Matplotlib display / backend errors#
Typical symptom:
the script imports successfully and then crashes on display or backend setup.
First fix:
export MPLBACKEND=Agg
MPLBACKEND=Agg uv run python src/example/BCW2011Liquidation.py
Loader Errors#
TypeError when using load_grid, load_grids, or load_sensitivity_result#
Cause:
the file was saved as one type but loaded with the wrong loader.
Correct mapping:
Saved with |
Load with |
|---|---|
|
|
|
|
|
|
The loaders validate types on load, so a wrong pairing fails loudly by design.
Workflow Selection Errors#
NotImplementedError: Solver.boundary_update() requires the model class to implement update_boundary(grid)#
Cause:
boundary_update()was called on a model that does not implementModel.update_boundary(grid).
What to do:
use
solve()if boundaries are fixed,use
boundary_search()if you want a root/search condition,implement
update_boundary(grid)only if your model has an explicit outer update rule.
This is not a solver bug. It is the guardrail for the boundary-update workflow.
Boundary Search Problems#
boundary_search() returns but grid.d2v[-1] is not close to zero#
This usually means one of the following:
the target condition is wrong,
the fixed-boundary solve is already unstable,
the search bracket is poor,
the grid is too coarse to resolve the right tail cleanly.
Check in this order:
print(grid.dv[-1], grid.d2v[-1])
print(grid.boundary)
Then ask:
does the target in
boundary_condition()really encode the desired contact condition?does the fixed-boundary solve behave reasonably before search?
for bisection, does the bracket plausibly contain the root?
Bisection does not settle#
Most common causes:
the lower and upper bounds are not economically meaningful,
the target does not switch sign over the bracket,
the model equations are mis-specified, so no sensible root exists in that range.
What to try:
inspect the target value at a few candidate boundaries manually,
narrow the bracket around a region that looks economically plausible,
confirm the base
solve()problem is stable before searching.
Policy Iteration Problems#
The run finishes, but policy values look surprising#
Do not assume “surprising” means “wrong.”
For BCW:
liquidation investment is strongly negative at low cash,
hedging
psiis pinned at-piin distressed states,positive investment only appears toward the healthier right side of the grid.
Before editing the code, compare your pattern to:
Also remember the case-specific patterns:
refinancing should raise
p(0)above liquidation value and create an interior issuance targetm,credit lines can push
p'(0)close to1and keep investment positive aroundw=0,frictionless hedging should move the payout boundary left relative to costly margin.
Convergence is slow or unstable#
Things to adjust in order:
verify equations and boundary formulas,
simplify the initial policy guess,
reduce ambition and get a smaller, stable baseline run first,
only then change
number, tolerances, or search method.
Changing tolerances before checking the economics usually wastes time.
Derivative and Grid Problems#
dv or d2v looks explosive#
Possible causes:
the value function is not being solved consistently,
the grid is too coarse in a region with sharp curvature,
denominators such as
dvord2vare used unstably in policy formulas,boundaries are inconsistent with the economics.
What to inspect:
df = grid.df
print(df[["s", "v", "dv", "d2v"]].head())
print(df[["s", "v", "dv", "d2v"]].tail())
Questions to ask:
is
vincreasing with cash?is
dv[-1]approaching the right-boundary target?is
d2v[-1]moving toward zero when it should?are the extreme values confined to the left tail, or are they everywhere?
When should I increase number?#
Increase the grid size after the formulation is already sensible, not before.
Good reason:
the curves are qualitatively correct but too jagged.
Bad reason:
the model does not converge at all and you have not checked the equations.
policy_guess Confusion#
Why does changing policy_guess affect convergence?#
Because it changes the starting point.
policy_guess=Truemeans “use the initializer exactly as the initial policy.”policy_guess=Falsemeans “construct a policy container, then immediately push it through an improvement step.”
If your initializer is already strong, True may converge faster.
If your initializer is weak or economically poor, False can rescue the run.
grid.aux Raises NotImplementedError#
Cause:
Grid.auxcallsModel.auxiliary(grid),and your model has not implemented that optional hook.
This is expected behavior.
What to do:
ignore
grid.auxuntil you actually need custom diagnostics,or implement
auxiliary(grid)in your model.
A Good Minimal Debugging Loop#
When your custom model fails, debug in this order:
uv run python -c "import finhjb"to confirm environment,get
solver.solve()working on fixed boundaries,inspect
state.df.head()andstate.df.tail(),only then add
boundary_update()orboundary_search(),only after that run sensitivity analysis.
This sequence prevents “stacked failures” where multiple moving parts hide the real problem.
When To Stop Debugging And Rethink The Formulation#
Step back and re-check the model if:
the right boundary never approaches its intended contact condition,
different search methods all fail in similar ways,
the policy formulas require dividing by values that routinely approach zero,
the solution shape is economically implausible everywhere, not just at one tail.
That usually signals a formulation problem, not a tuning problem.