Boltzmann wealth model
A simple model for wealth inequality
Here we will explore and calibrate a simple model of agents exchanging wealth. The agents start with a Bernoulli distributed wealth. Then each agent with one unit of money or more gives one unit of wealth to another random agents encountered, unless the ratio of the agents wealths is above a certain threshold. This model is adapted from this source.
Note that this notebook requires the installation of the mesa library: you can install it through pip install mesa
.
import numpy as np
import matplotlib.pyplot as plt
from models.economics.boltzmann_wealth import BoltzmannWealthModel, compute_gini
model = BoltzmannWealthModel(
num_agents=50, width=5, height=5, generosity_ratio=2.0, mean_init_wealth=10
)
model.run_model(1000)
agent_wealths = [agent.wealth for agent in model.schedule.agents]
plt.hist(agent_wealths)
plt.xlabel("wealth")
plt.ylabel("number of agents")
compute_gini(model)
plt.figure(figsize=(5, 5))
s_w = sorted(agent_wealths)
X_lorenz = np.cumsum(s_w) / np.sum(s_w)
X_lorenz = np.insert(X_lorenz, 0, 0)
X_lorenz[0], X_lorenz[-1]
plt.plot(
np.arange(X_lorenz.size) / (X_lorenz.size - 1),
X_lorenz,
)
plt.plot(
np.arange(X_lorenz.size) / (X_lorenz.size - 1),
np.linspace(0, 1, len(X_lorenz)),
"k--",
)
plt.xlabel("fraction of (sorted) population")
plt.ylabel("fraction of wealth")
plt.plot(model.datacollector.get_model_vars_dataframe())
Calibrate the model on the Italian Gini Index
In 2017 the Italian Gini index was 0.36 (World Bank estimate)
# real GDP of the italian economy
italian_data = 0.36 * np.ones((100, 1))
# wrapper of the ABM with a specific signature
def boltzmann_model(theta, N, seed=None):
model = BoltzmannWealthModel(
num_agents=100,
width=10,
height=10,
generosity_ratio=theta[0],
mean_init_wealth=5,
)
# burn-in phase of 200 steps
model.run_model(200 + N)
data = model.datacollector.get_model_vars_dataframe().to_numpy()
# discard initialization and burn-in phase of 200 steps
return data[201:]
boltzmann_model([1], N=5, seed=None)
# Euclidean distance between series
from black_it.loss_functions.minkowski import MinkowskiLoss
loss = MinkowskiLoss(p=2)
# choose samplers
from black_it.samplers.halton import HaltonSampler
from black_it.samplers.random_forest import RandomForestSampler
halton = HaltonSampler(batch_size=4)
forest = RandomForestSampler(batch_size=4)
# reasonable range of parameter values
parameters_bounds = np.array([[0.01, 100.0]]).T
parameters_precision = [
0.01,
]
# initialize calibrator
from black_it.calibrator import Calibrator
cal = Calibrator(
model=boltzmann_model,
samplers=[halton, forest],
loss_function=loss,
parameters_bounds=parameters_bounds,
parameters_precision=parameters_precision,
real_data=italian_data,
ensemble_size=3,
)
# calibrate for 5 epochs
params, losses = cal.calibrate(5)
# optimal parameter found
params[0]
min_idx = np.argmin(cal.losses_samp)
print(min_idx)
max_idx = np.argmax(cal.losses_samp)
print(max_idx)
plt.figure()
for i, simulated_series in enumerate(cal.series_samp[:, 0, :, 0]):
if i in (max_idx,):
continue
plt.plot(simulated_series, color="orange", alpha=0.2)
plt.plot(
cal.series_samp[min_idx, 0, :, 0],
color="green",
label="optimal simulated series",
alpha=0.9,
linewidth=3,
)
plt.plot(italian_data, "k--", label="reference value", alpha=0.9)
plt.xlabel("time step")
plt.ylabel("Gini index")
plt.legend()
plt.ylim(0, 0.6)