A Few Examples of Dynamic Calibration
September 19, 2025
This week was the MPSGE Monthly seminar, I presented a few examples calibrating a dynamic CGE model. I’ll use this blog post to share a few Julia specific tips and tricks I used in the process. The repository is available on GitHub.
Why is the data wrapped in a struct?
A struct is just a new data type. The dynamic models from my examples use 15 distinct variables, most of which are computed from the other variables. Wrapping the data in a struct allows me to pass around a single object instead of 15 different variables.
Let’s look at a small example. I want a function that requires two
inputs x and y, and computes z as
x * y. I could define the function like this:
function my_function(x::Float64, y::Float64)
z = x*y
return x + y + z
endOr I can hold the data in a struct and define the function like this:
struct MyData
x::Float64
y::Float64
z::Float64
function MyData(x::Float64, y::Float64)
z = x * y
new(x, y, z)
end
end
function my_function(data::MyData)
return data.x + data.y + data.z
endTo run each function, we can do the following:
my_function(2, 3) # returns 11.0
data = MyData(2, 3)
my_function(data) # also returns 11.0The first big advantage of using a struct is that I still have access
to z using data.z. The second is I can use the
MyData type to define other functions that require the same
data. For example, I could define a function that computes
x^2 + y^2 + z^2 like this:
function my_other_function(data::MyData)
return data.x^2 + data.y^2 + data.z^2
endThe struct allows me to pass around a single object that contains all
the data I need. If, for some reason, I need to change how
z is computed, I only need to change it in one place (the
constructor of the struct) instead of every function that uses
z.
This example is quite small, but the same principle applies to larger models with more variables.
The second big advantage is that Julia runs faster and is more efficient when there are fewer global variables. Julia is a compiled language, and the compiler can not guarantee the type of a global variable will always be the same. For this reason Julia will create more complex and generic code to handle the possibility of different types. By wrapping the data in a struct, I can ensure that the type of the data is always the same, and Julia can generate more efficient code.
Why use Plotly?
Julia has a rich ecosystem of plotting packages, here is a breakdown from 2023. The package I am using, PlotlyJS, isn’t even on that list!
Why did I choose PlotlyJS? Because I wanted a package that integrated nicely with dataframes, was easy to use, and produced interactive plots. PlotlyJS checks all those boxes. As a bonus, PlotlyJS can export plots as HTML files that can be easily shared and embedded in web pages. For example, the final plot from the included code is saved as an HTML file and embedded below:
Plotly itself is also popular in the Python and R communities, so there is a lot of documentation and examples available online.
If you are just learning Julia it may be worthwhile to learn either
Makie or AlgebraOfGraphics (which is
powered by Makie). Both are very powerful and flexible, but they have a
steeper learning curve than PlotlyJS. Do note that AlgebraOfGraphics is
designed to act like ggplot2 in R, so if you are familiar
with that package it may be easier to learn.
The difference between
import and using
In my shared code I use both import and
using to load packages. What is the difference? It has to
do with namespace management.
When you use using PackageName, all the exported
functions and types from that package are loaded into the current
namespace. This means you can call those functions and types directly
without prefixing them with the package name. For example:
using MPSGE
M = MPSGEModel()The function MPSGEModel is exported by the
MPSGE package, so I can call it directly.
When you use import PackageName, nothing is loaded into
the current namespace. You have to prefix all functions and types from
that package with the package name. For example:
import MPSGE
M = MPSGE.MPSGEModel()You can also use this method to import specific functions or types from a package. For example:
import MPSGE: MPSGEModel
M = MPSGEModel()In this case, only the MPSGEModel function is imported
into the current namespace, so I can call it directly. But no other
MPSGE functions or types are available without the MPSGE.
prefix.
This is useful for avoiding name conflicts. If two packages export a
function with the same name, using import allows you to
specify which package’s function you want to use.