Skip to content
Draft
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
657e910
Initial plan
Copilot Nov 28, 2025
96e5676
Add support for incomplete parameter definitions in @parameters macro
Copilot Nov 28, 2025
e8b2e39
Fix documentation placeholders in inspect.jl
Copilot Nov 28, 2025
89b4905
Merge branch 'main' into copilot/allow-optional-parameters-in-functions
thorek1 Nov 28, 2025
103fbe4
Merge branch 'main' into copilot/allow-optional-parameters-in-functions
thorek1 Nov 28, 2025
5c52a2b
simplified logic. need to have parameters sorted so that they can be …
thorek1 Nov 28, 2025
b192adc
add test script
thorek1 Nov 28, 2025
e69ca87
first working version
thorek1 Nov 28, 2025
6e4d0a0
call parameters macro if not called already. dont report missing para…
thorek1 Nov 29, 2025
113b961
Merge branch 'copilot/allow-optional-parameters-in-functions' of http…
thorek1 Nov 29, 2025
c93abb3
add tests to basic
thorek1 Nov 29, 2025
b02e728
Merge branch 'main' into copilot/allow-optional-parameters-in-functions
thorek1 Nov 29, 2025
880f8fe
Fix missing parameter detection to include parameters from calibratio…
Copilot Nov 29, 2025
af26601
Revert last commit and keep only parameter processing changes for mis…
Copilot Nov 30, 2025
33fecc9
Fix parameter expansion in calibration equations to properly capture …
Copilot Dec 1, 2025
f15277b
Fix parameter ordering to preserve declaration order instead of sorti…
Copilot Dec 1, 2025
ecf57ae
Restore 'Provide parameters later' test and implement parameter order…
Copilot Dec 1, 2025
e245224
Improve declared parameter detection to not rely on NaN values
Copilot Dec 1, 2025
d9753df
Remove parameter_order argument from write_parameters_input! and use …
Copilot Dec 1, 2025
a61e3a5
Minor optimization: avoid unnecessary collect() in write_parameters_i…
Copilot Dec 1, 2025
6bd75f0
Fix include path for Backus_Kehoe_Kydland_1992 model and update param…
thorek1 Dec 1, 2025
672f09f
make sure order is passed on an edited when missing parameters are fi…
thorek1 Dec 1, 2025
2aa6784
rm has_missing_parameters
thorek1 Dec 1, 2025
26f22ed
incomplete ordering of parameters
thorek1 Dec 1, 2025
65b0b21
Fix parameter ordering issue and restore has_missing_parameters function
Copilot Dec 1, 2025
4a18a3b
Remove parameter reordering to maintain SS_solve_func compatibility
Copilot Dec 2, 2025
b59ec36
Restore parameter reordering and delay SS_solve_func construction unt…
Copilot Dec 2, 2025
2d04dc2
Fix parameter reordering logic to correctly identify declared vs miss…
Copilot Dec 2, 2025
561e547
Fix functions_written flag and SS_solve_func setup for models with in…
Copilot Dec 2, 2025
bbfca82
Fix parameter reordering by clearing NSSS_solver_cache to rebuild bou…
Copilot Dec 3, 2025
4bb1642
Fix parameter symbol conversion in write_parameters_input! for Symbol…
Copilot Dec 3, 2025
9e2db97
Fix OrderedDict creation for single Pair parameters
Copilot Dec 3, 2025
32a467c
Fix NSSS_solver_cache clearing to use while loop instead of empty!
Copilot Dec 3, 2025
c6bbd26
Add additional cache clear before solve_steady_state! when all parame…
Copilot Dec 3, 2025
cbcc905
Move 'Provide parameters later' test to beginning of basic testset an…
Copilot Dec 3, 2025
0180adf
Remove redundant cache clearing before solve_steady_state! call
Copilot Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 188 additions & 37 deletions src/MacroModelling.jl

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions src/inspect.jl
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,82 @@ function get_calibrated_parameters(𝓂::ℳ; values::Bool = false)::Union{Vecto
end


"""
$(SIGNATURES)
Returns the parameters which are required by the model but have not been assigned values in the `@parameters` block. These parameters must be provided via the `parameters` keyword argument in functions like `get_irf`, `get_SS`, `simulate`, etc. before the model can be solved.

# Arguments
- $MODEL®

# Returns
- `Vector{String}` of the missing parameters.

# Examples
```jldoctest
using MacroModelling

@model RBC_incomplete begin
1 / c[0] = (β / c[1]) * (α * exp(z[1]) * k[0]^(α - 1) + (1 - δ))
c[0] + k[0] = (1 - δ) * k[-1] + q[0]
q[0] = exp(z[0]) * k[-1]^α
z[0] = ρ * z[-1] + std_z * eps_z[x]
end

@parameters RBC_incomplete begin
std_z = 0.01
ρ = 0.2
# Note: α, β, δ are not defined
end

get_missing_parameters(RBC_incomplete)
# output
3-element Vector{String}:
"α"
"β"
"δ"
```
"""
function get_missing_parameters(𝓂::ℳ)::Vector{String}
replace.(string.(𝓂.missing_parameters), "◖" => "{", "◗" => "}")
end


"""
$(SIGNATURES)
Returns whether the model has missing parameters that need to be provided before solving.

# Arguments
- $MODEL®

# Returns
- `Bool` indicating whether the model has missing parameters.

# Examples
```jldoctest
using MacroModelling

@model RBC begin
1 / c[0] = (β / c[1]) * (α * exp(z[1]) * k[0]^(α - 1) + (1 - δ))
c[0] + k[0] = (1 - δ) * k[-1] + q[0]
q[0] = exp(z[0]) * k[-1]^α
z[0] = ρ * z[-1] + std_z * eps_z[x]
end

@parameters RBC begin
std_z = 0.01
ρ = 0.2
end

has_missing_parameters(RBC)
# output
true
```
"""
function has_missing_parameters(𝓂::ℳ)::Bool
!isempty(𝓂.missing_parameters)
end


"""
$(SIGNATURES)
Returns the parameters contained in the model equations. Note that these parameters might be determined by other parameters or calibration equations defined in the `@parameters` block.
Expand Down
177 changes: 109 additions & 68 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,8 @@ macro model(𝓂,ex...)
$parameters,
$parameters,
$parameter_values,

Symbol[], # missing_parameters - to be filled by @parameters

Dict{Symbol, Float64}(), # guess

Expand Down Expand Up @@ -1084,6 +1086,7 @@ macro parameters(𝓂,ex...)
silent = false
symbolic = false
precompile = false
report_missing_parameters = true
perturbation_order = 1
guess = Dict{Symbol,Float64}()
simplify = true
Expand All @@ -1098,6 +1101,8 @@ macro parameters(𝓂,ex...)
verbose = x.args[2] :
x.args[1] == :silent && x.args[2] isa Bool ?
silent = x.args[2] :
x.args[1] == :report_missing_parameters && x.args[2] isa Bool ?
report_missing_parameters = x.args[2] :
x.args[1] == :precompile && x.args[2] isa Bool ?
precompile = x.args[2] :
x.args[1] == :perturbation_order && x.args[2] isa Int ?
Expand Down Expand Up @@ -1459,7 +1464,29 @@ macro parameters(𝓂,ex...)
calib_eq_parameters, calib_equations_list, ss_calib_list, par_calib_list = expand_calibration_equations($calib_eq_parameters, $calib_equations_list, $ss_calib_list, $par_calib_list, [mod.$𝓂.parameters_in_equations; mod.$𝓂.var])
calib_parameters_no_var, calib_equations_no_var_list = expand_indices($calib_parameters_no_var, $calib_equations_no_var_list, [mod.$𝓂.parameters_in_equations; mod.$𝓂.var])

@assert length(setdiff(setdiff(setdiff(union(reduce(union, par_calib_list,init = []),mod.$𝓂.parameters_in_equations),calib_parameters),calib_parameters_no_var),calib_eq_parameters)) == 0 "Undefined parameters: " * repr([setdiff(setdiff(setdiff(union(reduce(union,par_calib_list,init = []),mod.$𝓂.parameters_in_equations),calib_parameters),calib_parameters_no_var),calib_eq_parameters)...])
# Calculate missing parameters instead of asserting
# Include parameters from:
# 1. par_calib_list - parameters used in calibration equations (e.g., K_ss in "K[ss] = K_ss | beta")
# 2. parameters_in_equations - parameters used in model equations
# 3. par_no_var_calib_list - parameters used in parameter definitions (e.g., rho{H}{H} in "rho{F}{F} = rho{H}{H}")
# Subtract:
# 1. calib_parameters - parameters with explicit values (e.g., "α = 0.5")
# 2. calib_parameters_no_var - parameters defined as functions of other parameters (e.g., "α = alpha_param")
# 3. calib_eq_parameters - parameters determined by calibration equations (e.g., "beta" in "K[ss] = K_ss | beta")
all_required_params = union(
reduce(union, par_calib_list, init = Set{Symbol}()),
reduce(union, $par_no_var_calib_list, init = Set{Symbol}()),
Set{Symbol}(mod.$𝓂.parameters_in_equations)
)
defined_params = union(
Set{Symbol}(calib_parameters),
Set{Symbol}(calib_parameters_no_var),
Set{Symbol}(calib_eq_parameters)
)
missing_params = collect(setdiff(all_required_params, defined_params))
mod.$𝓂.missing_parameters = sort(missing_params)

has_missing_parameters = length(missing_params) > 0

for (k,v) in $bounds
mod.$𝓂.bounds[k] = haskey(mod.$𝓂.bounds, k) ? (max(mod.$𝓂.bounds[k][1], v[1]), min(mod.$𝓂.bounds[k][2], v[2])) : (v[1], v[2])
Expand All @@ -1481,8 +1508,13 @@ macro parameters(𝓂,ex...)
mod.$𝓂.ss_no_var_calib_list = $ss_no_var_calib_list
mod.$𝓂.par_no_var_calib_list = $par_no_var_calib_list

mod.$𝓂.parameters = calib_parameters
mod.$𝓂.parameter_values = calib_values
# Keep calib_parameters in declaration order, append missing_params at end
# This preserves declaration order for estimation and method of moments
all_params = vcat(calib_parameters, missing_params)
all_values = vcat(calib_values, fill(NaN, length(missing_params)))

mod.$𝓂.parameters = all_params
mod.$𝓂.parameter_values = all_values
mod.$𝓂.calibration_equations = calib_equations_list
mod.$𝓂.parameters_as_function_of_parameters = calib_parameters_no_var
mod.$𝓂.calibration_equations_no_var = calib_equations_no_var_list
Expand All @@ -1505,102 +1537,111 @@ macro parameters(𝓂,ex...)

# time_symbolics = @elapsed
# time_rm_red_SS_vars = @elapsed
if !$precompile
start_time = time()
if !has_missing_parameters
if !$precompile
start_time = time()

if !$silent print("Remove redundant variables in non-stochastic steady state problem:\t") end
if !$silent print("Remove redundant variables in non-stochastic steady state problem:\t") end

symbolics = create_symbols_eqs!(mod.$𝓂)
symbolics = create_symbols_eqs!(mod.$𝓂)

remove_redundant_SS_vars!(mod.$𝓂, symbolics, avoid_solve = !$simplify)
remove_redundant_SS_vars!(mod.$𝓂, symbolics, avoid_solve = !$simplify)

if !$silent println(round(time() - start_time, digits = 3), " seconds") end
if !$silent println(round(time() - start_time, digits = 3), " seconds") end


start_time = time()

if !$silent print("Set up non-stochastic steady state problem:\t\t\t\t") end
start_time = time()
if !$silent print("Set up non-stochastic steady state problem:\t\t\t\t") end

solve_steady_state!(mod.$𝓂, $symbolic, symbolics, verbose = $verbose, avoid_solve = !$simplify) # 2nd argument is SS_symbolic
solve_steady_state!(mod.$𝓂, $symbolic, symbolics, verbose = $verbose, avoid_solve = !$simplify) # 2nd argument is SS_symbolic

mod.$𝓂.obc_violation_equations = write_obc_violation_equations(mod.$𝓂)

set_up_obc_violation_function!(mod.$𝓂)
mod.$𝓂.obc_violation_equations = write_obc_violation_equations(mod.$𝓂)
set_up_obc_violation_function!(mod.$𝓂)

if !$silent println(round(time() - start_time, digits = 3), " seconds") end
else
start_time = time()

if !$silent print("Set up non-stochastic steady state problem:\t\t\t\t") end
if !$silent println(round(time() - start_time, digits = 3), " seconds") end
else
start_time = time()
if !$silent print("Set up non-stochastic steady state problem:\t\t\t\t") end

solve_steady_state!(mod.$𝓂, verbose = $verbose)
solve_steady_state!(mod.$𝓂, verbose = $verbose)

if !$silent println(round(time() - start_time, digits = 3), " seconds") end
if !$silent println(round(time() - start_time, digits = 3), " seconds") end
end
end

start_time = time()

# Mark functions as written even if we skipped SS setup due to missing parameters
# This prevents solve! from re-running @parameters with nothing
mod.$𝓂.solution.functions_written = true

opts = merge_calculation_options(verbose = $verbose)
if !has_missing_parameters
start_time = time()

opts = merge_calculation_options(verbose = $verbose)

if !$precompile
if !$silent
print("Find non-stochastic steady state:\t\t\t\t\t")
end
# time_SS_real_solve = @elapsed
SS_and_pars, (solution_error, iters) = mod.$𝓂.SS_solve_func(mod.$𝓂.parameter_values, mod.$𝓂, opts.tol, opts.verbose, true, mod.$𝓂.solver_parameters)
if !$precompile
if !$silent
print("Find non-stochastic steady state:\t\t\t\t\t")
end
# time_SS_real_solve = @elapsed
SS_and_pars, (solution_error, iters) = mod.$𝓂.SS_solve_func(mod.$𝓂.parameter_values, mod.$𝓂, opts.tol, opts.verbose, true, mod.$𝓂.solver_parameters)

select_fastest_SS_solver_parameters!(mod.$𝓂, tol = opts.tol)
select_fastest_SS_solver_parameters!(mod.$𝓂, tol = opts.tol)

found_solution = true
found_solution = true

if solution_error > opts.tol.NSSS_acceptance_tol
# start_time = time()
found_solution = find_SS_solver_parameters!(mod.$𝓂, tol = opts.tol, verbosity = 0, maxtime = 120, maxiter = 10000000)
# println("Find SS solver parameters which solve for the NSSS:\t",round(time() - start_time, digits = 3), " seconds")
if found_solution
SS_and_pars, (solution_error, iters) = mod.$𝓂.SS_solve_func(mod.$𝓂.parameter_values, mod.$𝓂, opts.tol, opts.verbose, true, mod.$𝓂.solver_parameters)
if solution_error > opts.tol.NSSS_acceptance_tol
# start_time = time()
found_solution = find_SS_solver_parameters!(mod.$𝓂, tol = opts.tol, verbosity = 0, maxtime = 120, maxiter = 10000000)
# println("Find SS solver parameters which solve for the NSSS:\t",round(time() - start_time, digits = 3), " seconds")
if found_solution
SS_and_pars, (solution_error, iters) = mod.$𝓂.SS_solve_func(mod.$𝓂.parameter_values, mod.$𝓂, opts.tol, opts.verbose, true, mod.$𝓂.solver_parameters)
end
end

if !$silent
println(round(time() - start_time, digits = 3), " seconds")
end

if !found_solution
@warn "Could not find non-stochastic steady state. Consider setting bounds on variables or calibrated parameters in the `@parameters` section (e.g. `k > 10`)."
end

mod.$𝓂.solution.non_stochastic_steady_state = SS_and_pars
mod.$𝓂.solution.outdated_NSSS = false
end

if !$silent
println(round(time() - start_time, digits = 3), " seconds")
end
start_time = time()

if !found_solution
@warn "Could not find non-stochastic steady state. Consider setting bounds on variables or calibrated parameters in the `@parameters` section (e.g. `k > 10`)."
if !$silent
if $perturbation_order == 1
print("Take symbolic derivatives up to first order:\t\t\t\t")
elseif $perturbation_order == 2
print("Take symbolic derivatives up to second order:\t\t\t\t")
elseif $perturbation_order == 3
print("Take symbolic derivatives up to third order:\t\t\t\t")
end
end

mod.$𝓂.solution.non_stochastic_steady_state = SS_and_pars
mod.$𝓂.solution.outdated_NSSS = false
end

write_auxiliary_indices!(mod.$𝓂)

start_time = time()
# time_dynamic_derivs = @elapsed
write_functions_mapping!(mod.$𝓂, $perturbation_order)

if !$silent
if $perturbation_order == 1
print("Take symbolic derivatives up to first order:\t\t\t\t")
elseif $perturbation_order == 2
print("Take symbolic derivatives up to second order:\t\t\t\t")
elseif $perturbation_order == 3
print("Take symbolic derivatives up to third order:\t\t\t\t")
mod.$𝓂.solution.outdated_algorithms = Set(all_available_algorithms)

if !$silent
println(round(time() - start_time, digits = 3), " seconds")
end
end

write_auxiliary_indices!(mod.$𝓂)

# time_dynamic_derivs = @elapsed
write_functions_mapping!(mod.$𝓂, $perturbation_order)

mod.$𝓂.solution.outdated_algorithms = Set(all_available_algorithms)

if !$silent
println(round(time() - start_time, digits = 3), " seconds")
if has_missing_parameters && $report_missing_parameters
@warn "Model has been set up with incomplete parameter definitions. Missing parameters: $(missing_params). The non-stochastic steady state and perturbation solution cannot be computed until all parameters are defined. Provide missing parameter values via the `parameters` keyword argument in functions like `get_irf`, `get_SS`, `simulate`, etc."
end

if !$silent Base.show(mod.$𝓂) end
if !$silent && $report_missing_parameters Base.show(mod.$𝓂) end
nothing
end
end
2 changes: 2 additions & 0 deletions src/structures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ mutable struct ℳ
parameters_as_function_of_parameters::Vector{Symbol}
parameters::Vector{Symbol}
parameter_values::Vector{Float64}

missing_parameters::Vector{Symbol}

guess::Dict{Symbol, Float64}

Expand Down
Loading
Loading