Skip to content

Instantly share code, notes, and snippets.

@dmarx
Created January 30, 2024 23:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmarx/8172ea3fdaf48b21b12ac01af62bd07d to your computer and use it in GitHub Desktop.
Save dmarx/8172ea3fdaf48b21b12ac01af62bd07d to your computer and use it in GitHub Desktop.
Hawkes Process Simulation
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()
@dmarx
Copy link
Author

dmarx commented Jan 30, 2024

Screenshot 2024-01-30 at 2 46 30 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment