Calibration
The calibration code lives in src/SubModules/SteadyState/calibration_steadystate.jl and is part of the SteadyState module.
This page documents the calibration workflow and how to set it up in practice. Calibration here means choosing parameters so that model-implied moments (e.g. capital–output ratio) are close to target moments from data or other sources.
The workflow is built around a single high-level function:
m_par = BASEforHANK.SteadyState.run_calibration(
moments_function,
cal_dict,
m_par;
solver = "NelderMead",
)All boilerplate (paths, loading modules, etc.) is handled in the example mainboard main_calibration.jl. You only need to:
- Specify which parameters to calibrate and which moments to target (
cal_dict), - Write a moments function that computes the model moments for a given
m_par.
1 — What Calibration Does
The calibration routine solves:
\[\min_{\theta} \sum_{j} w_j \left( m^{\text{model}}_j(\theta) - m^{\text{target}}_j \right)^2\]
where
\[\theta\]
: a subset of parameters inModelParameters,\[m^{\text{target}}_j\]
: user-specified target moments (e.g.K/Y = 11.22/4),\[m^{\text{model}}_j(\theta)\]
: corresponding model moments, computed from the model’s steady state,\[w_j\]
: implicit weights from the optimizer.
The output is an updated m_par where the chosen parameters are replaced by the parameter values that best match your targets.
2 — Required Ingredients
2.1 run_calibration Signature
m_par = BASEforHANK.SteadyState.run_calibration(
moments_function,
cal_dict,
m_par;
solver = "NelderMead",
)moments_function::FunctionUser-written function. Given a parameter objectm_par, it must:- Compute the steady state and required aggregates,
- Return a
Dict{String, Float64}with model moments, keyed by moment names.
cal_dict::DictCollects all calibration choices (what to calibrate, what to match, and solver options).m_par::ModelParametersAn instance of::ModelParameters. The optimizer will use this struct to define the initial parameter vector for the initialization of the optimization (depending on the optimizer) and then after running the optimization, the function will output a new set of parameters, all contained inm_par.solver::String(optional) Optimization algorithm. Currently:"NelderMead": local optimizer (default)."BBO": global optimizer usingBlackBoxOptim.
Details on the solvers can be found within their respective packages. The default solver is "NelderMead".
See BASEforHANK.SteadyState.run_calibration for the full signature and parameters. The tutorial below provides practical guidance that complements the API docs.
BASEforHANK.SteadyState.run_calibration — Function
run_calibration(moments_function, cal_dict, m_par; solver = "NelderMead")Calibrate model parameters by minimizing the distance between model-generated moments and empirical targets.
Arguments
moments_function: Function that takesm_parand returns aDictof model moments (keys matchingcal_dict["target_moments"]).cal_dict:Dictwith calibration settings. Required keys:"params_to_calibrate":Vector{Symbol}of parameter field names to tune."target_moments":Dictof empirical targets keyed by the moment names."opt_options"(optional): optimizer-specific options.
m_par: model parameter struct (fields referenced byparams_to_calibrate).solver: optimizer selector:"NelderMead"(default) or"BBO".
Returns
- Updated
m_parwith calibrated parameter values (same struct type as input).
Notes
- If
moments_functionfails for a candidate parameter vector, the optimizer is given a large penalty so it moves away from invalid regions.
2.2 The Calibration Dictionary cal_dict
At minimum, cal_dict must specify:
cal_dict = Dict()
cal_dict["target_moments"] = Dict()
# 1. Parameters to calibrate (symbols of fields in ModelParameters), for example:
cal_dict["params_to_calibrate"] = [:β, :λ]
# 2. Target moments (keyed by a descriptive string), for example:
cal_dict["target_moments"]["K/Y"] = 11.22 / 4 # Capital–output ratio
cal_dict["target_moments"]["B/K"] = 0.25 # Liquid–to–illiquid ratio
# 3. Optional: optimization options (here for Nelder–Mead)
cal_dict["opt_options"] = Optim.Options(;
time_limit = 1200,
f_reltol = 1e-3,
store_trace = true,
show_trace = true,
show_every = 30,
)Keys:
"params_to_calibrate" :: Vector{Symbol}Parameters fromModelParametersthat the optimizer may change."target_moments" :: Dict{String, Float64}Mapping of moment names → target values. Each key must match the keys returned by yourmoments_function."opt_options"(optional)- For
"NelderMead":Optim.Options. - For
"BBO": a named tuple with the optimizer settings (see Example 3 below).
- For
2.3 The Moments Function
The core of the calibration is the function:
function my_moments_function(m_par)::Dict
# 1. Compute steady state
ss_full = quiet_call(call_find_steadystate, m_par)
# 2. Compute aggregates required for your moments
n_par = ss_full.n_par
args_hh_prob = BASEforHANK.IncomesETC.compute_args_hh_prob_ss(ss_full.KSS, m_par, n_par)
BASEforHANK.Parsing.@read_args_hh_prob() # defines aggregates like N, etc.
# Example aggregates
K = ss_full.KSS
Y = BASEforHANK.IncomesETC.output(m_par.Z, K, N, m_par)
# 3. Construct model moments dictionary
model_moments = Dict(
"K/Y" => K / Y / 4.0, # Quarterly capital-output ratio
)
return model_moments
endRequirements:
- The function must accept only
m_paras input. - It must return a
Dict{String, Float64}. - The keys in this
Dictmust match those incal_dict["target_moments"]. - You are free to compute any aggregates you like, as long as they are implied by the model. We provide three examples below so you have a sense of how to precisely do this. Although you will have to find the equations to generate the aggregates you wish to match, it buys you the flexibility of being able to stipulate any moment (as long as there is a model-counterpart).
What does it do?
(1) Computes some aggregates:
ss_full = quiet_call(call_find_steadystate, m_par)is run. This will always need to be called, since this returns certain steady-state aggregates necessary in its own right and for the computation of other aggregates. On top,BASEforHANK.IncomesETC.compute_args_hh_prob_sswill always need to be called, since this returns additional aggregates possibly relevant for the user's calibration. From this, the user would be able to compute plenty moments of interest.(2) Computes the model-moments: The model-moments of interest to the user will need to be defined by in a dictionary
model_moments::Dict(). Here in this example, you the user wanted the capital-to-output ratio and so, the user needs to find its counterpart in the code. In this case, capital in the package code isKand output isY. The capital-to-output ratio is then computed asK/Y. The actual key in the dictionary, denoted as aString,"K/Y"here, can be anything and will only appear in the end, when we print the final model-moments generated from the parameters returned by the optimizer. For example, you can define it as:model_moments = Dict("Capital-to-Output" => K / Y / 4.0). BUT: the keys for the moments withinmodel_momentshas to match the keys ofcal_dicte.g.,cal_dict["target_moments"]["Capital-to-Output"] = K / Y / 4.0).(3) Output: The function in the end returns the model-moments, which the optimizer then compares to the target during the optimization procedure and ultimately, in a
PrettyTable, once the optimizer has ran through to the specified criteria.
3 — Basic Examples
3.1 One-Target Calibration
Goal: calibrate β to match a target capital–output ratio.
# 1. Calibration dictionary
cal_dict_one_target = Dict()
cal_dict_one_target["target_moments"] = Dict()
cal_dict_one_target["params_to_calibrate"] = [:β]
cal_dict_one_target["target_moments"]["K/Y"] = 11.22 / 4 # target K/Y (quarterly)
cal_dict_one_target["opt_options"] = Optim.Options(;
time_limit = 1200,
)# 2. Moments function
function calculate_one_moment(m_par)
# Steady state
ss_full = quiet_call(call_find_steadystate, m_par)
n_par = ss_full.n_par
# Aggregates
args_hh_prob = BASEforHANK.IncomesETC.compute_args_hh_prob_ss(ss_full.KSS, m_par, n_par)
BASEforHANK.Parsing.@read_args_hh_prob()
K = ss_full.KSS
Y = BASEforHANK.IncomesETC.output(m_par.Z, K, N, m_par)
model_moments = Dict("K/Y" => K / Y / 4.0)
return model_moments
end# 3. Run calibration
m_par = BASEforHANK.SteadyState.run_calibration(
calculate_one_moment,
cal_dict_one_target,
m_par;
solver = "NelderMead",
)3.2 Two-Target Calibration
Goal: calibrate β and λ to match K/Y and B/K.
# 1. Calibration dictionary
cal_dict_two_targets = Dict()
cal_dict_two_targets["target_moments"] = Dict()
cal_dict_two_targets["params_to_calibrate"] = [:β, :λ]
cal_dict_two_targets["target_moments"]["K/Y"] = 11.22 / 4
cal_dict_two_targets["target_moments"]["B/K"] = 0.25
cal_dict_two_targets["opt_options"] = Optim.Options(
time_limit = 1200,
f_reltol = 1e-3,
)# 2. Moments function with two moments
function calculate_two_moments(m_par)
# Steady state
ss_full = quiet_call(call_find_steadystate, m_par)
n_par = ss_full.n_par
args_hh_prob = BASEforHANK.IncomesETC.compute_args_hh_prob_ss(ss_full.KSS, m_par, n_par)
BASEforHANK.Parsing.@read_args_hh_prob()
K = ss_full.KSS
Y = BASEforHANK.IncomesETC.output(m_par.Z, K, N, m_par)
# Total bonds: integrate over steady-state distribution
B = sum(ss_full.distrSS .* ss_full.n_par.mesh_b)
model_moments = Dict(
"K/Y" => K / Y / 4.0,
"B/K" => B / K,
)
return model_moments
end# 3. Run calibration
m_par = BASEforHANK.SteadyState.run_calibration(
calculate_two_moments,
cal_dict_two_targets,
m_par;
solver = "NelderMead",
)4 — Higher-Dimensional Example and Global Optimization
When calibrating several parameters at once, one can still use NelderMead, but also the global optimizer such as BlackBoxOptim (solver "BBO"). Below is a compact example with five targets and five parameters.
4.1 Calibration Dictionary for "BBO"
cal_dict_BBO = Dict(
"params_to_calibrate" => [:β, :λ, :Tlev, :ζ, :Rbar],
"target_moments" => Dict(
"K/Y" => 11.22 / 4,
"B/K" => 0.25,
"G/Y" => 0.20,
"T10W" => 0.67,
"Frac Borrowers" => 0.16,
),
"opt_options" => (
SearchRange = [
(0.90, 0.999), # β
(0.01, 0.20), # λ
(1.00, 1.50), # Tlev
(0.00, 0.0005), # ζ
(0.00, 0.05), # Rbar
],
Method = :adaptive_de_rand_1_bin_radiuslimited,
MaxTime = 10800, # seconds
TraceInterval = 30,
TraceMode = :compact,
TargetFitness = 1e-3,
),
)Key points for "BBO":
"opt_options"must be a named tuple.SearchRangeis required: it gives lower and upper bounds for each parameter in"params_to_calibrate".- The broader the bounds, the longer the search may take.
- More can be read about
BlackBoxOptimhere: https://github.com/robertfeldt/BlackBoxOptim.jl
4.2 Moments Function (Sketch)
The full example computes:
K/Y(capital-output),B/K(liquid–to–illiquid ratio),G/Y(government spending-output),T10W(top 10% wealth share),Frac Borrowers(fraction of borrowers).
The structure is the same: compute the steady state, compute the necessary aggregates, then build and return a Dict. Conceptually:
function calculate_moments(m_par)
ss_full = quiet_call(call_find_steadystate, m_par)
n_par = ss_full.n_par
args_hh_prob = BASEforHANK.IncomesETC.compute_args_hh_prob_ss(ss_full.KSS, m_par, n_par)
BASEforHANK.Parsing.@read_args_hh_prob()
# Example aggregates (only schematic)
K = ss_full.KSS
B = sum(ss_full.distrSS .* ss_full.n_par.mesh_b)
Y = BASEforHANK.IncomesETC.output(m_par.Z, K, N, m_par)
# ... compute G, T, wealth distribution, TOP10Wshare, fr_borr, etc. ...
model_moments = Dict(
"K/Y" => K / Y / 4.0,
"B/K" => B / K,
"G/Y" => G / Y,
"T10W" => TOP10Wshare,
"Frac Borrowers" => fr_borr,
)
return model_moments
endThen run:
m_par = BASEforHANK.SteadyState.run_calibration(
calculate_moments,
cal_dict_BBO,
m_par;
solver = "BBO",
)5 — Practical Tips and Checklist
Tips:
- Start with few parameters and moments to test your setup.
- Use a short time limit initially (e.g. 20 minutes) and inspect how close the solution gets.
- Narrow the
SearchRangewhen using"BBO"to reduce runtime. - Check the optimizer trace (objective values) for convergence issues or weird behavior.
Checklist:
[ ] Decide which model parameters to calibrate (
"params_to_calibrate").[ ] Decide which data (or target) moments to match and their values (
"target_moments").[ ] Write a
moments_function(m_par)that:- [ ] Computes the steady state,
- [ ] Computes aggregates needed for the target moments,
- [ ] Returns a
Dictwith keys matching"target_moments".
[ ] Set reasonable optimization options in
"opt_options"(and bounds for"BBO").[ ] Run
run_calibrationwith your chosen solver.[ ] Inspect the results: updated
m_parand the printed model vs. target moments.
Once this is in place, calibration becomes a drop-in step in your workflow: you can quickly test alternative targets, different sets of calibrated parameters, or change solvers by modifying just cal_dict and the moments function.