From 23c4f515b65679a62635383ec833f7a07671e1a0 Mon Sep 17 00:00:00 2001 From: Adriano Meligrana <68152031+Tortar@users.noreply.github.com> Date: Fri, 29 Nov 2024 00:02:18 +0100 Subject: [PATCH] General docs improvements (#34) --- Project.toml | 2 +- docs/src/index.md | 5 ++- examples/basic_example.jl | 6 +--- examples/benchmark_w_matlab.jl | 20 ++++++------ examples/change_expectations.jl | 20 ++++++------ examples/compare_model_vs_real.jl | 31 ++++++++----------- .../get_parameters_and_initial_conditions.jl | 25 +++++++-------- examples/get_predictions.jl | 13 +++----- examples/get_simulations.jl | 12 ++++--- examples/multithreading_speedup.jl | 25 ++++++++------- examples/scenario_analysis_via_overload.jl | 25 ++++++--------- examples/scenario_analysis_via_shock.jl | 30 ++++++++++-------- 12 files changed, 99 insertions(+), 115 deletions(-) diff --git a/Project.toml b/Project.toml index 6176b94..4a1b525 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "BeforeIT" uuid = "ca9fcad7-41d0-4f76-b1e5-366c28bce52e" -authors = ["Aldo Glielmo ", "Mitja Devetak "] +authors = ["Aldo Glielmo ", "Mitja Devetak ", "Adriano Meligrana "] version = "0.1.2" [deps] diff --git a/docs/src/index.md b/docs/src/index.md index 415e7db..3d75cc2 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,7 +4,6 @@ CurrentModule = BeforeIT # Behavioural agent-based economic forecasting in Julia - Welcome to BeforeIT.jl, a Julia implementation of the agent-based model presented in [Economic forecasting with an agent-based model](https://www.sciencedirect.com/science/article/pii/S0014292122001891), the first ABM matching the performance of traditional economic forecasting tools. With BeforeIT.jl, you can perform economic forecasting and explore different counterfactual scenarios. Thanks to its modular design, the package is also a great starting point for anyone looking to extend its capabilities or integrate it with other tools. @@ -40,7 +39,7 @@ model = BeforeIT.initialise_model(parameters, initial_conditions, T) data = BeforeIT.run_one_sim!(model) ``` -To plot the results of the simulation, install the `Plots` package via ```Pkg.add("Plots")``` and then run +To plot the results of the simulation, you can use the `Plots` package ```julia using Plots @@ -52,7 +51,7 @@ plot(data.real_gdp) BeforeIT.jl is released under the GNU Affero General Public License v3 or later (AGPLv3+). -Copyright 2024- Banca d'Italia and the authors. +Copyright 2024 - Banca d'Italia and the authors. ## Main developers and maintainers diff --git a/examples/basic_example.jl b/examples/basic_example.jl index 4be6117..3ec3c0f 100644 --- a/examples/basic_example.jl +++ b/examples/basic_example.jl @@ -5,7 +5,6 @@ import BeforeIT as Bit using FileIO, Plots - # We then initialise the model loading some precomputed set of parameters and by specifying a number of epochs. # In another tutorial we will illustrate how to compute parameters and initial conditions. @@ -17,7 +16,6 @@ initial_conditions = Bit.AUSTRIA2010Q1.initial_conditions T = 16 model = Bit.init_model(parameters, initial_conditions, T) - # Note that the it is very simple to inspect the model by typing fieldnames(typeof(model)) @@ -33,13 +31,12 @@ data = Bit.init_data(model); # We can run now the model for a number of epochs and progressively update the data tracker. for t in 1:T - println(t) Bit.run_one_epoch!(model; multi_threading = true) Bit.update_data!(data, model) end # Note that we can equivalently run the model for a number of epochs in the single command -# `data = BeforeIT.run_one_sim!(model)` , but writing the loop explicitely is more instructive. +# `data = BeforeIT.run_one_sim!(model)`, but writing the loop explicitely is more instructive. # We can then plot any time series stored in the data tracker, for example @@ -67,4 +64,3 @@ Threads.nthreads() ps = Bit.plot_data_vector(data_vector) plot(ps..., layout = (3, 3)) - diff --git a/examples/benchmark_w_matlab.jl b/examples/benchmark_w_matlab.jl index 6cef620..f4cc904 100644 --- a/examples/benchmark_w_matlab.jl +++ b/examples/benchmark_w_matlab.jl @@ -1,5 +1,5 @@ -# # Comparing the performance of the Julia and MATLAB implementations +## Comparing the performance of the Julia and MATLAB implementations # We can compare the performance of the Julia and MATLAB implementations # by running the same model for the same number of epochs and measuring @@ -22,11 +22,11 @@ parameters = BeforeIT.AUSTRIA2010Q1.parameters initial_conditions = BeforeIT.AUSTRIA2010Q1.initial_conditions T = 12 -# we run the code to compile it first +# We run the code to compile it first @time run(parameters, initial_conditions, T; multi_threading = false); @time run(parameters, initial_conditions, T; multi_threading = true); -# time taken by the MATLAB code and the Generated C code with MATLAB Coder +# Time taken by the MATLAB code and the Generated C code with MATLAB Coder # (6 threads for the parallel version), computed independently on an AMD Ryzen 5 5600H matlab_times = [4.399592, 4.398576, 4.352314, 4.385039, 4.389989] matlab_time = mean(matlab_times) @@ -40,7 +40,7 @@ c_times_multi_thread = [0.305, 0.324, 0.330, 0.334, 0.323] c_time_multi_thread = mean(c_times_multi_thread) c_time_multi_thread_std = std(c_times_multi_thread) -# time taken by the Julia code (same platform as in the the matlab benchmarks), +# Time taken by the Julia code (same platform as in the the matlab benchmarks), # computed as the average of 5 runs n_runs = 5 @@ -58,13 +58,12 @@ end julia_time_multi_thread = mean(julia_times_multi_thread) julia_time_multi_thread_std = std(julia_times_multi_thread) -# get the number of threads used +# Get the number of threads used n_threads = Threads.nthreads() theme(:default, bg = :white) -# bar chart of the time taken vs the time taken by the MATLAB code, also plot the stds as error bars -# make a white background with no grid +# Bar chart of the time taken vs the time taken by the MATLAB code, also plot the stds as error bars bar( ["MATLAB", "Gen. C, 1 thread", "Gen. C, 6 threads", "Julia, 1 thread", "Julia, $n_threads threads"], [matlab_time, c_time, c_time_multi_thread, julia_time_1_thread, julia_time_multi_thread], @@ -79,9 +78,8 @@ bar( guidefont = font(6) ) +# Save the image +savefig("benchmark_w_matlab.png") -# the Julia implementation is faster than the MATLAB implementation, and the multi-threaded version is +# The Julia implementation is faster than the MATLAB implementation, and the multi-threaded version is # faster than the single-threaded version. - -# save the image -savefig("benchmark_w_matlab.png") diff --git a/examples/change_expectations.jl b/examples/change_expectations.jl index f38939e..8bc62a9 100644 --- a/examples/change_expectations.jl +++ b/examples/change_expectations.jl @@ -1,11 +1,11 @@ # # Changing expectations via function overloading -# In this tutorial we will illustrate how to experiment with different expectations of the agents in the model. +# In this tutorial we will illustrate how to experiment with +# different expectations of the agents in the model. import BeforeIT as Bit using Random, Plots - # Import standard parameters and initial conditions par = Bit.AUSTRIA2010Q1.parameters @@ -19,21 +19,21 @@ model = Bit.init_model(par, init, T) data = Bit.run_one_sim!(model) # Now we can experiment with changing expectations of the agents in the model. -# We will change the function 'estimate_next_value' to make the agents expect -# the last value of the time series (in way representing backward looking expectations) +# We will change the function `estimate_next_value` to make the agents expect +# the last value of the time series (so to represent backward looking expectations) import BeforeIT: estimate_next_value function estimate_next_value(data) return data[end] end -# run the model again, with the same seed +# Run the model again, with the same seed Random.seed!(1234) model = Bit.init_model(par, init, T) data_back = Bit.run_one_sim!(model) -# plot the results, comparing the two cases as different lines +# Plot the results, comparing the two cases as different lines p1 = plot(data.real_gdp, title = "gdp", titlefont = 10, label = "forward looking") plot!(p1, data_back.real_gdp, titlefont = 10, label = "backward looking") @@ -43,7 +43,7 @@ plot!(p2, data_back.real_household_consumption, titlefont = 10, label = "backwar plot(p1, p2, layout = (2, 1), legend = true) -# plot all time series +# Plot all time series p1 = plot(data.real_gdp, title = "gdp", titlefont = 10) plot!(p1, data_back.real_gdp, titlefont = 10) @@ -66,6 +66,6 @@ plot!(p9, data_back.nominal_gdp ./ data_back.real_gdp, titlefont = 10) plot(p1, p2, p3, p4, p5, p6, p7, p8, p9, layout = (3, 3), legend = false) -# Note that, importantly, once the function estimate_next_value has been changed, the model will use the new -# expectations in all the simulations, unless the function is changed again. -# To restore the original expectations you need to close the Julia session. +# Note that, importantly, once the function `estimate_next_value` has been changed, +# the model will use the new expectations in all the simulations, unless the function +# is changed again. To restore the original expectations you could close the Julia session. diff --git a/examples/compare_model_vs_real.jl b/examples/compare_model_vs_real.jl index cd366ba..5f78cac 100644 --- a/examples/compare_model_vs_real.jl +++ b/examples/compare_model_vs_real.jl @@ -11,7 +11,6 @@ real_data = BeforeIT.ITALY_CALIBRATION.data model = load("data/italy/abm_predictions/2015Q1.jld2")["model_dict"] function plot_model_vs_real(model, real, varname; crop = true) - if length(varname) > 9 if varname[(end - 8):end] == "quarterly" x_nums = "quarters_num" @@ -39,14 +38,12 @@ function plot_model_vs_real(model, real, varname; crop = true) xlimits = :auto end - if crop all_tick_numbers = model[x_nums] else all_tick_numbers = real[x_nums] end - num_ticks = [] year_ticks = [] for r in all_tick_numbers @@ -59,7 +56,6 @@ function plot_model_vs_real(model, real, varname; crop = true) end end - p = plot( real[x_nums], 1e6 * real[varname], @@ -76,7 +72,6 @@ function plot_model_vs_real(model, real, varname; crop = true) return p end - num_ticks = [] year_ticks = [] for r in real_data["years_num"] @@ -89,42 +84,42 @@ for r in real_data["years_num"] end end -# plot real gdp +# Plot real gdp plot_model_vs_real(model, real_data, "real_gdp") -# plot real household consumption +# Plot real household consumption plot_model_vs_real(model, real_data, "real_household_consumption") -# plot real fixed capital formation +# Plot real fixed capital formation plot_model_vs_real(model, real_data, "real_fixed_capitalformation") -# plot real government consumption +# Plot real government consumption plot_model_vs_real(model, real_data, "real_government_consumption") -# plot real exports +# Plot real exports plot_model_vs_real(model, real_data, "real_exports") -# plot real imports +# Plot real imports plot_model_vs_real(model, real_data, "real_imports") -### quarterly plots ### +### Quarterly Plots ### -# plot real gdp quarterly +# Plot real gdp quarterly p1 = plot_model_vs_real(model, real_data, "real_gdp_quarterly") -# plot real household consumption quarterly +# Plot real household consumption quarterly p2 = plot_model_vs_real(model, real_data, "real_household_consumption_quarterly") -# plot real fixed capital formation quarterly +# Plot real fixed capital formation quarterly p3 = plot_model_vs_real(model, real_data, "real_fixed_capitalformation_quarterly") -# plot real government consumption quarterly +# Plot real government consumption quarterly p4 = plot_model_vs_real(model, real_data, "real_government_consumption_quarterly") -# plot real exports quarterly +# Plot real exports quarterly p5 = plot_model_vs_real(model, real_data, "real_exports_quarterly") -# plot real imports quarterly +# Plot real imports quarterly p6 = plot_model_vs_real(model, real_data, "real_imports_quarterly") plot(p1, p2, p3, p4, p5, p6, layout = (3, 2), legend = false) diff --git a/examples/get_parameters_and_initial_conditions.jl b/examples/get_parameters_and_initial_conditions.jl index e0a20e3..cc946ce 100644 --- a/examples/get_parameters_and_initial_conditions.jl +++ b/examples/get_parameters_and_initial_conditions.jl @@ -3,21 +3,23 @@ import BeforeIT as Bit using Dates, FileIO - -# We start from loading the calibration oject for italy, which contains 4 datasets: calibration_data, figaro, data, and ea -# These are saved within BeforeIT for the Italian case, and would need to be appropriately generated for other countries +# We start from loading the calibration object for italy, which contains +# 4 datasets: `calibration_data`, `figaro`, `data`, and `ea`. These are +# saved within `BeforeIT.jl` for the Italian case, and would need to be +# appropriately generated for other countries. cal = Bit.ITALY_CALIBRATION - fieldnames(typeof(cal)) -# These are essentually 4 dictionaries with well defined keys, such as +# These are essentially 4 dictionaries with well defined keys, such as + println(keys(cal.calibration)) println(keys(cal.figaro)) println(keys(cal.data)) println(keys(cal.ea)) # The object also contains two time variables related to the data + println(cal.max_calibration_date) println(cal.estimation_date) @@ -26,19 +28,18 @@ println(cal.estimation_date) calibration_date = DateTime(2010, 03, 31) parameters, initial_conditions = Bit.get_params_and_initial_conditions(cal, calibration_date; scale = 0.01) -# In sgeneral, we might want to repeat this operation for multiple quarters. -# In the following, we loop over all quarters from 2010Q1 to 2019Q4 +# In general, we might want to repeat this operation for multiple quarters. +# In the following, we loop over all quarters from `2010Q1` to `2019Q4` # and save the parameters and initial conditions in separate files. # We can then load these files later to run the model for each quarter. + start_calibration_date = DateTime(2010, 03, 31) end_calibration_date = DateTime(2019, 12, 31) for calibration_date in collect(start_calibration_date:Dates.Month(3):end_calibration_date) params, init_conds = Bit.get_params_and_initial_conditions(cal, calibration_date; scale = 0.0005) save( - "data/" * - "italy/" * - "/parameters/" * + "data/italy/parameters/" * string(year(calibration_date)) * "Q" * string(Dates.quarterofyear(calibration_date)) * @@ -46,9 +47,7 @@ for calibration_date in collect(start_calibration_date:Dates.Month(3):end_calibr params, ) save( - "data/" * - "italy/" * - "/initial_conditions/" * + "data/italy/initial_conditions/" * string(year(calibration_date)) * "Q" * string(Dates.quarterofyear(calibration_date)) * diff --git a/examples/get_predictions.jl b/examples/get_predictions.jl index 0eae39f..624db8b 100644 --- a/examples/get_predictions.jl +++ b/examples/get_predictions.jl @@ -1,34 +1,29 @@ -# In this tutorial we illustrate how to get predictions from the model for a number of quarters starting from previous simulations. +# In this tutorial we illustrate how to get predictions from the model for +# a number of quarters starting from previous simulations. using BeforeIT, FileIO using Dates year_ = 2010 -number_years = 9#10 +number_years = 9 number_quarters = 4 * number_years -horizon = 12#12 +horizon = 12 number_seeds = 4 number_sectors = 62 # Load the real time series data = BeforeIT.ITALY_CALIBRATION.data - quarters_num = [] year_m = year_ for month in 4:3:((number_years + 1) * 12 + 1) - year_m = year_ + (month รท 12) mont_m = month % 12 date = DateTime(year_m, mont_m, 1) - Day(1) - push!(quarters_num, BeforeIT.date2num(date)) end for i in 1:number_quarters - quarter_num = quarters_num[i] - BeforeIT.get_predictions_from_sims(data, quarter_num, horizon, number_seeds) - end diff --git a/examples/get_simulations.jl b/examples/get_simulations.jl index 9b6dc2e..cf64a54 100644 --- a/examples/get_simulations.jl +++ b/examples/get_simulations.jl @@ -1,10 +1,13 @@ -# Here we show how to get simulations for all quarters from 2010Q1 to 2019Q4, and for all years from 2010 to 2019. +# Here we show how to get simulations for all quarters +# from `2010Q1` to `2019Q4`, and for all years from 2010 to 2019. using BeforeIT, MAT, FileIO -# The following code, loads the parameters and initial conditions, it initialises the model, -# runs the model n_sims times, and finally saves the data_list into a .jld2 file with an appropriate name. -# The whole process is repeatead for all quarters from 2010Q1 to 2019Q4, and for all years from 2010 to 2019. +# The following code loads the parameters and initial conditions, +# it initialises the model, runs the model `n_sims` times, and finally +# saves the `data_vector` into a `.jld2` file with an appropriate name. +# The whole process is repeatead for all quarters from `2010Q1` to `2019Q4`, +# and for all years from 2010 to 2019. for year in 2010:2019 for quarter in 1:4 @@ -16,6 +19,5 @@ for year in 2010:2019 n_sims = 4 data_vector = BeforeIT.run_n_sims(model, n_sims) save("data/italy/simulations/" * string(year) * "Q" * string(quarter) * ".jld2", "data_vector", data_vector) - end end diff --git a/examples/multithreading_speedup.jl b/examples/multithreading_speedup.jl index a8b96ec..1f9c573 100644 --- a/examples/multithreading_speedup.jl +++ b/examples/multithreading_speedup.jl @@ -1,13 +1,12 @@ # # Multithreading speedup for large models -# In this tutorial we illustrate how to make use of multi threading in BeforeIT to allow for faster -# executions of single simulation runs. +# In this tutorial we illustrate how to make use of multi threading in `BeforeIT.jl` +# to allow for faster executions of single simulation runs. import BeforeIT as Bit using FileIO, Plots, StatsPlots - -# We then initialise the model, this time we will use the Italy 2010Q1 scenario, +# First, we initialise the model, this time we use the Italy 2010Q1 scenario, # and we want to simulate the model for a large number of epochs parameters = Bit.ITALY2010Q1.parameters @@ -15,23 +14,25 @@ initial_conditions = Bit.ITALY2010Q1.initial_conditions T = 50 model = Bit.init_model(parameters, initial_conditions, T); - # The model is in scale 1:2000, so it has around 30,000 households +model.prop.H -println(model.prop.H) - -# Note that households are the sum of active and inactive households and the owners of firms and of the bank -println(length(model.w_act) + length(model.w_inact) + length(model.firms) + 1) +# Note that the households number is actually the sum of active and +# inactive households, the owners of firms and of the bank +length(model.w_act) + length(model.w_inact) + length(model.firms) + 1 # Let's fist check how many threads we have available in this Julia session +Threads.nthreads() -println(Threads.nthreads()) +# Then we need to first compile the code not to count compilation time, +# we can do that just by executing the function one time +Bit.run_one_sim!(model; multi_threading = false) # Let's now compare the performance of single threading and multi threading - +model = Bit.init_model(parameters, initial_conditions, T); @time data = Bit.run_one_sim!(model; multi_threading = false); model = Bit.init_model(parameters, initial_conditions, T); @time data = Bit.run_one_sim!(model; multi_threading = true); -# Is the speedup in line to what we would expect? +# Is the speedup in line to what we would expect? Yes! diff --git a/examples/scenario_analysis_via_overload.jl b/examples/scenario_analysis_via_overload.jl index 9d14206..d42dcc6 100644 --- a/examples/scenario_analysis_via_overload.jl +++ b/examples/scenario_analysis_via_overload.jl @@ -1,21 +1,23 @@ # # Scenario analysis via function overloading -# In this tutorial we will illustrate how to perform a scenario analysis by running the model multiple times -# under a specific shock and comparing the results with the unshocked model. +# In this tutorial we will illustrate how to perform a scenario analysis by running +# the model multiple times under a specific shock and comparing the results with the +# unshocked model. import BeforeIT as Bit using Plots, StatsPlots - parameters = Bit.AUSTRIA2010Q1.parameters initial_conditions = Bit.AUSTRIA2010Q1.initial_conditions -# initialise the model and the data collector +# Initialise the model and the data collector + T = 20 model = Bit.init_model(parameters, initial_conditions, T); data = Bit.init_data(model); # Simulate the model for T quarters + data_vec_baseline = Bit.run_n_sims(model, 4) # Now, apply a shock to the model and simulate it again @@ -27,7 +29,6 @@ function central_bank_rate(cb::Bit.CentralBank, model::Bit.Model) gamma_EA = model.rotw.gamma_EA pi_EA = model.rotw.pi_EA taylor_rate = Bit.taylor_rule(cb.rho, cb.r_bar, cb.r_star, cb.pi_star, cb.xi_pi, cb.xi_gamma, gamma_EA, pi_EA) - if model.agg.t < 10 return 0.01 else @@ -59,13 +60,7 @@ StatsPlots.errorline!( ylabel = "GDP", ) -# Note that, importantly, once the function central_bank_rate has been changed, the model will use the new -# interest rate in all the simulations, unless the function is changed again. -# To restore the original interest rate, you need to close and restart the Julia session. - -function central_bank_rate(cb::Bit.CentralBank, model::Bit.Model) - gamma_EA = model.rotw.gamma_EA - pi_EA = model.rotw.pi_EA - taylor_rate = Bit.taylor_rule(cb.rho, cb.r_bar, cb.r_star, cb.pi_star, cb.xi_pi, cb.xi_gamma, gamma_EA, pi_EA) - return taylor_rate -end +# Note that, importantly, once the function `central_bank_rate` has been changed, +# the model will use the new interest rate in all the simulations, unless the +# function is changed again. To restore the original interest rate, you could +# close and restart the Julia session. diff --git a/examples/scenario_analysis_via_shock.jl b/examples/scenario_analysis_via_shock.jl index 4d84da9..ea5ac6f 100644 --- a/examples/scenario_analysis_via_shock.jl +++ b/examples/scenario_analysis_via_shock.jl @@ -1,26 +1,28 @@ # # Scenario analysis via custom shocks -# In this tutorial we will illustrate how to perform a scenario analysis by running the model multiple times -# under a specific shock and comparing the results with the unshocked model. +# In this tutorial we will illustrate how to perform a scenario analysis by +# running the model multiple times under a specific shock and comparing the +# results with the unshocked model. import BeforeIT as Bit using Plots, StatsPlots - parameters = Bit.AUSTRIA2010Q1.parameters initial_conditions = Bit.AUSTRIA2010Q1.initial_conditions -# initialise the model and the data collector +# Initialise the model and the data collector T = 20 model = Bit.init_model(parameters, initial_conditions, T); # Simulate the model for T quarters data_vec_baseline = Bit.run_n_sims(model, 4) -# Now, apply a shock to the model and simulate it again -# A shock is simply a function that takes the model and changes some of its parameters for a specific time period. +# Now, apply a shock to the model and simulate it again. +# A shock is simply a function that takes the model and changes some of +# its parameters for a specific time period. -# In this case, let's define an interest rate shock that sets the interest rate for a number of epochs. +# In this case, let's define an interest rate shock that sets the interest +# rate for a number of epochs. # We do this by first defining a "struct" with some useful attributes struct CustomShock @@ -28,15 +30,16 @@ struct CustomShock final_time::Int # number of epochs for the shock end -# and then by making the struct a callable function that changes the interest rate in the model, -# this is done in Julia using the syntax below +# and then by making the struct a callable function that changes the interest +# rate in the model, this is done in Julia using the syntax below function (s::CustomShock)(model::Bit.Model) if model.agg.t <= s.final_time model.cb.r_bar = s.rate end end -# Now we define a specific shock with a rate of 0.01 for the first 10 epochs, and run a shocked simulation +# Now we define a specific shock with a rate of 0.01 for the first 10 epochs, +# and run a shocked simulation custom_shock = CustomShock(0.0, 10) data_vec_shocked = Bit.run_n_sims(model, 4; shock = custom_shock) @@ -63,6 +66,7 @@ StatsPlots.errorline!( ylabel = "GDP", ) -# Note that, importantly, once the function central_bank_rate has been changed, the model will use the new -# interest rate in all the simulations, unless the function is changed again. -# To restore the original interest rate, we can simply re-import the function central_bank_rate +# Note that, importantly, once the function `central_bank_rate` has been changed, +# the model will use the new interest rate in all the simulations, unless the function +# is changed again. To restore the original interest rate, we can simply re-import the +# function `central_bank_rate`.