Model of Planned Aggregate Expediture¶
This is just some initial setup, and data types to run the model.
Setup Model¶
Lets setup some types to simulate the model. With our model we want to be able to simulate the following:
- Gross Domestic Product
- Planned Aggregate Expenditure
- Aggregate Demand and Supply
We also want to simulate changes overtime, so we want to parameterise our model over time. The main reason is to update certain exogenous parameters over time.
Base utility types¶
We need to setup some utiltiy datatypes. These will be building blocks for the different components of the model.
Coefficents¶
First we have Coefficient.*
which is how we represent our exogenous
parameters.
- In some cases for simiplicity we'll hardcode some with
Coefficient.Const
- In other cases we'll use
Coefficient.Series
While there is certainly a relationship and interactions between the data represented by these coefficients, we simply have to take observed values as inputs.
from collections import namedtuple
from dataclasses import dataclass, field
import numpy as np
class Coefficient:
class Base:
pass
@dataclass
class Const(Base):
value: float
def get(self, t):
return self.value
@dataclass
class Series(Base):
data: list[float]
def get(self, t):
return self.data[t]
Contribution¶
Next we have Contribution
. The 4 sector model for GDP has some functions that reference the Output parameter Ï’
(the final value for GDP), these are often multipliers, in order compute GDP we need to rearrange the equation, so returning these parts seperately allows us to do this, which is largely what Contribution
exists to do.
For example C
(our Consumption function) looks like this:
C = C0 + c(ϒ - T) - ɑr
T = T0 + tÏ’
With our Contribution
we would group it like so:
Contribution(value=C0 - ɑr - cT0, gdp_m=c(1 - t))
That way when we add up all the other components of GDP, we'll add all the mupltipliers together and subtract them from 1 to get and divide everything else by the result. You'll see this in the defintion of Economy
later.
@dataclass
class Contribution:
value: float
gdp_m: float
@property
def t(self):
return (self.value, self.gdp_m)
def apply(self, gdp):
return self.value + self.gdp_m * gdp
Lets build our model¶
Inflation Function (π)¶
This is based on the Adaptive Expectations of inflation. Where the previous periods inflation is added with a eplison
(ε) to represent shock.
π = π[t-1] + ε
@dataclass
class Inflation:
previous: Coefficient.Base
eplison: Coefficient.Base
def get(self, t):
return self.previous.get(t) + self.eplison.get(t)
Policy Reaction function¶
We use this to represent interest. This multiplies π
by a gamma ɣ
, to be honest I actually forget what that presents. Note there's a function for aggregate_demand_m
, this basically returns real interest with inflation lifted out the aggregate demand curve represents the relation between inflation and output, so it will be one of the axises so it's important to be able to lift it out.
r = r0 + ɣπ
r = r0 + ɣ(π[t-1] + ε)
r0
defines autonomous interest, which exists outside our model.É£
is a coefficient, which kind of represents policy response to inflation.
@dataclass
class RealInterest:
autonomous: Coefficient.Base
gamma: Coefficient.Base
inflation: Inflation = field(repr=False)
def aggregate_demand_m(self, t):
return (self.autonomous.get(t),self.gamma.get(t))
def get(self, t):
return self.autonomous.get(t) + self.gamma.get(t) * self.inflation.get(t)
Consumption Sector and the Tax Function¶
I'm defining this together as they are more or less coupled together. This where contribution comes in.
C = C0 + c(ϒ - T) - ɑr
T = T0 + tÏ’
C = C0 + cϒ - (cT0 + ɑr0 + ɑɣπ[t-1] + ɑɣε)
C0
represents nondiscretionary spending. Spending that was going to happen independly of growth.T0
represents taxiation not represented in the model, or at least taxs that we can't capture in our interaction term (so taxes that aren't necesary in response to consumption or output like GST or income tax).(Ï’ - T)
represents discretionary income.c
is the marginal perpensity to spend in relation to discretionary income.t
is the marginal tax rate, try not to think of it in terms of tax bracket. Note that it's an interaction term against outputÏ’
. It just represents the portion of tax that is in response to output.É‘
this represents the rate investment decreases in response to interest changes.
@dataclass
class Tax:
autonomous: Coefficient.Base
mpt: Coefficient.Base
def pae(self, t):
return Contribution(value=self.autonomous.get(t), gdp_m=self.mpt.get(t))
@dataclass
class ConsumptionSector:
autonomous: Coefficient.Base
mpc: Coefficient.Base
alpha: Coefficient.Base
tax: Tax
real_interest: RealInterest = field(repr=False)
def aggregate_demand(self, t):
a, b = self.real_interest.aggregate_demand_m(t)
tax = self.mpc.get(t) * self.tax.pae(t).value
c = self.autonomous.get(t)
alpha = self.alpha.get(t)
return (c - tax - alpha * a, alpha * b)
def pae(self, t):
mpc = self.mpc.get(t)
tv, tm = self.tax.pae(t).t
value = self.autonomous.get(t) - (mpc * tv + self.alpha.get(t) * self.real_interest.get(t))
return Contribution(value, mpc * (1 - tm))
Investment sector¶
This represents planned investment, note the negative relationship with real interest.
Ip = I0 - βr
Ip = I0 - β(r0 + ɣ(π[t-1] + ε))
Ip = I0 - βr0 - βɣπ[t-1] - βɣε
I0
is autonomous investment spending, it's an exogenous variable.β
this represents the rate investment decreases in response to interest changes.r
is our real interest
@dataclass
class InvestmentSector:
autonomous: Coefficient.Base
beta: Coefficient.Base
real_interest: RealInterest = field(repr=False)
def aggregate_demand(self, t):
a, b = self.real_interest.aggregate_demand_m(t)
i = self.autonomous.get(t)
beta = self.beta.get(t)
return (i - beta * a, beta * b)
def pae(self, t):
value = self.autonomous.get(t) - self.beta.get(t) * self.real_interest.get(t)
return Contribution(value, 0)
Government sector¶
This is mostly government spending.
G = G0
G0
is autonomous government spending, it's an exogenous variable.
@dataclass
class GovernmentSector:
autonomous: Coefficient.Base
def aggregate_demand(self, t):
return (self.autonomous.get(t), 0)
def pae(self, t):
return Contribution(self.autonomous.get(t), 0)
Foreign Sector¶
For the most part this is net exports, this also has a multipler on the output.
NX = X - mY
X
is exports.m
is perpensity to import.
@dataclass
class ForeignSector:
autonomous: Coefficient.Base
mpi: Coefficient.Base
def aggregate_demand(self, t):
return (self.autonomous.get(t), 0)
def pae(self, t):
return Contribution(self.autonomous.get(t), -self.mpi.get(t))
The Model for our Economy¶
@dataclass
class Economy:
c: ConsumptionSector
i: InvestmentSector
g: GovernmentSector
f: ForeignSector
def aggregate_demand(self, t, inflation):
m, a, b = self.components_aggregate_demand(t)
return (m * a) - (m * b * inflation)
def pae(self, t):
v, m = self.components_pae(t).t
return v / (1-m)
def components_pae(self, t):
cv, cm = self.c.pae(t).t
iv, im = self.i.pae(t).t
gv, gm = self.g.pae(t).t
fv, fm = self.f.pae(t).t
return Contribution(cv + iv + gv + fv, cm + im + gm + fm)
def components_aggregate_demand(self, t):
m = self.components_pae(t).gdp_m
ca, cb = self.c.aggregate_demand(t)
ia, ib = self.i.aggregate_demand(t)
ga, gb = self.g.aggregate_demand(t)
fa, fb = self.f.aggregate_demand(t)
return (m, (ca + ia + ga + fa), (cb + ib + gb + fb))
Setup up Fictionary Economy¶
In our simulation we're going to feed some values to some of the coffientents to simulate an economy responding to an inflationary shock (note this incredibly contrived, and so this won't really reflect the true nature in which our coefficients would change overtime in response to other factors.
Setup our coefficients.¶
We'll have 11 periods represented by the numbers 0 through to 10
r_0
represents changes in autonomous real interest.previous_inflation
represents inflaiton from the previous period.eplison
represents flucation in the response to inflation.
periods = list(range(0, 11))
N = len(periods)
r_0 = [0.1, 0.1, 0.1, 0.1, 0.3, 0.3, 0.2, 0.2, 0.15, 0.1, 0.1]
previous_inflation = [0.1, 0.1, 0.2, 0.3, 0.2, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1]
eplison = [0, 0, 0, 0.5, 0.4, 0.3, 0.2, 0.2, 0.1, 0, 0]
Wire up our model¶
inflation = Inflation(
previous=Coefficient.Series(previous_inflation),
eplison=Coefficient.Series(eplison),
)
real_interest = RealInterest(
autonomous=Coefficient.Series(r_0),
gamma=Coefficient.Const(1),
inflation=inflation,
)
tax = Tax(
autonomous=Coefficient.Const(3000),
mpt=Coefficient.Const(0.1),
)
consumption = ConsumptionSector(
autonomous=Coefficient.Const(1800),
mpc=Coefficient.Const(0.6),
alpha=Coefficient.Const(5000),
real_interest=real_interest,
tax=tax,
)
investment = InvestmentSector(
autonomous=Coefficient.Const(9000),
beta=Coefficient.Const(5000),
real_interest=real_interest,
)
government = GovernmentSector(Coefficient.Const(1500))
foreign = ForeignSector(Coefficient.Const(100), Coefficient.Const(0.1))
country_a = Economy(c=consumption, i=investment, g=government, f=foreign)
Inspect the shape of our model¶
- Aggregate Demand should be
AD = (0.44)(9600) - (0.44)(10000)(Ï€)
- PAE should be
PAE = 8600 + 0.44Ï’
from pprint import pprint
pprint(country_a)
pprint(dict(
pae_components=country_a.components_pae(0),
components_aggregate_demand=country_a.components_aggregate_demand(0),
))
Economy(c=ConsumptionSector(autonomous=Coefficient.Const(value=1800), mpc=Coefficient.Const(value=0.6), alpha=Coefficient.Const(value=5000), tax=Tax(autonomous=Coefficient.Const(value=3000), mpt=Coefficient.Const(value=0.1))), i=InvestmentSector(autonomous=Coefficient.Const(value=9000), beta=Coefficient.Const(value=5000)), g=GovernmentSector(autonomous=Coefficient.Const(value=1500)), f=ForeignSector(autonomous=Coefficient.Const(value=100), mpi=Coefficient.Const(value=0.1))) {'components_aggregate_demand': (0.44000000000000006, 9600.0, 10000), 'pae_components': Contribution(value=8600.0, gdp_m=0.44000000000000006)}
Run and Visualise our model¶
from IPython.display import HTML
from matplotlib import animation, pyplot as plt
fig, ax = plt.subplots(1, 5, figsize=(50, 12))
fig.suptitle('Country A PAE in respect to inflation shock', fontsize=48, y=0.98)
output = [country_a.pae(t)/N for t in periods]
ax[0].set_title('Change in Ï’ over time', fontsize=24)
ax[0].plot(periods, output)
ax[1].set_title('Ï’ over time', fontsize=24)
ax[1].plot(periods, np.cumsum(output))
ax[2].set_title('Real Interest over time', fontsize=24)
ax[2].plot(periods, np.cumsum([real_interest.get(t) for t in periods]), label='r')
ax[2].plot(periods, np.cumsum([inflation.get(t) for t in periods]), label='Ï€')
ax[2].legend()
ax[3].set_title('Change in Interest & Inflation over time', fontsize=24)
ax[3].plot(periods, [real_interest.get(t) for t in periods], label='r')
ax[3].plot(periods, [inflation.get(t) for t in periods], label='Ï€')
ax[3].plot(periods, r_0, label='râ‚€')
ax[3].plot(periods, eplison, label='ε')
ax[3].legend()
ax[4].set_title('Sectors', fontsize=24)
ax[4].plot(periods, [consumption.pae(t).apply(output[t])/N for t in periods], label='C')
ax[4].plot(periods, [investment.pae(t).apply(output[t])/N for t in periods], label='I')
ax[4].plot(periods, [government.pae(t).apply(output[t])/N for t in periods], label='G')
ax[4].plot(periods, [foreign.pae(t).apply(output[t])/N for t in periods], label='NX')
ax[4].legend()
plt.show()
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
inflation_points = [i/100 for i in (range(0, 101))]
ad_curve = lambda t: [country_a.aggregate_demand(t, i) for i in inflation_points]
as_curve = lambda t: [inflation.get(t) for _ in inflation_points]
equilibrium = lambda t: [country_a.aggregate_demand(t, 0)*(1-inflation.get(t)) for _ in inflation_points]
offset_screen = np.full((101,), -1)
ad_line_shadow, = ax.plot(offset_screen, inflation_points,
color='b', linestyle=(0, (2, 3)))
as_line_shadow, = ax.plot(ad_curve(0), offset_screen,
color='r', linestyle=(0, (1, 5)))
eq_line_shadow, = ax.plot(offset_screen, inflation_points,
color='g', linestyle=(0, (1, 5)))
ad_line, = ax.plot(ad_curve(0), inflation_points, color='b', label='Aggregate Demand')
as_line, = ax.plot(ad_curve(0), as_curve(0), color='r', label='Aggregate Supply')
eq_line, = ax.plot(equilibrium(0), inflation_points, color='g', label='Equilibrium')
fig.suptitle('\nAggregated Demand and supply Simulation', fontsize=24, y=0.98)
fig.subplots_adjust(top=0.85)
ax.set_xlim(0, country_a.aggregate_demand(0, 0))
ax.set_ylim(0, 1)
ax.set_ylabel('Ï€', fontsize=24)
ax.set_xlabel('Ï’', fontsize=24)
legend = ax.legend(loc='lower left')
display(HTML('<hr>'))
plt.show()
display(HTML('<hr>'))
def update(frame):
ad_line.set_xdata(ad_curve(frame))
as_line.set_ydata(as_curve(frame))
eq_line.set_xdata(equilibrium(frame))
if frame:
eq_line_shadow.set_xdata(equilibrium(frame-1))
ad_line_shadow.set_xdata(ad_curve(frame-1))
as_line_shadow.set_ydata(as_curve(frame-1))
else:
eq_line_shadow.set_xdata(offset_screen)
ad_line_shadow.set_xdata(offset_screen)
as_line_shadow.set_ydata(offset_screen)
return legend,
ani = animation.FuncAnimation(fig, update, frames=10, blit=True, interval=200)
HTML(ani.to_jshtml())