Created
January 30, 2024 23:00
-
-
Save dmarx/8172ea3fdaf48b21b12ac01af62bd07d to your computer and use it in GitHub Desktop.
Hawkes Process Simulation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import scipy as sp | |
from scipy.stats import poisson, expon | |
import matplotlib.pyplot as plt | |
import math | |
import random | |
random.seed(42) | |
def hawkes_process_simulation( | |
horizon = 100, | |
decay_rate = .995, | |
intensity_bump_per_event = 1, | |
max_intensity = 100, | |
): | |
""" | |
Simulates event arrival times for a self-exciting point-process, | |
i.e. simulating traffic from "viral" attention. | |
This is basically a fancy poisson process. What makes it special is that the rate of the process is not fixed. | |
Instead, the rate is modeled by an "intensity function". Whenever a new event is observed, the intensity function | |
experiences a bump. While no events are happening, the intensity function experiences exponential decay. This manifests | |
occasional random spikes of accelerating event rates. | |
Algorithm modified from: https://scse.d.umn.edu/sites/scse.d.umn.edu/files/obral_master.pdf | |
Parameters: | |
horizon: duration of simulation. event times will be generated in the range [0,horizon] with a presumptive event at t=0. | |
decay_rate: rate at which the intensity function decays in absence of events | |
intensity_bump_per_event: amount by which intensity function increases when a new event is observed | |
max_intensity: this can be finicky to parameterize, so I added the ability to threshold the intensity function | |
Returns: | |
event_history: list of times at which simulated events occur | |
excitation_history: simulated intensity function associated with the generated events | |
""" | |
def exp_decay(t, decay_rate=0.5, v0=intensity_bump_per_event): | |
return v0 * math.exp(-t * decay_rate) | |
excitation_history = [1] | |
event_history = [0] | |
while event_history[-1] < horizon: | |
# sample new event interval | |
excitation = excitation_history[-1] | |
poisson_rate = 1/min(excitation, max_intensity) | |
time_to_candidate_event = expon.rvs(loc=0, scale=poisson_rate, size=1)[0] | |
#excitation_at_candidate = exp_decay(t=time_to_candidate_event, decay_rate=decay_rate, v0=excitation) | |
decayed_excitation = exp_decay(t=time_to_candidate_event, decay_rate=decay_rate, v0=excitation) | |
# "thinning" via rejection sampling | |
if random.random() < decayed_excitation: | |
# accept event | |
t_prev = event_history[-1] | |
t = t_prev + time_to_candidate_event | |
event_history.append(t) | |
# exp decay self-excitation | |
excitation = decayed_excitation + intensity_bump_per_event | |
excitation_history.append(excitation) | |
return event_history, excitation_history | |
####### | |
import seaborn as sns | |
for i in range(4): | |
event_history, excitation_history = hawkes_process_simulation(horizon=100) | |
#plt.plot(event_history, excitation_history, label=f"n={len(event_history)}") | |
sns.kdeplot(event_history, bw_adjust=.2, label=f"n={len(event_history)}") | |
#sns.rugplot(event_history) | |
plt.title("Event Density for Hawkes Process Simulations.\n`n`= # events simulated") | |
plt.legend() | |
plt.show() |
Author
dmarx
commented
Jan 30, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment