Understanding MPSGE: Interpreting solutions
July 18, 2025
The discussion during the MPSGE monthly this week got me thinking about an interesting example to demonstrate what the solutions from MPSGE mean. I thought it would be useful to share this example here, as it is a common question that comes up when working with MPSGE.
Model Setup
The model is a simple two sector, two good, closed economy with some intermediate inputs. The model diagram is given below.
The model has two sectors, X and Y, with
the W sector acting as a composite to represent the
Hicksian welfare index. I should note that W is not a
“real” sector, it is in the model to simplify the demand structure.
If you are unfamiliar with the tree structure above, it’s a more specific method to represent a SAM. A standard SAM lacks information about taxes and the nesting structure. While you can represent the same information in a standard SAM, the matrix becomes large and sparse which makes it difficult to interpret. The tree structure allows us to represent the same information in a more compact form.
Let’s break down the X sector to ensure we understand
the structure. The large X in the middle of the tree
represents the sector, anything above is an output and below is an
input, you may have surmised as much from the arrows. Directly above
X we see t=0, this is saying the elasticity of
transformation on the outputs is 0. Of course, there is only one output
PX with a quantity of 170, making the
elasticity of transformation irrelevant.
The input side is more interesting. The top nest is Cobb-Douglas, we
can see this with the s=1. The PX input has a
quantity of 20. The
denotes a tax on the input with a rate of T and going to
RA. We also have a nested CES structure called
va with an elasticity of .5.
Finally, we have one consumer, RA. For consumers, the
outputs represent endowments and the inputs represent final demands.
We will take PL to be the numeraire.
Model Balance
This model is not balanced. I just made up the numbers as I went. What does it mean to be balanced and how can we tell this is unbalanced?
The W sector is balanced, the sum of the inputs,
,
is the same as the sum of the outputs,
.
The X sector is not balanced, the outputs sum to 170 and
the inputs sum to 100. This is called the zero profit
condition, the sum of the inputs must equal the sum of the
outputs. This can be tricker to determine visually if there are taxes,
we may do an example in the future to demonstrate this.
Related to the zero profit condition is the income balance
condition. This refers to consumers, the sum of the endowments
must equal the sum of the final demands. Again, this is more complicated
with taxes, but in this case the endowment is 200 and the
final demand is 200, so it is balanced.
Finally, there is the market clearance condition.
Every commodity must sum to zero, with inputs being negative and outputs
being positive. Consider PX, this has a total output of
170 and a total input of 20+50+100=170, which
means for PX the market is balanced. Sometimes we get lucky
with numbers. PY is not balanced, the outputs sum to
125 and the inputs sum to 165.
Implementing in Julia
Here is a link to my Julia
Code. The file model.jl contains the MPSGE model, a
function to get the new equilibrium quantities, and a function to report
the results. The model is written so that we can easily update the start
values of the variables, this will be important.
The file main.jl contains the code to run the models and
generate the results. The rest of this document is discussing the
results of this file. I written comments in the code so you should be
able to follow along.
I think it’ll be useful to discuss the new_quantity
function. Find the definition of this function in model.jl,
you’ll see there are three version:
new_quantity(M::MPSGEModel, X::Symbol, P::Symbol, n::Symbol)
new_quantity(X::Sector, P::Commodity, n::Symbol)
new_quantity(RA::Consumer, P::Commodity, n::Symbol)Julia uses multiple dispatch, which means that the function can have
different implementations based on the types of the arguments. The first
version is a convenience so that we can use symbols to represent the
objects in the model. This function calls
new_quantity(M[X], M[P], n). Julia type-checks the first
argument and then calls the appropriate version of the function.
At the MPSGE Monthly, I discussed how the new_quantity
function works for sectors. To summarize,
compensated_demand(X, P, n)*X*Prepresents the total value of commodity P in sector
X under nest n.
For consumers, the function is similar, but using the endowments and demands instead of the inputs and outputs.
What are we doing?
It’s finally time to talk about what we are actually doing. We are
going to create an unbalanced model M and find the
equilibrium. We use this solution to create two new models,
L and N. The model L will have
quantities extracted from M using the
new_quantity function. The model N has the
same quantities as M, but the starting values will be set
to the values from M.
Both L and N will be balanced models, but
in slightly different ways. L will be balanced at the
standard benchmark solution, which means you could add up the values and
verify balance. You can verify this by inspecting the X
production block, production(L[:X]). Add up the inputs and
it will be the same as the outputs. N will appear balanced
when solving with cumulative_iteration_limit=0, but the
prices will not be unital. This is expected, we are using the same
quantities as M, but the prices are not set to 1.
At this point you should read some code and understand how
M, L, and N are created and
solved. Remember, Julia is interactive. If there is a portion that you
don’t understand, try to rerun it, break it down, extract small
examples, and play with it. You probably won’t break anything.
In particular, look at new_inputs and
new_outputs, this is kind of key.
Below are the equilibrium solutions before any applied taxes, taken
from the pre_tax dataframe.
| var | M | N | L |
|---|---|---|---|
| PY | 0.589443 | 0.589443 | 1.0 |
| PK | 1.1916 | 1.1916 | 1.0 |
| PL | 1.0 | 1.0 | 1.0 |
| PX | 0.387055 | 0.387055 | 1.0 |
| PW | 0.488249 | 0.488249 | 1.0 |
| RA | 219.16 | 219.16 | 219.16 |
| W | 2.24434 | 2.24434 | 1.0 |
| X | 4.22812 | 4.22812 | 1.0 |
| Y | 4.23592 | 4.23592 | 1.0 |
This should not be surprising. M and N have
the same values because we set them to be the same and L is
balanced at the benchmark solution, so all prices are set to 1.
More interesting is what happens when we set the value of the tax rate to be 20% and solve the models again.
| var | M | N | L |
|---|---|---|---|
| PY | 0.608383 | 0.608383 | 1.03213 |
| PK | 1.18811 | 1.18811 | 0.997069 |
| PL | 1.0 | 1.0 | 1.0 |
| PX | 0.412881 | 0.412881 | 1.06672 |
| PW | 0.510632 | 0.510632 | 1.04584 |
| RA | 227.957 | 227.957 | 227.957 |
| W | 2.23211 | 2.23211 | 0.994547 |
| X | 3.90919 | 3.90919 | 0.92457 |
| Y | 4.12817 | 4.12817 | 0.974563 |
It appears that L has a different solution than
M and N. Remember, these are all the same
model with the same tax, so the solutions should be consistent.
So what is going on? Everything in MPSGE is relative. In this case,
the solutions are relative to the initial equilibrium solution. For
L this is very easy to interpret because the benchmark
values are 1 so the price of PY shows a 3.2% increase
(again, relative to the numeraire, PL). That same 3.2%
increase can be seen in N as well, take the value of
PY in after the counterfactual and divide by the value of
PY in the benchmark,
0.608383/0.589443 = 1.03213.
The dataframe comparison computes these values for each
variable. And you can see that all the values are now equal.
| var | M | N | L |
|---|---|---|---|
| PY | 1.03213 | 1.03213 | 1.03213 |
| PK | 0.997069 | 0.997069 | 0.997069 |
| PL | 1.0 | 1.0 | 1.0 |
| PX | 1.06672 | 1.06672 | 1.06672 |
| PW | 1.04584 | 1.04584 | 1.04584 |
| RA | 1.04014 | 1.04014 | 1.04014 |
| W | 0.994547 | 0.994547 | 0.994547 |
| X | 0.92457 | 0.92457 | 0.92457 |
| Y | 0.974563 | 0.974563 | 0.974563 |
Conclusion
My intention is to show that MPSGE values are relative and it is very important to understand the context of the model when interpreting the results. I also wanted to demonstrate how flexible the MPSGE.jl syntax is for both creating models, but also for extracting information.