Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New structure #17

Merged
merged 34 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
39ac662
Handle different input syntax
Oct 13, 2023
e67a0b8
Remove math symbols from model
Oct 14, 2023
12c0d1f
Add potential tests
Oct 27, 2023
21ac059
Add solve functionality to src
Oct 27, 2023
3682b46
Legacy code for stochastic model
Oct 27, 2023
b9a9802
Add solve file and file with new types for model components
Oct 27, 2023
0b8bbba
New component types of model (vars, params)
Oct 27, 2023
837137d
Finished creation of Variables type
Oct 27, 2023
0811afd
Delete redundant exports, add model fct
Oct 28, 2023
68f4b0b
Extend Readme
Oct 28, 2023
291c1cd
Allow blocks in variable definition
Oct 28, 2023
3a89b62
Extend readme
Oct 28, 2023
5eb39c2
Moved solve function and used new syntax
Oct 28, 2023
fe2c935
Implemented new syntax
Oct 28, 2023
58fda11
Model is now immutable
Oct 28, 2023
762009f
New equations type
Oct 28, 2023
f473b14
Remark to thread safety
Oct 28, 2023
d8591c5
Adapted combine functionality
Oct 28, 2023
a5f0200
Minor change in display
Oct 28, 2023
27bd047
Load default models
Oct 28, 2023
c415884
Add autodetection of endos to docs
Oct 28, 2023
cf60015
Add some tests
Oct 28, 2023
856a1fd
Adapted format
Oct 28, 2023
d96b41c
Adapted format
Oct 28, 2023
91845fd
Small fix
Oct 29, 2023
53eb6e1
Adapt combine example
Oct 29, 2023
0cdb931
New method to find left symbol
Oct 29, 2023
f72d88c
Completed new model generation
Oct 29, 2023
7629403
Adapted models
Oct 29, 2023
9266f3f
Bugfix for empty variables
Oct 29, 2023
e9772aa
Removed println and fixed BMW
Oct 29, 2023
29ede00
Included BMW
Oct 29, 2023
d62c19a
Add some packages
Oct 29, 2023
b279576
Merge branch 'main' into new-structure
JohannesNaegele Oct 29, 2023
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
7 changes: 5 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ version = "1.0.0-DEV"

[deps]
Crayons = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Pipe = "b98c9c47-44ae-5843-9183-064241ee97a0"

[compat]
NLsolve = "4"
MacroTools = "0.5"
Crayons = "4"
MacroTools = "0.5"
NLsolve = "4"
julia = "1"

[extras]
Expand Down
154 changes: 152 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,157 @@ This package provides solution and calibration methods for stock-flow consistent

## Basic usage

### Model definition

Consider SIM from Godley and Lavoie 2007:

```julia
# Define parameter values
params_dict = @parameters begin
θ = 0.2
α_1 = 0.6
α_2 = 0.4
end

# Define endogenous variables
endogenous = @variables Y, T, YD, C, H_s, H_h, H
# Define exogenous variables
exogenous = @variables G

# Define model
my_first_model = model(
endos = endogenous,
exos = exogenous,
params = params_dict,
equations = @equations begin
Y = C + G
T = θ * Y
YD = Y - T
C = α_1 * YD + α_2 * H[-1]
H_s + H_s[-1] = G - T
H_h + H_h[-1] = YD - C
H = H_s + H_s[-1] + H[-1]
end
)
```

The difference between parameters and exogenous parameters is that the latter *can change over time* which is especially important if they appear in lagged terms. In this case, we need to provide several values for one exogenous variable.

Note also that the model is not aware of any concrete values of parameters or exogenous variables. Instead, data is always supplied externaly to solution/calibration functions. Thus, `params_dict` is just syntactical sugar for `@variables [k for (k, v) in params_dict]`.

Lastly, the specification of endogenous variables is *optional* and might be omitted if is much effort for larger models. However, it enables easier debugging. If not endogenous variables are not specified, the package will assume the symbol farthest on the left hand side of each equation to be endogenous.

### Model solution
If we want to solve a model we need data on
1. exogenous variables (and their lags)
2. lags of endogenous variables
3. parameters

```julia
# data on exogenous parameter G
exos = [20.0][:, :]
# lagged values of endogenous variables are all 0.0
lags = fill(0.0, length(my_first_model.endogenous_variables), 1)
# get raw parameter values
param_values = map(x -> params_dict[x], my_first_model.parameters)
```



```julia
# Solve model for 59 periods
for i in 1:59
solution = solve(my_first_model, lags, exos, param_values)
lags = hcat(lags, solution)
end
```

### Data handling and plotting
`DataFrames` and `Pipe` give us functionality similar to R's `dplyr`; the package ```Gadfly``` is very similar to R's `ggplots2`:

```julia
using DataFrames
using Pipe
using Gadfly

# Convert results to DataFrame
df = DataFrame(lags', my_first_model.endogenous_variables)
# Add time column
df[!, :period] = 1:nrow(df)
# Select variables, convert to long format, and plot variables
@pipe df |>
select(_, [:Y, :C, :YD, :period]) |>
stack(_, Not(:period), variable_name=:variable) |>
plot(
_,
x=:period,
y=:value,
color=:variable,
Geom.line
)
```

An example with actual data can be found here.

## Advanced usage

### Probabilistic models

### Model calibration



## Syntax

There are plenty different syntax options for defining variables *outside the model function* enabled:

```julia
# As single variables (slurping)
endogenous = @variables Y T YD C H_s H_h H
# As tuple
endogenous = @variables Y, T, YD, C, H_s, H_h, H
# As array
endogenous = @variables [
Y,
T,
YD,
C,
H_s,
H_h,
H
]
# As block
endogenous = @variables begin
Y
T
YD
C
H_s
H_h
H
end
```

Inside the function we need parantheses and can not use whitespace seperation.

## Internals

Internally, we just generate a function `f!` for our model which can be used together with an arbitrary root finding solver:

```julia
function f!(diff, endos, lags, exos, params)
diff[1] = endos[1] - (endos[4] + exos[1, end - 0])
diff[2] = endos[2] - params[1] * endos[1]
diff[3] = endos[3] - (endos[1] - endos[2])
diff[4] = endos[4] - (params[2] * endos[3] + params[3] * lags[7, end - 0])
diff[5] = (endos[5] + lags[5, end - 0]) - (exos[1, end - 0] - endos[2])
diff[6] = (endos[6] + lags[6, end - 0]) - (endos[3] - endos[4])
diff[7] = endos[7] - (endos[5] + lags[5, end - 0] + lags[7, end - 0])
end
```

## Remarks

- The instantiation of a model is not thread-safe yet.
- The point I would like to see proven: Julia is a long-term Pareto improvement over R, Matlab et al.
Currently the model instantiation is not thread-safe.

Feel free to ask questions and report bugs via issues!
25 changes: 13 additions & 12 deletions examples/combine.jl
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
using Consistent

PC_gdp = @model begin
@endogenous Y YD T V C
@exogenous r G B_h
@parameters α_1 α_2 θ
@equations begin
PC_gdp = model(
endos = @variables(Y, YD, T, V, C),
exos = @variables(r, G, B_h),
params = @variables(α_1, α_2, θ),
eqs = @equations begin
Y = C + G
YD = Y - T + r[-1] * B_h[-1]
T = θ * (Y + r[-1] * B_h[-1])
V = V[-1] + (YD - C)
C = α_1 * YD + α_2 * V[-1]
end
end
)

PC_hh = @model begin
@endogenous H_h B_h B_s H_s B_cb r
@exogenous r_exo G V YD T
@parameters λ_0 λ_1 λ_2
@equations begin
PC_hh = model(
endos = @variables(H_h, B_h, B_s, H_s, B_cb, r),
exos = @variables(r_exo, G, V, YD, T),
params = @variables(λ_0, λ_1, λ_2),
eqs = @equations begin
H_h = V - B_h
B_h = (λ_0 + λ_1 * r - λ_2 * (YD / V)) * V
B_s = (G + r[-1] * B_s[-1]) - (T + r[-1] * B_cb[-1]) + B_s[-1]
H_s = B_cb - B_cb[-1] + H_s[-1]
B_cb = B_s - B_h
r = r_exo
end
end
)

# Note: Since the variables and equations are ordered this operation is not commutative!
PC_complete = PC_gdp + PC_hh
60 changes: 41 additions & 19 deletions examples/solve.jl
Original file line number Diff line number Diff line change
@@ -1,35 +1,57 @@
using Consistent
using NLsolve
using DataFrames
using Gadfly
using Pipe
using BenchmarkTools
Gadfly.push_theme(:dark)

function solve(model, lags, exos, params)
nlsolve(
(x, y) -> model.f!(x, y, lags, exos, params),
fill(1.0, length(model.endogenous_variables)),
autodiff = :forward,
).zero
# Define parameter values
params_dict = @parameters begin
θ = 0.2
α_1 = 0.6
α_2 = 0.4
end

# Setup SIM
model = Consistent.SIM() # load predefined SIM model
params_dict = Consistent.SIM(true) # load default parameters
exos = [20.0] # this is only G
exos = exos[:, :] # bring in matrix format
lags = fill(0.0, length(model.endogenous_variables), 1) # lagged values of endogenous variables are all 0.0
param_values = map(x -> params_dict[x], model.parameters) # get raw parameter values
solution = solve(model, lags, exos, param_values) # solve first period
# Define endogenous variables
endogenous = @variables Y, T, YD, C, H_s, H_h, H
# Define exogenous variables
exogenous = @variables G

# Solve model for 50 periods
for i in 1:50
solution = solve(model, lags, exos, param_values) # solve first period
# Define model equations
equations = @equations begin
Y = C + G
T = θ * Y
YD = Y - T
C = α_1 * YD + α_2 * H[-1]
H_s + H_s[-1] = G - T
H_h + H_h[-1] = YD - C
H = H_s + H_s[-1] + H[-1]
end

# Define model
my_first_model = model(
endos = endogenous,
exos = exogenous,
params = params_dict,
eqs = equations
)

# Data on exogenous parameter G
exos = [20.0][:, :]
# Lagged values of endogenous variables are all 0.0
lags = fill(0.0, length(my_first_model.endogenous_variables), 1)
# Get raw parameter values
param_values = map(x -> params_dict[x], my_first_model.parameters)

# Solve model for 59 periods
for i in 1:59
solution = solve(my_first_model, lags, exos, param_values)
lags = hcat(lags, solution)
end

# Convert results to DataFrame
df = DataFrame(lags', model.endogenous_variables)
df = DataFrame(lags', my_first_model.endogenous_variables)
# Add time column
df[!, :period] = 1:nrow(df)
# Select variables, convert to long format, and plot variables
@pipe df |>
Expand Down
46 changes: 46 additions & 0 deletions examples/solve_SIM_stoch.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Consistent
using NLsolve
using Distributions
using Plots
using StatsPlots

# SIM_stoch
model = SIMStoch()
g_0 = 20.0
u_G = rand(Normal(0, 1), 1000 * 100)
u_T = rand(Normal(0, 0.25), 1000 * 100)
u_C = rand(Normal(0, 0.5), 1000 * 100)
exos = zeros(4, 1)
exos[1] = g_0
initial_guess = fill(1.0, length(model.endogenous_variables))
lags = fill(0.0, length(model.endogenous_variables))
lags = lags[:, :]
param_values = map(x -> params[x], model.parameters)
a_0 = solve(model, lags, exos, param_values)
simulation = zeros(101, 1000)
simulation[1, :] .= a_0[2]

for i in 1:1000
a = a_0
for j = 1:100
exos[2] = u_G[j+(i-1)*100]
exos[3] = u_T[j+(i-1)*100]
exos[4] = u_C[j+(i-1)*100]
a = solve(model, a[:, :], exos, param_values)
simulation[j+1, i] = a[2]
end
end

plot(simulation[:, 1])
violin(transpose(simulation[1:50, :]), linewidth = 0, legend = false)
# plot(simulation[1:30,1:1000], legend = false, seriestype = :scatter)

# DIS steady state
initial_guess = fill(1.0, length(sfc_model.endogenous_variables))
lags = fill(0.1, (length(sfc_model.endogenous_variables), 1)) # quite arbitrary
param_values = map(x -> get(values, x, nothing), sfc_model.parameters)
a = solve(initial_guess, lags, Matrix[], param_values)
for i in 1:1000
a = solve(initial_guess, a[:,:], Matrix[], param_values)
end
a
15 changes: 6 additions & 9 deletions src/CombineModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@ function +(model1::Model, model2::Model)
exos2 = filter(
x -> !((x in model1.endogenous_variables) || (x in exos1)), model2.exogenous_variables
)
equations = vcat(model1.equations, model2.equations)
global Consistent.sfc_model = Model()
sfc_model.endogenous_variables = vcat(model1.endogenous_variables, model2.endogenous_variables)
sfc_model.exogenous_variables = vcat(exos1, exos2)
sfc_model.parameters = vcat(model1.parameters, model2.parameters)
sfc_model.math_operators = model1.math_operators # FIXME: does this make sense?
sfc_model.equations = equations
eval(build_f!(equations))
deepcopy(sfc_model)
return model(
endos=Variables(vcat(model1.endogenous_variables, model2.endogenous_variables)),
exos=Variables(vcat(exos1, exos2)),
params=Variables(vcat(model1.parameters, model2.parameters)),
eqs=Equations(vcat(model1.equations, model2.equations))
)
end
8 changes: 7 additions & 1 deletion src/Consistent.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
module Consistent

export @model, @endogenous, @exogenous, @parameters, @equations
export @parameters, @equations, @variables
export model, solve

include("Helpers.jl")
include("ModelComponents.jl")
include("Model.jl")
include("Variables.jl")
include("ConstructResiduals.jl")
include("Macros.jl")
include("CombineModels.jl")
include("Solve.jl")

# Godley/Lavoie
include("models/SIM.jl")
include("models/SIM_stoch.jl")
include("models/LP.jl")
include("models/DIS.jl")
include("models/PC.jl")

include("models/BMW.jl")

end # module Consistent
Loading
Loading