Implementation Details
This section describes important functions and implementation features in greater detail. Additional documentation can also be found in function documentation or in-line.
This section focuses on what the code does and why. Docstrings and the code itself (including comments) provide detailed information regarding how these basic procedures are implemented.
The AbstractModel
Type
The AbstractModel
type provides a common interface for all model objects, which greatly facilitates the implementation of new model specifications. Any concrete subtype of AbstractModel
can be passed to any function defined for AbstractModel
, provided that the concrete type has the fields that the function expects to be available.
If a user wants to define a new subclass of models, say regression models, then the user could create a new AbstractRegressionModel
type as a subtype of AbstractModel
. Functions defined for AbstractRegressionModel
would only apply to concrete subtypes of AbstractRegressionModel
, but functions defined for AbstractModel
will still work on these concrete subtypes.
The AbstractParameter
Type
The AbstractParameter
type implements our notion of a model parameter: a time-invariant, unobserved value that has significance in the model, e.g. for likelihood computation and estimation.
Though all parameters are time-invariant, they can have different features. Some parameters are scaled for use when solving the model[1] and constructing the model's measurement equations[2].
During optimization, parameters may be transformed from model space to the real line via one of three different transformations: Untransformed
, SquareRoot
, and Exponential
. These transformations are also defined as types, and require additional information for each parameter. Typically, we have two "hyperparameters" for these transformations, a
, and b
.
Untransformed
:a
andb
do nothingSquareRoot
:a
andb
specify the bounds the parameter takes, i.e. $x\in (a, b)$Exponential
:a
andb
are the parameters in the transformation $a + exp(x - b)$
In some models, steady state values might be relevant parameters. They are typically functions of other parameters, so they do not need to be estimated directly.
While parameters are "time-invariant", we do allow regime switching. As an example, suppose that we have a linear regression with data from time periods $t = 1,\dots, T$, where $T > 4$, and in $t = 3$, the intercept of the regression is assumed to change values because of a structural break in the time series. We can model the intercept as a parameter with regime-switching. The parameter has one value in periods $t = 1, 2$ and a different value in periods $t = 3,\dots, T$. Currently, only regime-switching in the values of the parameter has been tested, but we have implemented regime switching in all the features. For example, you may want a different prior in each regime. See Regime-Switching Interface for documentation on the interface for regime-switching parameters.
The various requirements on parameters are nicely addressed using a parameterized type hierarchy.
AbstractParameter{T<:Number}
: The common abstract supertype for all parameters.Parameter{T<:Number, U<:Transform}
: The abstract supertype for parameters that are directly estimated.UnscaledParameter{T<:Number, U:<Transform}
: Concrete type for parameters that do not need to be scaled for equilibrium conditions.ScaledParameter{T<:Number, U:<Transform}
: Concrete type for parameters that are scaled for equilibrium conditions.
SteadyStateParameter{T<:Number}
: Concrete type for steady-state parameters.
All Parameter
s have the fields defined in UnscaledParameter
:
Missing docstring for UnscaledParameter
. Check Documenter's build log for details.
ScaledParameters
also have the following fields:
scaledvalue::T
: Parameter value scaled for use ineqcond.jl
scaling::Function
: Function used to scale parameter value for use in equilibrium conditions.
Note: Though not strictly necessary, defining a scaling with the parameter object allows for much a much cleaner definition of the equilibrium conditions.
Because the values of SteadyStateParameter
s are directly computed as a function of ScaledParameter
s and UnscaledParameter
s, they only require 4 fields:
ModelConstructors.SteadyStateParameter
— TypeSteadyStateParameter{T} <: AbstractParameter{T}
Steady-state model parameter whose value depends upon the value of other (non-steady-state) Parameter
s. SteadyStateParameter
s must be constructed and added to an instance of a model object m
after all other model Parameter
s have been defined. Once added to m
, SteadyStateParameter
s are stored in m.steady_state
. Their values are calculated and set by steadystate!(m)
, rather than being estimated directly. SteadyStateParameter
s do not require transformations from the model space to the real line or scalings for use in equilibrium conditions.
Fields
key::Symbol
: Parameter name. Should conform to the guidelines established in the DSGE Style Guide.value::T
: The parameter's steady-state value.description::String
: Short description of the parameter's economic significance.tex_label::String
: String for printing parameter name to LaTeX.
The Observable
and PseudoObservable
Types
We similarly encapsulate information about observables and pseudo-observables (unobserved linear combinations of states, e.g. the output gap) into the Observable
and PseudoObservable
types. Each type has identifier fields key
, name
, and longname
.
Most importantly, both Observable
s and PseudoObservable
s include the information needed for transformations to and from model units. For Observable
s, these are the input_series
, fwd_transform
, and rev_transform
fields. "Forward transformations" are applied to transform the raw input data series specified in input_series
to model units. The model is estimated and forecasted in model units, and then we apply "reverse transformations" to get human-readable units before computing means and bands or plotting. Pseudo-observables are not observed, so they do not have input_series
or fwd_transform
s, but they may however have rev_transform
s.
As an example, the :obs_gdp
Observable
uses as input_series
aggregate nominal GDP in levels, the GDP price index, and population in levels, all from FRED.[3] These series are fwd_transform
ed to get quarter-over-quarter log growth rates of per-capita real GDP, which are the Observable
's model units. The reverse transformation then converts :obs_gdp
into annualized quarter-over-quarter percent changes of aggregate real GDP.
ModelConstructors.Observable
— Typemutable struct Observable
Fields
key::Symbol
input_series::Vector{Symbol}
: vector of mnemonics, each in the form:MNEMONIC__SOURCE
(e.g.:GDP__FRED
). This vector is parsed to determine source (e.g. per-capita consumption gets population and consumption).fwd_transform::Function
: Extracts appropriateinput_series
from a DataFrame of levels, and transforms data to model units (for example, computes per-capita growth rates from levels).rev_transform::Function
: Transforms a series from model units into observable units. May take kwargs.name::String
: e.g. "Real GDP growth"longname::String
: e.g. "Real GDP growth per capita"
ModelConstructors.PseudoObservable
— Typemutable struct PseudoObservable
Fields
key::Symbol
name::String
: e.g. "Flexible Output Growth"longname::String
: e.g. "Output that would prevail in a flexible-price economy"rev_transform::Function
: Transforms a series from model units into observable units. May take kwargs.
Model Settings
The Setting
type implements computational settings that affect how the code runs without affecting the mathematical definition of the model. Depending on the model, these may include flags (e.g. whether or not to recompute the Hessian), parameterization for the Metropolis-Hastings algorithm (e.g. number of times to draw from the posterior distribution), and the vintage of data being used (Setting
is a parametric type - a Setting{T<:Any}
, so Booleans, Numbers, and Strings can all be turned into Setting
s). If settings exist for a model type, then they should be stored centrally in the settings
dictionary within the model object.
Why implement a Setting
type when we could put their values directly into the source code or dictionary? The most obvious answer is that the parametric type allows us to implement a single interface for all Setting
s (Booleans, Strings, etc.), so that when we access a particular setting during the estimation and forecast steps, we don't have to think about the setting's type.
Setting
s play an important role in addition to providing useful abstraction. Estimating and forecasting the New York Fed DSGE model takes many hours of computation time and creates a lot of output files. It is useful to be able to compare model output from two different models whose settings differ slightly (for example, consider two identical models that use different vintages of data as input). A central feature of the Setting
type is a mechanism that generates unique, meaningful filenames when code is executed with different settings. Specifically, when a setting takes on a non-default value, a user-defined setting code (along with the setting's value) are appended to all output files generated during execution.
The Setting{T<:Any}
type is defined as follows:
ModelConstructors.Setting
— TypeSetting{T}
The Setting
type is an interface for computational settings that affect how the code runs without affecting the mathematical definition of the model. It also provides support for non-conflicting file names for output of 2 models that differ only in the values of their computational settings.
Fields
key::Symbol
: Name of settingvalue::T
: Value of settingprint::Bool
: Indicates whether to append this setting's code and value to output file names. If true, output file names will include a suffix of the form_code1=val1_code2=val2
etc. where codes are listed in alphabetical order.code::String
: string to print to output file suffixes whenprint=true
.description::String
: Short description of what the setting is used for.
We provide two functions default_settings!
and default_test_settings!
to initialize settings that most models can have. The settings are
- save root
- input data root
- vintage of data to be used
- dataset id
To update the value of an existing function, the user has two options. First, the user may use the <=
syntax. However, for this to work properly, it is essential that the setting's key
field be exactly the same as that of an existing entry in m.settings
. Otherwise, an additional entry will be added to m.settings
and the old setting will be the one accessed from other all routines. A potentially safer, though clunkier, option is to use the update!
method.
Type Interfaces
Parameter
Interface
Base.rand
— MethodDistributions.rand(p::Vector{AbstractParameter{Float64}}, n::Int;
regime_switching::Bool = false, toggle::Bool = true)
Generate n
draws from the priors of each parameter in p
.This returns a matrix of size (length(p),n)
, where each column is a sample. To sample from p
when it has regime-switching, set regime_switching = true
. The toggle
keyword is only relevant for regime-switching sampling. Please see ?ModelConstructors.rand_regime_switching
.
Base.rand
— MethodDistributions.rand(p::Vector{AbstractParameter{Float64}}; regime_switching::Bool = false,
toggle::Bool = true)
Generate a draw from the prior of each parameter in p
.
ModelConstructors.differentiate_transform_to_model_space
— Methoddifferentiate_transform_to_model_space{S<:Real,T<:Number, U<:Transform}(p::Parameter{S,T,U}, x::S)
Differentiates the transform of x
from the real line to lie between p.valuebounds
The transformations are defined as follows, where (a,b) = p.transform_parameterization and c a scalar (default=1):
- Untransformed:
x
- SquareRoot:
(a+b)/2 + (b-a)/2 * c * x/sqrt(1 + c^2 * x^2)
- Exponential:
a + exp(c*(x-b))
Their gradients are therefore
- Untransformed:
1
- SquareRoot:
(b-a)/2 * c / (1 + c^2 * x^2)^(3/2)
- Exponential:
c * exp(c*(x-b))
ModelConstructors.differentiate_transform_to_real_line
— Methoddifferentiate_transform_to_real_line{S<:Real,T<:Number, U<:Transform}(p::Parameter{S,T,U}, x::S)
Differentiates the transform of x
from the model space lying between p.valuebounds
to the real line. The transformations are defined as follows, where (a,b) = p.transform_parameterization and c a scalar (default=1):
- Untransformed: x
- SquareRoot: (1/c)*cx/sqrt(1 - cx^2), where cx = 2 * (x - (a+b)/2)/(b-a)
- Exponential: b + (1 / c) * log(x-a)
Their gradients are therefore
- Untransformed:
1
- SquareRoot:
(1/c) * (1 / ( 1 - cx^2)^(-3/2)) * (2/(b-a))
- Exponential:
1 / (c * (x - a))
ModelConstructors.moments
— Methodmoments(θ::Parameter)
If θ's prior is a RootInverseGamma
, τ and ν. Otherwise, returns the mean and standard deviation of the prior. If θ is fixed, returns (θ.value, 0.0)
.
ModelConstructors.n_parameters_regime_switching
— Methodfunction n_parameters_regime_switching(p::ParameterVector)
calculates the total number of parameters in p
across all regimes.
ModelConstructors.parameter
— Methodparameter(p::ScaledParameter{S,T,U}, newvalue::S) where {S<:Real, T<:Number,U<:Transform}
Returns a ScaledParameter with value field equal to newvalue
and scaledvalue field equal to p.scaling(newvalue)
. If p
is a fixed parameter, it is returned unchanged.
ModelConstructors.parameter
— Methodparameter(p::UnscaledParameter{S,T,U}, newvalue::S) where {S<:Real,T<:Number,U<:Transform}
Returns an UnscaledParameter with value field equal to newvalue
. If p
is a fixed parameter, it is returned unchanged.
ModelConstructors.parameter
— Methodparameter{S,T,U<:Transform}(key::Symbol, value::S, valuebounds = (value,value),
transform_parameterization = (value,value),
transform = Untransformed(), prior = NullablePrior();
fixed = true, scaling::Function = identity, description = "",
tex_label::String = "")
By default, returns a fixed UnscaledParameter
object with key key
and value value
. If scaling
is given, a ScaledParameter
object is returned.
ModelConstructors.transform_to_model_space
— Methodtransform_to_model_space{S<:Real,T<:Number, U<:Transform}(p::Parameter{S,T,U}, x::S)
Transforms x
from the real line to lie between p.valuebounds
without updating p.value
. The transformations are defined as follows, where (a,b) = p.transform_parameterization and c a scalar (default=1):
- Untransformed:
x
- SquareRoot:
(a+b)/2 + (b-a)/2 * c * x/sqrt(1 + c^2 * x^2)
- Exponential:
a + exp(c*(x-b))
ModelConstructors.transform_to_real_line
— Methodtransform_to_real_line(p::Parameter{S,T,U}, x::S = p.value) where {S<:Real, T<:Number, U<:Transform}
Transforms p.value
from model space (between p.valuebounds
) to the real line, without updating p.value
. The transformations are defined as follows, where (a,b) = p.transform_parameterization, c a scalar (default=1), and x = p.value:
- Untransformed: x
- SquareRoot: (1/c)*cx/sqrt(1 - cx^2), where cx = 2 * (x - (a+b)/2)/(b-a)
- Exponential: b + (1 / c) * log(x-a)
ModelConstructors.update!
— Methodupdate!(pvec::ParameterVector, values::Vector{S},
indices::BitArray{1}; change_value_type::Bool = false) where S
Updates a subset of parameters in pvec
specified by indices. Assumes values
is sorted in the same order as the parameters in pvec
, ignoring parameters that are to be left unchanged.
However, update!
will not overwrite fixed parameters, even if indices
has a true in an index corresponding to a fixed parameter.
Examples
julia> pvec = ParameterVector{Float64}(undef, 3);
julia> pvec[1] = parameter(:a, 1., (0., 3.), (0., 3.), fixed = false);
julia> pvec[2] = parameter(:b, 1.);
julia> pvec[3] = parameter(:c, 1., (0., 3.), (0., 3.), fixed = false);
julia> values = [2., 2.];
julia> update!(pvec, values, [true, false, true]);
julia> map(x -> x.value, pvec)
3-element Array{Float64,1}:
2.0
1.0
2.0
julia> pvec = ParameterVector{Float64}(undef, 3);
julia> pvec[1] = parameter(:a, 1.);
julia> pvec[2] = parameter(:b, 1.);
julia> pvec[3] = parameter(:c, 1.);
julia> values = [2., 2.];
julia> update!(pvec, values, [true, false, true]);
julia> map(x -> x.value, pvec)
3-element Array{Float64,1}:
1.0
1.0
1.0
ModelConstructors.update!
— Methodupdate!(pvec::ParameterVector, values::Vector{S}; change_value_type::Bool = false) where S
Update all parameters in pvec
that are not fixed with values
. Length of values
must equal length of pvec
. Function optimized for speed.
ModelConstructors.update
— Methodupdate(pvec::ParameterVector, values::Vector{S}) where S
Returns a copy of pvec
where non-fixed parameter values are updated to values
. pvec
remains unchanged. Length of values
must equal length of pvec
.
We define the non-mutating version like this because we need the type stability of map!
ModelConstructors.parameters2namedtuple
— Methodparameters2namedtuple(m)
returns the parameters of m
as a NamedTuple
. The input m
can be either an AbstractVector{<: AbstractParameter}
or an AbstractModel
.
ModelConstructors.rand_regime_switching
— Methodrand_regime_switching(p::Vector{AbstractParameter{Float64}}; toggle::Bool = true)
Generate a draw from the prior of each parameter in p
.
Regime-Switching Interface
To implement regime-switching, we add a field to Parameter
types called regimes::Dict{Symbol, OrderedDict{Int, Any}}
. The keys of the top level dictionary are the names of the other fields in a Parameter
type, e.g. :value
. Each key then points to an OrderedDict
, whose keys are the numbers of different regimes and values are the corresponding values for each regime.
The field regimes
functions as a "storage" of information. When a Parameter
type interacts with another object in Julia, e.g. p + 1.
, where p
is a Parameter
, what actually happens is p.value + 1.
. Only the current fields of p
will be used when interacting with other objects. To use a different value (or different fields) from another regime, the user needs to tell the parameter to switch regimes the toggle_regime!
function (see below).
By default, the regimes
field is empty (see the documentation of the parameter
function in Parameter
Interface). To add values, either pass in the dictionary as a keyword to parameter
or use set_regime_val!
. Note that the latter function is not exported.
Note that regimes must be sorted in order because we store the regimes as an OrderedDict
, and OrderedDict
objects are sorted by insertion order.
ModelConstructors.regime_fixed
— Methodregime_fixed(p::Parameter{S}, i::Int) where S <: Real
regime_fixed(p::Parameter{S}, model_regime::Int, d::AbstractDict{Int, Int}) where S <: Real
returns whether p
is fixed in regime i
for the first method and whether true p
is fixed in regime d[model_regime]
for the second method.
ModelConstructors.regime_prior
— Methodregime_prior(p::Parameter{S}, i::Int) where S <: Real
regime_prior(p::Parameter{S}, model_regime::Int, d::AbstractDict{Int, Int}) where S <: Real
returns the prior of p
in regime i
for the first method and the prior of p
in regime d[model_regime]
for the second.
ModelConstructors.regime_val
— Methodregime_val(p::Parameter{S}, i::Int) where S <: Real
regime_val(p::Parameter{S}, model_regime::Int, d::AbstractDict{Int, Int}) where S <: Real
returns the value of p
in regime i
for the first method and the value of p
in regime d[model_regime
for the second.
ModelConstructors.regime_valuebounds
— Methodregime_valuebounds(p::Parameter{S}, i::Int) where S <: Real
regime_valuebounds(p::Parameter{S}, model_regime::Int, d::AbstractDict{Int, Int}) where S <: Real
returns the valuebounds
of p
in regime i
for the first method and the valuebounds
of p
in regime d[model_regime]
for the second method.
ModelConstructors.set_regime_fixed!
— Methodset_regime_fixed!(p::Parameter{S}, i::Int, v::S; update_valuebounds::Interval = (NaN, NaN))
sets whether p
is fixed in regime i
of p
. Set update_valuebounds to true to set the valuebounds to match the fixed value.
The second method allows the user to pass a dictionary to permit the case where there may be differences between the regimes of a regime-switching model and the regimes for the parameters. For example, aside from regime-switching in parameters, the model may also include other forms of regime-switching. To allow estimation of regime-switching parameters in such a model, the dictionary d
maps each "model" regime to a "parameter" regime. In this way, the second method specifies which "parameter" regime should be used at a given "model" regime.
ModelConstructors.set_regime_prior!
— Methodset_regime_prior!(p::Parameter{S}, i::Int, v)
set_regime_prior!(p::Parameter{S}, model_regime::Int, v, d::AbstractDict{Int, Int})
sets the prior in regime i
of p
to be v
. The type of v
can be a NullablePriorUnivariate
, NullablePriorMultivariate
, ContinuousUnivariateDistribution
, or `ContinuousMultivariateDistribution'.
The second method allows the user to pass a dictionary to permit the case where there may be differences between the regimes of a regime-switching model and the regimes for the parameters. For example, aside from regime-switching in parameters, the model may also include other forms of regime-switching. To allow estimation of regime-switching parameters in such a model, the dictionary d
maps each "model" regime to a "parameter" regime. In this way, the second method specifies which "parameter" regime should be used at a given "model" regime.
ModelConstructors.set_regime_val!
— Method set_regime_val!(p::Parameter{S},
i::Int, v::S; override_bounds::Bool = false) where S <: Real
set_regime_val!(p::Parameter{S},
model_regime::Int, v::S, d::AbstractDict{Int, Int}; override_bounds::Bool = false) where S <: Real
sets the value in regime i
of p
to be v
. By default, we enforce the bounds that are currently in p
, but the bounds can be ignoerd by setting override_bounds = true
.
The second method allows the user to pass a dictionary to permit the case where there may be differences between the regimes of a regime-switching model and the regimes for the parameters. For example, aside from regime-switching in parameters, the model may also include other forms of regime-switching. To allow estimation of regime-switching parameters in such a model, the dictionary d
maps each "model" regime to a "parameter" regime. In this way, the second method specifies which "parameter" regime should be used at a given "model" regime.
ModelConstructors.set_regime_valuebounds!
— Methodset_regime_valuebounds!(p::Parameter{S}, i::Int, v::S)
sets valuebounds for p
in regime i
to v
.
The second method allows the user to pass a dictionary to permit the case where there may be differences between the regimes of a regime-switching model and the regimes for the parameters. For example, aside from regime-switching in parameters, the model may also include other forms of regime-switching. To allow estimation of regime-switching parameters in such a model, the dictionary d
maps each "model" regime to a "parameter" regime. In this way, the second method specifies which "parameter" regime should be used at a given "model" regime.
ModelConstructors.toggle_regime!
— Methodtoggle_regime!(p::Parameter{S}, i::Int) where S <: Real
toggle_regime!(pvec::ParameterVector{S}, i::Int) where S <: Real
toggle_regime!(p::Parameter{S}, model_regime::Int, d::AbstractDict{Int, Int}) where S <: Real
toggle_regime!(pvec::ParameterVector{S}, model_regime::Int, d::AbstractDict{Symbol, <: AbstractDict{Int, Int}}) where S <: Real
toggle_regime!(pvec::ParameterVector{S}, model_regime::Int, d::AbstractDict{Int, Int}) where S <: Real
changes the fields of p
to regime i
.
For example, if
p.regimes[:value] = OrderedDict{Int, Any}(1 => 1, 2 => 3)
then toggle_regime!(p, 1)
will cause p.value = 1
and toggle_regime!(p, 2)
will cause p.value = 3
.
The third method allows the user to pass a dictionary to permit the case where there may be differences between the regimes of a regime-switching model and the regimes for the parameters. For example, aside from regime-switching in parameters, the model may also include other forms of regime-switching. To allow estimation of regime-switching parameters in such a model, the dictionary d
maps each "model" regime to a "parameter" regime. In this way, the second method specifies which "parameter" regime should be used at a given "model" regime.
The fourth method extends the third to a ParameterVector, with the possibility that each parameter may have different mappings to the model regimes. Each key of d
corresponds to the key of a parameter, and each value of d
is the mapping for model regimes to the parameter regimes of p.key
. The fifth method is similar to the fourth but assumes any regime-switching parameter has the same mapping from model regimes to parameter regimes, hence the use of a common dictionary.
ModelConstructors.get_values
— Methodget_values(pvec::ParameterVector{S}; regime_switching::Bool = true) where {S <: Real}
constructs a vector of the underlying values in a ParameterVector
, including if there are regime-switching values.
Setting
Interface
Base.:<=
— Method(<=)(m::AbstractModel, s::Setting)
Syntax for adding a setting to a model/overwriting a setting via m <= Setting(...)
ModelConstructors.get_setting
— Methodget_setting(m::AbstractModel, setting::Symbol)
Returns the value of the setting
ModelConstructors.update!
— Methodupdate!(a::Setting, b::Setting)
Update a
with the fields of b
if:
- The
key
field is updated ifa.key == b.key
- The
print
boolean andcode
string are overwritten ifa.print
is false andb.print
is true, ora.print
is true,b.print
is false, and b.code is non-empty. - The
description
field is updated ifb.description
is nonempty
rng::MersenneTwister
testing::Bool
observable_mappings::Dict{Symbol, Observable}
pseudo_observable_mappings::Dict{Symbol, PseudoObservable}
- 1By solving the model, we mean a mapping from parameters to some objects of interest. In a state space model, solving the model is a mapping from parameters to a state transition function. By constructing
- 2In the context of a state space model, a measurement equation is mapping from states to observable data.
- 3In DSGE.jl, we implement a
load_data
function that parsesinput_series
to retrieve data from FRED. To take full advantage of theObservable
type, users may want to write their ownload_data
function. For example, it may be convenient to write aload_data
function that parsesinput_series
to select column(s) from saved CSV files and combines them into a single data frame.