Skip to content

Support purely backward looking models (VAR, nonlinear growth models)#214

Draft
Copilot wants to merge 41 commits intomainfrom
copilot/add-backward-model-functionality
Draft

Support purely backward looking models (VAR, nonlinear growth models)#214
Copilot wants to merge 41 commits intomainfrom
copilot/add-backward-model-functionality

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Dec 31, 2025

Summary

This PR adds support for purely backward looking models and implements the :newton algorithm option for simulating them.

Changes Made

New Features

Default algorithm for backward looking models:

  • Added DEFAULT_ALGORITHM_BACKWARD_LOOKING = :newton constant
  • The following functions now default to :newton algorithm when model is backward looking (nFuture_not_past_and_mixed == 0):
    • get_irf
    • get_conditional_forecast
    • plot_irf
    • plot_irf!
    • plot_conditional_forecast
    • plot_conditional_forecast!
  • Users can still explicitly specify algorithm = :first_order if needed

New reference keyword argument:

  • Added reference parameter to get_irf, plot_irf, plot_irf!, plot_conditional_forecast, and plot_conditional_forecast! with options:
    • :steady_state (default for non-backward models): deviations from relevant steady state (existing behavior), black horizontal reference line
    • :baseline (default for backward models): deviations from the no-shock path starting from initial_state, black line shows baseline path (can be time-varying)
  • For reference = :baseline:
    • Computes the no-shock forward iteration from initial_state
    • Plots show IRF data relative to this time-varying baseline
    • Black reference line becomes the baseline path instead of horizontal steady state
    • ✅ Dual axis reference now uses initial state value (not first period of simulation)
  • Useful for backward looking models with explosive dynamics where deviations from baseline path are more meaningful
  • Ignored when levels = true
  • Added docstring documentation

Structural Refactoring

structures.jl:

  • Renamed jacobian_func to jacobian_state_func for clarity
  • jacobian_shock_func - jacobian w.r.t. shocks (for conditional forecasting)
  • Added lu_buffer::𝒮.LinearCache for LinearSolve caching in Newton iterations
  • Kept jac_state_buffer and jac_shock_buffer for conditional forecasting

macros.jl:

  • Initialize backward_looking_solution in solution struct with updated buffers
  • Add :newton to available algorithms

MacroModelling.jl:

  • Backward_looking struct is filled only in the algorithm == :newton condition
  • write_newton_simulation_functions!() creates buffers
  • Added validation for reference parameter

get_functions.jl:

  • Updated get_irf and get_conditional_forecast to use dynamic default algorithm based on model type
  • Added reference parameter with validation and documentation
  • Baseline path computation now controlled by reference parameter
  • Conditional forecasting uses standard \ operator for matrix operations
  • Skip find_newton_shocks when no conditions in a period

StatsPlotsExt.jl:

  • Updated standard_subplot to accept optional baseline_path and initial_value parameters
  • plot_irf computes baseline path and initial state in levels when reference = :baseline for backward looking models
  • Dual axis reference uses initial state value (not first period of simulation)
  • Added reference parameter to plot_irf!, plot_conditional_forecast, and plot_conditional_forecast!
  • ✅ Removed Statistics.jl dependency - replaced mean with sum/length for computing reference values

Tests (test_backward_looking_models.jl)

  • ✅ All 59 tests pass
  • Tests now use default :newton algorithm (no need to explicitly specify for backward looking models)
  • Added reference = :baseline tests for explosive model identity
  • Added conditional forecasting tests for Solow growth model
  • Verified identity: irf_baseline + baseline_lev ≈ irf_lev

Status

  • ✅ Default algorithm for backward looking models is :newton in all relevant functions
  • reference parameter implemented for get_irf, plot_irf, plot_irf!, plot_conditional_forecast, plot_conditional_forecast!
  • reference = :baseline plots time-varying baseline path instead of horizontal steady state line
  • ✅ Dual axis reference uses initial state value (not first period of simulation)
  • ✅ Removed Statistics.jl as a heavy dependency
  • ✅ All 59 backward looking model tests pass
  • ✅ Code review comments addressed
Original prompt

add the possibility to work with purely backward looking models. they might or might not have a steady state and use newton style solver to simulate them forward. you also need an interface to set the initial state


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits December 31, 2025 17:16
…on-Expr types

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
…/etc functions

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for backward looking models with initial state interface Support purely backward looking models (VAR, nonlinear growth models) Dec 31, 2025
Copilot AI requested a review from thorek1 December 31, 2025 17:27
@thorek1
Copy link
Copy Markdown
Owner

thorek1 commented Dec 31, 2025

@copilot try again to fix the parsing related issues

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 31, 2025

@copilot try again to fix the parsing related issues

Fixed in 1efac13. There was a syntax error on line 519 where haskey(bounds, x.args[2].args[1]] had ] instead of ). The backward looking model (VAR2) now parses and runs IRFs correctly.

@thorek1
Copy link
Copy Markdown
Owner

thorek1 commented Dec 31, 2025

@copilot add a test file with the var model and also make nonlinear backward looking models work

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 31, 2025

@copilot add a test file with the var model and also make nonlinear backward looking models work

Added in db479ab. Created test/test_backward_looking_models.jl with:

  • VAR model: 2-variable linear backward looking model
  • Solow growth model: Nonlinear backward looking model with capital accumulation

Both models test nFuture_not_past_and_mixed == 0, steady state computation, solution, IRFs, and simulation. Tests are included in the basic test set.

@thorek1
Copy link
Copy Markdown
Owner

thorek1 commented Jan 1, 2026

@copilot add another algorithm option: newton
this should in the case of the SolowGrowth model (a nonlinear backward looking model with a steady state) use the newton algorithm (rely on algorithm already implemented) to find the values for the next period

@thorek1
Copy link
Copy Markdown
Owner

thorek1 commented Jan 1, 2026

@copilot add another algorithm option: newton
this should, in the case of the SolowGrowth model (a nonlinear backward looking model with a steady state), use the newton algorithm (rely on the newton algorithm already implemented) to find the values for the next period. this means you need to write code that writes a jacobian function that takes the current past values and shocks as input and returns the derivatives of the nonlinear equations wrt present values. you also need to write code that generates a function that evaluates the residuals of the nonlienar functions given the past and present values, as well as the shocks.

…cobian functions

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 1, 2026

@copilot add another algorithm option: newton
this should, in the case of the SolowGrowth model (a nonlinear backward looking model with a steady state), use the newton algorithm (rely on the newton a...

Implemented in 884b1fa. Added:

  1. write_newton_simulation_functions!() - generates residual function F(x_t, x_{t-1}, e_t) and jacobian ∂F/∂x_t using Symbolics.jl
  2. create_newton_state_update() - creates state update function that uses Newton iterations to solve for present values
  3. Updated parse_algorithm_to_state_update() to handle :newton algorithm
  4. Added tests verifying first_order and newton produce identical results for linear VAR model (max diff < 1e-16)

Comment thread src/MacroModelling.jl
past_not_future_and_mixed_idx = 𝓂.timings.past_not_future_and_mixed_idx

# Create the state update function
function state_update_newton(state::Vector{T}, shock::Vector{S}) where {T, S}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot you need to add the steady state values before evaluating the residual and jacobian functions and substract it before returning the results

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1359ddb. The create_newton_state_update() function now:

  1. Adds steady state values to past variables from the input state (converting from deviations to levels)
  2. Evaluates residual and jacobian functions with values in levels
  3. Subtracts steady state from the result before returning (converting back to deviations)

Verified: VAR model still matches first_order exactly (max diff < 1e-16), and Solow growth model produces slightly different results as expected for nonlinear models.

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copilot AI requested a review from thorek1 January 2, 2026 20:41
@thorek1
Copy link
Copy Markdown
Owner

thorek1 commented Jan 2, 2026

@copilot call the argument "plot_baseline" "reference" instead

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 2, 2026

@copilot call the argument "plot_baseline" "reference" instead

Done in 394b6f0. Renamed plot_baseline to reference in plot_irf. The parameter is now a Symbol with options :steady_state or :baseline, defaulting to :baseline for backward looking models and :steady_state otherwise.

@thorek1
Copy link
Copy Markdown
Owner

thorek1 commented Jan 2, 2026

@copilot also rename "deviations_from" as "reference"

Copy link
Copy Markdown
Owner

@thorek1 thorek1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also for get_conditional_forecast using backward looking models the default algorithm should be :newton

Comment thread test/test_backward_looking_models.jl Outdated
conditions[z_idx, 1] = 0.02 # Condition z in period 1
conditions[z_idx, 2] = 0.01 # Condition z in period 2

cf_newton = get_conditional_forecast(SolowGrowth, conditions, algorithm = :newton)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot also here given a backward looking model the default should be :newton and not first order (same for the plot irf and plot conditional forecast functions)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5948038. Updated plot_irf!, plot_conditional_forecast, and plot_conditional_forecast! to use :newton as default algorithm for backward looking models. Also fixed duplicate reference parameter in plot_irf.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 2, 2026

@copilot also rename "deviations_from" as "reference"

Done in d4a0ade. Renamed deviations_from to reference in both get_irf and plot_irf. Updated all related constants (DEFAULT_REFERENCE), docstrings (REFERENCE®), and tests.

…ions

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 2, 2026

also for get_conditional_forecast using backward looking models the default algorithm should be :newton

Done in 5948038. Updated get_conditional_forecast to use :newton as default algorithm for backward looking models, using the same pattern as get_irf: algorithm::Symbol = 𝓂.timings.nFuture_not_past_and_mixed == 0 ? DEFAULT_ALGORITHM_BACKWARD_LOOKING : DEFAULT_ALGORITHM

Copy link
Copy Markdown
Owner

@thorek1 thorek1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the plot_irf, plot_irf!, plot_conditional_forecast, and plot_conditional_forecast! functions, add the reference argument and implement the logic such that when it is :steady_state, then the current behaviour is executed. when it is :baseline, then the solid black line which normally refers to the reference steady state becomes the baseline. the baseline is the dynamics in levels starting from the initial state and propagating forward without shocks

…ast, plot_conditional_forecast!

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 2, 2026

in the plot_irf, plot_irf!, plot_conditional_forecast, and plot_conditional_forecast! functions, add the reference argument and implement the logic such that when it is :steady_state, then the current...

Done in f23d759. Added reference parameter to plot_irf, plot_irf!, plot_conditional_forecast, and plot_conditional_forecast!:

  • When reference = :steady_state: current behavior - horizontal black line at steady state
  • When reference = :baseline: black line becomes the baseline path (no-shock dynamics from initial_state in levels)

For plot_irf, the baseline path is computed when reference = :baseline for backward looking models using the :newton algorithm, and passed to standard_subplot which now supports time-varying reference lines.

Copy link
Copy Markdown
Owner

@thorek1 thorek1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

address comments

Comment thread ext/StatsPlotsExt.jl Outdated
import DataStructures: OrderedSet
import SparseArrays: SparseMatrixCSC
import NLopt
import Statistics: mean
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot dont use Statistics here. this is a heavy dependency. also mean is a very easy function. find alternatives

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ef33187. Removed the Statistics import and replaced mean(filter(isfinite, baseline_path)) with sum(finite_baseline_vals) / length(finite_baseline_vals).

Comment thread ext/StatsPlotsExt.jl Outdated
# irf_data is in deviations from baseline, so we add baseline_path to get levels
plot_data = irf_data # .+ baseline_path
finite_baseline_vals = filter(isfinite, baseline_path)
ref_for_dual_axis = first(finite_baseline_vals)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot use the initial state as reference instead of the first period value (which this implies)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7b76497. The dual axis reference now uses the initial state value in levels (initial_state + NSSS) instead of the first period of the baseline simulation.

…value

Co-authored-by: thorek1 <13523097+thorek1@users.noreply.github.com>
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.

2 participants