The "20-Year" Stochastic Pulse
A unique feature of this model is the implementation of a stochastic shock trigger. Based on historical frequency data (4–5 major events per century), the model introduces a "drastic shift" every 20 years. This forces the agents to adapt to sudden changes—such as a $200,000 drop in property value or a $25,000 income reduction—to measure the "Abyss" or the point of no return for vulnerable populations.
Model Constraints & Future Scope
While current hardware limited the simulation to a sample size of 200 high-fidelity agents, the logic is designed to be linearly scalable. Future iterations aim to utilize cloud-based high-performance computing (HPC) to simulate Seattle's full population and integrate more granular "Quality of House" variables (Levels 1–2) to reflect gentrification and urban decay patterns.
import agentpy as ap
import numpy as np
import random
import pandas as pd
class Citizen(ap.Agent):
def setup(self):
if np.random.random() <= .078:
self.status = 1
else:
self.status = 0 # 0 = buyer, 1 = seller
self.income = np.random.uniform(39000, 170000) # Random income between $50,000 and $150,000
self.age = np.random.uniform(20, 70) # age randomized between 20 and 70
self.savings = (self.age - 20) * .1 * self.income # initial savings based on age of buyer
self.budget = self.savings # Budget is savings
self.unemployment_prob = 0.031 # Probability of becoming unemployed
self.quality = np.random.uniform(1, 2)
self.price = 500000 * self.quality # Median house price with some variability
self.house = 1 # do you have house
self.homeless = 0
def step(self):
self.age += 1
self.savings += .2 * self.income
self.budget = self.savings
if (self.unemployment_prob >= np.random.random() or self.age > 70) and self.house != 0:
self.status = 1
self.income = 0 # Set income to zero if buyer becomes unemployed and dies from old age
if self.age >= 70 and self.house == 0: # create new citizen after old one dies
self.status = 0
self.age = 20
self.savings = 0
self.income = np.random.uniform(39000, 170000)
if self.savings >= 500000:
self.status = 0
if self.status == 1:
self.house = 0
if self.income == 0 and self.status == 0:
self.income = np.random.uniform(50000, 150000)
#if np.random.random() <= .05:
#self.status = 1
if self.model.pool and self.status == 0:
potential_sellers = [s for s in self.model.pool if s.status == 1]
if potential_sellers:
chosen_seller = np.random.choice(potential_sellers)
if chosen_seller.price <= self.budget:
self.savings -= chosen_seller.price
self.model.pool.remove(chosen_seller)
self.model.transactions += 1
self.model.total_spent += chosen_seller.price
self.house = 1
if self.house == 0:
if np.random.random() <= .45:
self.homeless = 1
class HousingMarketModel(ap.Model):
def setup(self):
self.i = 0
self.pop = 100
self.pool = ap.AgentList(self, self.pop, Citizen)
self.transactions = 0
self.total_spent = 0
def step(self):
self.pool.step()
# self.pool.shuffle()
self.sellersLeft = [s for s in self.model.pool if s.status == 1]
self.homeless = [p for p in self.model.pool if p.homeless == 1]
print(f" {len(self.sellersLeft)}, {len(self.homeless)}, {(self.pop)}\t")
self.pop = round(self.pop * 1.03)
self.pool = ap.AgentList(self, self.pop, Citizen)
#define parameters
parameters = {
'steps': 50,
'seed': 12
}
# Run the model
model = HousingMarketModel(parameters)
results = model.run()
# Calculate number of houses left on the market
houses_left = len(model.sellersLeft)
print("Number of houses left on the market:", houses_left)
# finding regressions for median income, unemployment, and houseprice
training_data = pd.read_csv('q2_data_v2.csv')
median_income_regression = np.polyfit(training_data['year'], training_data['median_income'], 2)
unemployment_regression = np.polyfit(training_data['year'], training_data['unemployment_rate'], 2)
house_price_regression = np.polyfit(training_data['year'], training_data['house_price'], 2)
median_income = lambda year: np.polyval(median_income_regression, year)
unemployment = lambda year: np.polyval(unemployment_regression, year)
house_price = lambda year: np.polyval(house_price_regression, year)
class Citizen(ap.Agent):
def setup(self):
self.years = 2022
if np.random.random() <= .078:
self.status = 1
else:
self.status = 0 # 0 = buyer, 1 = seller
self.income = np.random.uniform(39000, 170000) # Random income between $39,000 and $170,000
self.age = np.random.uniform(20, 70) # age randomized between 20 and 70
self.savings = (self.age - 20) * .1 * self.income # initial savings based on age of buyer
self.budget = self.savings # Budget is savings
self.unemployment_prob = .01 * unemployment(self.years) # Probability of becoming unemployed
self.quality = np.random.uniform(1, 2)
self.price = 500000 * self.quality # Median house price with some variability
self.house = 1 # do you have house
self.homeless = 0
def step(self):
self.age += 1
self.years += 1
self.unemployment_prob = .01 * unemployment(self.years)
self.savings += .2 * self.income
self.budget = self.savings
if (self.unemployment_prob >= np.random.random() or self.age > 70) and self.house != 0:
self.status = 1
self.income = 0 # Set income to zero if buyer becomes unemployed and dies from old age
if self.age >= 70 and self.house == 0: # create new citizen after old one dies
self.status = 0
self.age = 20
self.savings = 0
self.income = np.random.uniform(39000, 170000)
if self.savings >= 500000:
self.status = 0
if self.status == 1:
self.house = 0
if self.income == 0 and self.status == 0:
self.income = np.random.uniform(39000, 170000)
#if np.random.random() <= .05:
#self.status = 1
if self.model.pool and self.status == 0:
potential_sellers = [s for s in self.model.pool if s.status == 1]
if potential_sellers:
chosen_seller = np.random.choice(potential_sellers)
if chosen_seller.price <= self.budget:
self.savings -= chosen_seller.price
self.model.pool.remove(chosen_seller)
self.house = 1
if self.house == 0:
if np.random.random() <= .6:
self.homeless = 1
class HousingMarketModel(ap.Model):
def setup(self):
self.i = 0
self.pop = 200
self.pool = ap.AgentList(self, self.pop, Citizen)
def step(self):
self.pool.step()
# self.pool.shuffle()
self.sellersLeft = [s for s in self.model.pool if s.status == 1]
self.homeless = [p for p in self.model.pool if p.homeless == 1]
print(f" {len(self.sellersLeft)}, {len(self.homeless)}, {(self.pop)}\t")
self.pop = round(self.pop * 1.01024)
self.pool = ap.AgentList(self, self.pop, Citizen)
#define parameters
parameters = {
'steps': 50,
'seed': 12
}
# Run the model
model = HousingMarketModel(parameters)
results = model.run()
# Calculate number of houses left on the market
houses_left = len(model.sellersLeft)
print("Number of houses left on the market:", houses_left)
"""Agent Based Model for natural disaster in Seattle, 20 years after the model starts."""
class Citizen(ap.Agent):
def setup(self):
self.years = 2022
self.priceConstant = 500000
self.incomeRange = [39000, 170000]
if np.random.random() <= .078:
self.status = 1
else:
self.status = 0 # 0 = buyer, 1 = seller
self.income = np.random.uniform(self.incomeRange[0], self.incomeRange[1]) # Random income between $39,000 and $170,000
self.age = np.random.uniform(20, 70) # age randomized between 20 and 70
self.savings = (self.age - 20) * .1 * self.income # initial savings based on age of buyer
self.budget = self.savings # Budget is savings
self.unemployment_prob = .01 * unemployment(self.years) # Probability of becoming unemployed
self.quality = np.random.uniform(1, 2)
self.price = self.priceConstant * self.quality # Median house price with some variability
self.house = 1 # do you have house
self.homeless = 0
def step(self):
self.priceConstant = self.model.priceConstant
self.incomeRange = self.model.incomeRange
self.age += 1
self.years += 1
self.unemployment_prob = .01 * unemployment(self.years)
self.savings += .2 * self.income
self.budget = self.savings
if (self.unemployment_prob >= np.random.random() or self.age > 70) and self.house != 0:
self.status = 1
self.income = 0 # Set income to zero if buyer becomes unemployed and dies from old age
if self.age >= 70 and self.house == 0: # create new citizen after old one dies
self.status = 0
self.age = 20
self.savings = 0
self.income = np.random.uniform(self.incomeRange[0], self.incomeRange[1])
if self.savings >= 500000:
self.status = 0
if self.status == 1:
self.house = 0
if self.income == 0 and self.status == 0:
self.income = np.random.uniform(self.incomeRange[0], self.incomeRange[1])
#if np.random.random() <= .05:
#self.status = 1
if self.model.pool and self.status == 0:
potential_sellers = [s for s in self.model.pool if s.status == 1]
if potential_sellers:
chosen_seller = np.random.choice(potential_sellers)
if chosen_seller.price <= self.budget:
self.savings -= chosen_seller.price
self.model.pool.remove(chosen_seller)
self.house = 1
if self.house == 0:
if np.random.random() <= .6:
self.homeless = 1
class HousingMarketModel(ap.Model):
def setup(self):
self.years = 2022
self.i = 0
self.pop = 200
self.priceConstant = 500000
self.incomeRange = [39000, 170000]
self.unemployment_const = .01
self.pool = ap.AgentList(self, self.pop, Citizen)
def step(self):
if self.years == 2042:
self.priceConstant = 400000 # housing prices decreased from disaster
self.incomeRange = [30000, 145000] # income decreases from disaster
elif self.years > 2042:
self.priceConstant = 100000 * (1 - .8 ** (self.years - 2042)) + 400000 # housing prices recovers over time
self.incomeRange = [9000 * (1 - .95 ** (self.years - 2042)) + 30000, 25000 * (1 - .9 ** (self.years - 2042)) + 145000] # income recovers over time
self.years += 1
self.pool.step()
# self.pool.shuffle()
self.sellersLeft = [s for s in self.model.pool if s.status == 1]
self.homeless = [p for p in self.model.pool if p.homeless == 1]
print(f" {len(self.sellersLeft)}, {len(self.homeless)}, {(self.pop)}\t")
self.pop = round(self.pop * .99) # natural disaster makes less people come to the area as the housing is less desirable
self.pool = ap.AgentList(self, self.pop, Citizen)
#define parameters
parameters = {
'steps': 50,
'seed': 12
}
# Run the model
model = HousingMarketModel(parameters)
results = model.run()
# Calculate number of houses left on the market
houses_left = len(model.sellersLeft)
print("Number of houses left on the market:", houses_left)
"""Agent Based Model for economic recessions in Seattle, 20 years after the model starts."""
class Citizen(ap.Agent):
def setup(self):
self.years = 2022
if np.random.random() <= .078: # 7.8% of intial population are selling
self.status = 1
else:
self.status = 0 # 0 = buyer, 1 = seller
self.income = np.random.uniform(self.model.incomeLow, self.model.incomeHigh) # Random income between $39,000 and $170,000
self.age = np.random.uniform(20, 70) # age randomized between 20 and 70
self.savings = (self.age - 20) * .1 * self.income # initial savings based on age of buyer
self.budget = self.savings # Budget is savings
self.unemployment_prob = .01 * 3 # unemployment(self.years) # Probability of becoming unemployed
self.quality = np.random.uniform(1, 2)
self.price = self.model.priceConstant * self.quality # Median house price with some variability
self.house = 1 # do you have house
self.homeless = 0
def step(self):
self.years = self.model.years
self.age += 1
self.unemployment_prob = self.model.unemployment_const * unemployment(self.years)
self.savings += .2 * self.income
self.budget = self.savings
if (self.unemployment_prob >= np.random.random() or self.age > 70) and self.house != 0:
self.status = 1
self.income = 0 # Set income to zero if buyer becomes unemployed and dies from old age
if self.age >= 70 and self.house == 0: # create new citizen after old one dies
self.status = 0
self.age = 20
self.savings = 0
self.income = np.random.uniform(self.model.incomeLow, self.model.incomeHigh)
if self.savings >= 500000:
self.status = 0
if self.status == 1:
self.house = 0
if self.income == 0 and self.status == 0:
self.income = np.random.uniform(self.model.incomeLow, self.model.incomeHigh)
#if np.random.random() <= .05:
#self.status = 1
if self.model.pool and self.status == 0:
potential_sellers = [s for s in self.model.pool if s.status == 1]
if potential_sellers:
chosen_seller = np.random.choice(potential_sellers)
if chosen_seller.price <= self.budget:
self.savings -= chosen_seller.price
self.model.pool.remove(chosen_seller)
self.house = 1
if self.house == 0:
if np.random.random() <= .6:
self.homeless = 1
class HousingMarketModel(ap.Model):
def setup(self):
self.years = 2022
self.i = 0
self.pop = 200
self.priceConstant = 500000
self.incomeLow = 39000
self.incomeHigh = 170000
self.unemployment_const = .01
self.pool = ap.AgentList(self, self.pop, Citizen)
def step(self):
if self.years == 2042:
self.priceConstant = 550000 # housing prices increases from recession
self.incomeLow = 30000
self.incomeHigh = 145000 # income decreases from recession
self.unemployment_const = .015 # unemployment increases from recession
elif self.years > 2042:
self.priceConstant = (-50000) * (1 - .9 ** (self.years - 2042)) + 550000 # housing prices recovers over time
self.incomeLow = 9000 * (1 - .95 ** (self.years - 2042)) + 30000 # income recovers over time
self.incomeHigh = 25000 * (1 - .9 ** (self.years - 2042)) + 145000
self.unemployment_const = (-.003) * (1 - .9 ** (self.years - 2042)) + .013 #unemployment recovers over time
self.years += 1
self.pool.step()
# self.pool.shuffle()
self.sellersLeft = [s for s in self.model.pool if s.status == 1]
self.homeless = [p for p in self.model.pool if p.homeless == 1]
print(f" {len(self.sellersLeft)}, {len(self.homeless)}, {(self.pop)}\t")
self.pop = round(self.pop * 1.01025)
self.pool = ap.AgentList(self, self.pop, Citizen)
#define parameters
parameters = {
'steps': 50,
'seed': 12
}
# Run the model
model = HousingMarketModel(parameters)
results = model.run()
# Calculate number of houses left on the market
houses_left = len(model.sellersLeft)
print("Number of houses left on the market:", houses_left)
class Citizen(ap.Agent):
def setup(self):
self.years = 2022
if np.random.random() <= .078: # 7.8% of intial population are selling
self.status = 1
else:
self.status = 0 # 0 = buyer, 1 = seller
self.income = np.random.uniform(self.model.incomeLow, self.model.incomeHigh) # Random income between $39,000 and $170,000
self.age = np.random.uniform(20, 70) # age randomized between 20 and 70
self.savings = (self.age - 20) * .1 * self.income # initial savings based on age of buyer
self.budget = self.savings # Budget is savings
self.unemployment_prob = .01 * 3 # unemployment(self.years) # Probability of becoming unemployed
self.quality = np.random.uniform(1, 2)
self.price = self.model.priceConstant * self.quality # Median house price with some variability
self.house = 1 # do you have house
self.homeless = 0
def step(self):
self.years = self.model.years
self.age += 1
self.unemployment_prob = self.model.unemployment_const * unemployment(self.years)
self.savings += .2 * self.income
self.budget = self.savings
if (self.unemployment_prob >= np.random.random() or self.age > 70) and self.house != 0:
self.status = 1
self.income = 0 # Set income to zero if buyer becomes unemployed and dies from old age
if self.age >= 70 and self.house == 0: # create new citizen after old one dies
self.status = 0
self.age = 20
self.savings = 0
self.income = np.random.uniform(self.model.incomeLow, self.model.incomeHigh)
if self.savings >= 500000:
self.status = 0
if self.status == 1:
self.house = 0
if self.income == 0 and self.status == 0:
self.income = np.random.uniform(self.model.incomeLow, self.model.incomeHigh)
#if np.random.random() <= .05:
#self.status = 1
if self.model.pool and self.status == 0:
potential_sellers = [s for s in self.model.pool if s.status == 1]
if potential_sellers:
chosen_seller = np.random.choice(potential_sellers)
if chosen_seller.price <= self.budget:
self.savings -= chosen_seller.price
self.model.pool.remove(chosen_seller)
self.house = 1
if self.house == 0:
if np.random.random() <= .8: # less housing available, so more chance of becoming homeless after selling a house
self.homeless = 1
class HousingMarketModel(ap.Model):
def setup(self):
self.years = 2022
self.i = 0
self.pop = 100 # decreased initial sample population because my pc was overheating
self.priceConstant = 500000
self.incomeLow = 39000
self.incomeHigh = 170000
self.unemployment_const = .01
self.pool = ap.AgentList(self, self.pop, Citizen)
def step(self):
if self.years == 2042:
self.priceConstant = 600000 # housing prices increases from migrant populations
self.incomeLow = 30000
self.incomeHigh = 145000 # income decreases from more people wanting jobs
self.unemployment_const = .02 # unemployment increases from migrant populations
elif self.years > 2042:
self.priceConstant = (-50000) * (1 - .9 ** (self.years - 2042)) + 550000 # housing prices recovers over time
self.incomeLow = 9000 * (1 - .85 ** (self.years - 2042)) + 30000 # income recovers over time
self.incomeHigh = 25000 * (1 - .85 ** (self.years - 2042)) + 145000
self.unemployment_const = (-.002) * (1 - .8 ** (self.years - 2042)) + .012 #unemployment recovers over time
self.years += 1
self.pool.step()
# self.pool.shuffle()
self.sellersLeft = [s for s in self.model.pool if s.status == 1]
self.homeless = [p for p in self.model.pool if p.homeless == 1]
print(f" {len(self.sellersLeft)}, {len(self.homeless)}, {(self.pop)}\t")
self.pop = round(self.pop * 1.03) # migrants greatly increases populations
self.pool = ap.AgentList(self, self.pop, Citizen)
#define parameters
parameters = {
'steps': 50,
'seed': 12
}
# Run the model
model = HousingMarketModel(parameters)
results = model.run()
# Calculate number of houses left on the market
houses_left = len(model.sellersLeft)
print("Number of houses left on the market:", houses_left)
class Citizen(ap.Agent):
def setup(self):
self.years = 2022
if np.random.random() <= .078:
self.status = 1
else:
self.status = 0 # 0 = buyer, 1 = seller
self.income = np.random.uniform(39000, 170000) # Random income between $39,000 and $170,000
self.age = np.random.uniform(20, 70) # age randomized between 20 and 70
self.savings = (self.age - 20) * .1 * self.income # initial savings based on age of buyer
self.budget = self.savings # Budget is savings
self.unemployment_prob = .01 * unemployment(self.years) # Probability of becoming unemployed
self.quality = np.random.uniform(1, 2)
self.price = 500000 * self.quality # Median house price with some variability
self.house = 1 # do you have house
self.homeless = 0
def step(self):
self.age += 1
self.years += 1
self.unemployment_prob = .01 * unemployment(self.years)
self.savings += .2 * self.income
self.budget = self.savings
if (self.unemployment_prob >= np.random.random() or self.age > 70) and self.house != 0:
self.status = 1
self.income = 0 # Set income to zero if buyer becomes unemployed and dies from old age
if self.age >= 70 and self.house == 0: # create new citizen after old one dies
self.status = 0
self.age = 20
self.savings = 0
self.income = np.random.uniform(39000, 170000)
if self.savings >= 500000:
self.status = 0
if self.status == 1:
self.house = 0
if self.income == 0 and self.status == 0:
self.income = np.random.uniform(39000, 170000)
#if np.random.random() <= .05:
#self.status = 1
if self.model.pool and self.status == 0:
potential_sellers = [s for s in self.model.pool if s.status == 1]
if potential_sellers:
chosen_seller = np.random.choice(potential_sellers)
if chosen_seller.price <= self.budget:
self.savings -= chosen_seller.price
self.model.pool.remove(chosen_seller)
self.house = 1
if self.house == 0:
if np.random.random() <= .6:
self.homeless = 1
class HousingMarketModel(ap.Model):
def setup(self):
self.i = 0
self.pop = 200
self.pool = ap.AgentList(self, self.pop, Citizen)
def step(self):
self.pool.step()
# self.pool.shuffle()
self.sellersLeft = [s for s in self.model.pool if s.status == 1]
self.homeless = [p for p in self.model.pool if p.homeless == 1]
print(f" {len(self.sellersLeft)}, {len(self.homeless)}, {(self.pop)}\t")
self.pop = round(self.pop * 1.01024)
self.pool = ap.AgentList(self, self.pop, Citizen)
#define parameters
parameters = {
'steps': 50,
'seed': 12
}
# Run the model
model = HousingMarketModel(parameters)
results = model.run()
# Calculate number of houses left on the market
houses_left = len(model.sellersLeft)
print("Number of houses left on the market:", houses_left)
\end{minted}