Skip to content

Commit 1776c9c

Browse files
committed
Merge branch 'main' into dev
2 parents d218695 + 269e8f1 commit 1776c9c

File tree

7 files changed

+231
-7
lines changed

7 files changed

+231
-7
lines changed

NEWS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# ModelConstructors.jl v0.2.4
2+
- Extend `transform_to_model_space` and `transform_to_real_line` for regime-switching parameters.
3+
4+
# ModelConstructors.jl v0.2.3
5+
- Move methods for computing free and fixed indices of parameters from SMC.jl to ModelConstructors.jl
6+
7+
# ModelConstructors.jl v0.2.2
8+
- Try to convert types to match rather than throwing a `MethodError` immediately
9+
when calling `parameter`.
10+
111
# ModelConstructors.jl v0.2.1
212
- Raise compat bounds for some packages
313

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelConstructors"
22
uuid = "e47e5152-bd14-11e9-1b46-c951f0a7041d"
33
authors = ["William Chen <william.chen@ny.frb.org>", "Shlok Goyal <shlok.goyal@ny.frb.org>", "Alissa Johnson <alissa.johnson@ny.rb.org"]
4-
version = "0.2.1"
4+
version = "0.2.4"
55

66
[deps]
77
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"

docs/src/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@ Developers of this package at the
3030
[New York Fed](https://www.newyorkfed.org/research) include
3131

3232
* [William Chen](https://github.com/chenwilliam77)
33+
* [Shlok Goyal](https://github.com/ShlokG)
34+
* [Alissa Johnson](https://github.com/alissarjohnson)
3335
* [Ethan Matlin](https://github.com/ethanmatlin)
3436
* [Reca Sarfati](https://github.com/rsarfati)

src/abstractmodel.jl

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,66 @@ n_pseudo_observables(m::AbstractModel) = length(m.pseudo_observables)
164164
n_equilibrium_conditions(m::AbstractModel) = length(m.equilibrium_conditions)
165165
n_parameters(m::AbstractModel) = length(m.parameters)
166166
n_parameters_steady_state(m::AbstractModel) = length(m.steady_state)
167-
n_parameters_free(m::AbstractModel) = sum([!α.fixed for α in m.parameters])
167+
n_parameters_free(m::AbstractModel) = length(get_free_para_inds(m.parameters; regime_switching = true))
168168

169169
function n_parameters_regime_switching(m::AbstractModel)
170170
return n_parameters_regime_switching(m.parameters)
171171
end
172172

173+
function get_fixed_para_inds(parameters::ParameterVector; regime_switching::Bool = false,
174+
toggle::Bool = true)
175+
if regime_switching
176+
if toggle
177+
toggle_regime!(parameters, 1)
178+
end
179+
180+
reg_fixed =.fixed for θ in parameters] # it is assumed all regimes are toggled to regime 1
181+
for θ in parameters
182+
if !isempty.regimes) # this parameter has regimes
183+
if haskey.regimes, :fixed)
184+
push!(reg_fixed, [regime_fixed(θ, i) for i in 2:length.regimes[:value])]...)
185+
elseif θ.fixed # since regimes[:fixed] is non-existent but θ.fixed is true,
186+
# it is assumed all regimes are fixed.
187+
push!(reg_fixed, trues(length.regimes[:value]) - 1)...)
188+
else # All regimes are not fixed
189+
push!(reg_fixed, falses(length.regimes[:value]) - 1)...)
190+
end
191+
end
192+
end
193+
194+
return findall(reg_fixed)
195+
else
196+
return findall([θ.fixed for θ in parameters])
197+
end
198+
end
199+
200+
function get_free_para_inds(parameters::ParameterVector; regime_switching::Bool = false,
201+
toggle::Bool = true)
202+
if regime_switching
203+
if toggle
204+
toggle_regime!(parameters, 1)
205+
end
206+
207+
reg_free = [!θ.fixed for θ in parameters] # it is assumed all regimes are toggled to regime 1
208+
for θ in parameters
209+
if !isempty.regimes) # this parameter has regimes
210+
if haskey.regimes, :fixed)
211+
push!(reg_free, [!regime_fixed(θ, i) for i in 2:length.regimes[:value])]...)
212+
elseif θ.fixed # since regimes[:fixed] is non-existent but θ.fixed is true,
213+
# it is assumed all regimes are fixed.
214+
push!(reg_free, falses(length.regimes[:value]) - 1)...)
215+
else # All regimes are not fixed
216+
push!(reg_free, trues(length.regimes[:value]) - 1)...)
217+
end
218+
end
219+
end
220+
221+
return findall(reg_free)
222+
else
223+
return findall([!θ.fixed for θ in parameters])
224+
end
225+
end
226+
173227
"""
174228
```
175229
get_dict(m, class, index)

src/parameters.jl

Lines changed: 132 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ function parameter(key::Symbol,
387387
scaling::Function = identity,
388388
regimes::Dict{Symbol,OrderedDict{Int64,Any}} = Dict{Symbol,OrderedDict{Int64,Any}}(),
389389
description::String = "No description available.",
390-
tex_label::String = "") where {V<:Vector, T <: Float64, U <:Transform} #{V<:Vector, S<:Real, T <: Float64, U <:Transform}
390+
tex_label::String = "") where {V<:Vector, T <: Real, U <:Transform} #{V<:Vector, S<:Real, T <: Float64, U <:Transform}
391391

392392
# If fixed=true, force bounds to match and leave prior as null. We need to define new
393393
# variable names here because of lexical scoping.
@@ -445,6 +445,32 @@ function parameter(key::Symbol,
445445
end
446446
end
447447

448+
function parameter(key::Symbol,
449+
value::Union{T1, V}, #value::Union{S,V},
450+
valuebounds::Interval{T2} = (value,value),
451+
transform_parameterization::Interval{T3} = (value,value),
452+
transform::U = Untransformed(),
453+
prior::Union{NullableOrPriorUnivariate, NullableOrPriorMultivariate} = NullablePriorUnivariate();
454+
fixed::Bool = true,
455+
scaling::Function = identity,
456+
regimes::Dict{Symbol,OrderedDict{Int64,Any}} = Dict{Symbol,OrderedDict{Int64,Any}}(),
457+
description::String = "No description available.",
458+
tex_label::String = "") where {V<:Vector, T1 <: Real, T2 <: Real, T3 <: Real, U <:Transform}
459+
warn_str = "The element types of the fields `value` ($(typeof(value))), `valuebounds` ($(eltype(valuebounds))), " *
460+
"and `transform_parameterization` ($(eltype(transform_parameterization))) do not match. " *
461+
"Attempting to convert all types to the same type as `value`. Note that the element type for the prior " *
462+
"distribution should also be $(typeof(value))."
463+
@warn warn_str
464+
465+
valuebounds_new = (convert(T1, valuebounds[1]), convert(T1, valuebounds[2]))
466+
transform_parameterization_new = (convert(T1, transform_parameterization[1]),
467+
convert(T1, transform_parameterization[2]))
468+
469+
return parameter(key, value, valuebounds_new, transform_parameterization_new,
470+
transform, prior; fixed = fixed, scaling = scaling,
471+
regimes = regimes, description = description, tex_label = tex_label)
472+
end
473+
448474
function parameter_ad(key::Symbol,
449475
value::Union{S,V},
450476
valuebounds::Interval{T} = (value,value),
@@ -750,8 +776,60 @@ function transform_to_model_space(p::Parameter{T,Exponential}, x::T) where T
750776
a + exp(c*(x-b))
751777
end
752778

753-
transform_to_model_space(pvec::ParameterVector{T}, values::Vector{T}) where T = map(transform_to_model_space, pvec, values)
754-
transform_to_model_space(pvec::ParameterVector, values::Vector{S}) where S = map(transform_to_model_space, pvec, values)
779+
@inline function transform_to_model_space(pvec::ParameterVector{T}, values::Vector{T};
780+
regime_switching::Bool = false) where T
781+
if regime_switching
782+
# Transform values in the first regime
783+
output = similar(values)
784+
plen = length(pvec)
785+
map!(transform_to_model_space, output, pvec, values[1:plen])
786+
787+
# Now transform values in the second regime and on
788+
i = 0
789+
for p in pvec
790+
if !isempty(p.regimes)
791+
for (k, v) in p.regimes[:value]
792+
if k != 1 # Skip the first regime.
793+
i += 1 # `values` stores regime values (after the first regime) beside each other.
794+
output[plen + i] = transform_to_model_space(p, values[plen + i])
795+
end
796+
end
797+
end
798+
end
799+
800+
return output
801+
else
802+
return map(transform_to_model_space, pvec, values)
803+
end
804+
end
805+
806+
@inline function transform_to_model_space(pvec::ParameterVector, values::Vector{S};
807+
regime_switching::Bool = false) where S
808+
809+
if regime_switching
810+
# Transform values in the first regime
811+
output = similar(values)
812+
plen = length(pvec)
813+
map!(transform_to_model_space, output, pvec, values[1:plen])
814+
815+
# Now transform values in the second regime and on
816+
i = 0
817+
for p in pvec
818+
if !isempty(p.regimes)
819+
for (k, v) in p.regimes[:value]
820+
if k != 1 # Skip the first regime.
821+
i += 1 # `values` stores regime values (after the first regime) beside each other.
822+
output[plen + i] = transform_to_model_space(p, values[plen + i])
823+
end
824+
end
825+
end
826+
end
827+
828+
return output
829+
else
830+
return map(transform_to_model_space, pvec, values)
831+
end
832+
end
755833

756834
"""
757835
```
@@ -837,8 +915,57 @@ function transform_to_real_line(p::Parameter{T,Exponential}, x::T = p.value) whe
837915
b + (1 ./ c) * log(x-a)
838916
end
839917

840-
transform_to_real_line(pvec::ParameterVector{T}, values::Vector{T}) where T = map(transform_to_real_line, pvec, values)
841-
transform_to_real_line(pvec::ParameterVector{T}) where T = map(transform_to_real_line, pvec)
918+
@inline function transform_to_real_line(pvec::ParameterVector{T}, values::Vector{T}; regime_switching::Bool = false) where T
919+
if regime_switching
920+
# Transform values in the first regime
921+
output = similar(values)
922+
plen = length(pvec)
923+
map!(transform_to_real_line, output, pvec, values[1:plen])
924+
925+
# Now transform values in the second regime and on
926+
i = 0
927+
for p in pvec
928+
if !isempty(p.regimes)
929+
for (k, v) in p.regimes[:value]
930+
if k != 1 # Skip the first regime.
931+
i += 1 # `values` stores regime values (after the first regime) beside each other.
932+
output[plen + i] = transform_to_real_line(p, values[plen + i])
933+
end
934+
end
935+
end
936+
end
937+
938+
return output
939+
else
940+
map(transform_to_real_line, pvec, values)
941+
end
942+
end
943+
@inline function transform_to_real_line(pvec::ParameterVector{T}; regime_switching::Bool = false) where T
944+
if regime_switching
945+
values = get_values(pvec) # regime-switching parameters returned by default
946+
947+
# Transform values in the first regime
948+
plen = length(pvec) # since values is not passed, we can mutate values directly to avoid extra allocations
949+
map!(transform_to_real_line, (@view values[1:plen]), pvec, values[1:plen])
950+
951+
# Now transform values in the second regime and on
952+
i = 0
953+
for p in pvec
954+
if !isempty(p.regimes)
955+
for (k, v) in p.regimes[:value]
956+
if k != 1 # Skip the first regime.
957+
i += 1 # `values` stores regime values (after the first regime) beside each other.
958+
values[plen + i] = transform_to_real_line(p, values[plen + i])
959+
end
960+
end
961+
end
962+
end
963+
964+
return values
965+
else
966+
map(transform_to_real_line, pvec)
967+
end
968+
end
842969

843970
"""
844971
```

test/parameters.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,19 @@ tomodel_answers[3] = 1.
3939
@test differentiate_transform_to_real_line(u,u.value) != differentiate_transform_to_model_space(u,u.value)
4040
end
4141
end
42+
end
43+
44+
@testset "Check type conversion for `value`, `valuebounds`, and `transform_parameterization`" begin
45+
@info "The following warning is expected"
46+
u1 = parameter(:σ_pist, 2.5230, (1, 5), (Float32(1), Float32(5)), Untransformed())
47+
u2 = parameter(:σ_pist, 2.5230, (1., 5.), (1., 5.), Untransformed())
4248

49+
@test u1.value == u2.value
50+
@test u1.valuebounds == u2.valuebounds
51+
@test u1.transform_parameterization == u2.transform_parameterization
52+
@test typeof(u1.value) == typeof(u2.value)
53+
@test eltype(u1.valuebounds) == eltype(u2.valuebounds)
54+
@test eltype(u1.transform_parameterization) == eltype(u2.transform_parameterization)
4355
end
4456

4557
# probability

test/regimes.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Test, ModelConstructors
22

33
# CURRENTLY ONLY TESTS VALUE, PRIOR, FIXED, AND VALUEBOUNDS SWITCHING, NO REGIME SWITCHING IN OTHER CASES
4+
# ALSO TESTS IF TRANSFORMS WORK CORRECTLY WITH REGIME-SWITCHING
45

56
@info "The following error 'get_regime_val(), Input Error: No regime 3' is expected."
67
@testset "Regime switching with parameters" begin
@@ -67,6 +68,24 @@ using Test, ModelConstructors
6768
ModelConstructors.set_regime_fixed!(u, 2, false; update_valuebounds = (10., 11.))
6869
@test u.regimes[:valuebounds][1] == (10., 11.)
6970
@test u.regimes[:valuebounds][2] == (10., 11.)
71+
72+
# test transform_to_real_line
73+
ModelConstructors.toggle_regime!(uvec, 1)
74+
set_regime_val!(uvec[1], 2, 1.; override_bounds = true) # break the valuebounds for now
75+
set_regime_val!(uvec[2], 2, 1.; override_bounds = true)
76+
values = ModelConstructors.get_values(uvec)
77+
real_vals_true = similar(values)
78+
real_vals_true[1] = ModelConstructors.transform_to_real_line(uvec[1], uvec[1].regimes[:value][1])
79+
real_vals_true[2] = ModelConstructors.transform_to_real_line(uvec[2], uvec[2].regimes[:value][1])
80+
real_vals_true[3] = ModelConstructors.transform_to_real_line(uvec[1], uvec[1].regimes[:value][2])
81+
real_vals_true[4] = ModelConstructors.transform_to_real_line(uvec[2], uvec[2].regimes[:value][2])
82+
real_vals1 = transform_to_real_line(uvec; regime_switching = true)
83+
real_vals2 = transform_to_real_line(uvec, values; regime_switching = true)
84+
@test real_vals1 == real_vals2 == real_vals_true
85+
86+
# test transform_to_model_space
87+
model_vals = transform_to_model_space(uvec, real_vals1; regime_switching = true)
88+
@test model_vals == values
7089
end
7190

7291
@testset "Regime switching with parameters when model regimes are different" begin

0 commit comments

Comments
 (0)