Here is my solution to "What is the simplest practical method to implement?" using python, specifically numpy, scipy and tick.
One modification is that I set the exponential kernel such that alpha x beta x exp (-beta (t - ti)), to coincide with how tick defines exponential kernels:
https://x-datainitiative.github.io/tick/modules/generated/tick.hawkes.HawkesExpKern.html#tick.hawkes.HawkesExpKern
I'm assuming the reader might not be familiar with python.
Import the relevant libraries:
import numpy as np
from scipy.optimize import minimize
from tick.hawkes import SimuHawkesExpKernels
Define the recursive function (R(i) in the question above) which returns an array the same size as the number of events:
def _recursive(timestamps, beta):
r_array = np.zeros(len(timestamps))
for i in range(1, len(timestamps)):
r_array[i] = np.exp(-beta * (timestamps[i] - timestamps[i - 1])) * (1 + r_array[i - 1])
return r_array
Define the log likelihood function specifying the various parameters:
def log_likelihood(timestamps, mu, alpha, beta, runtime):
r = _recursive(timestamps, beta)
return -runtime * mu + alpha * np.sum(np.exp(-beta * (runtime - timestamps)) - 1) + \
np.sum(np.log(mu + alpha * beta * r))
Simulate some Hawkes data using the tick library (or some other means):
m = 0.5
a = 0.2
b = 0.3
rt = 1000
simu = SimuHawkesExpKernels([[a]], b, [m], rt, seed=0)
simu.simulate()
t = simu.timestamps[0]
Scipy's minimize function is probably the most common python optimisation for scalar functions. It expects a function with only two sets of parameters; those you want to minimise and those which are fixed. There are various possible minimisation methods, I am using the default which is L-BFGS-B for bounded problems and is a quasi-newton Method.
Note that I should have started with a brute search first but the question asked for the simplest practical method. I could have also split into a minimisation over mu and alpha and then used simulated annealing over beta since the log-logarithm is convex over mu and alpha only.
Define a new function to be used by the minimize function and returns the negative log-likelihood:
def crit(params, *args):
mu, alpha, beta = params
timestamps, runtime = args
return -log_likelihood(timestamps, mu, alpha, beta, runtime)
Call the minimize function and set the bounds for m,a and b to be positive:
minimize(crit, [m + 0.1, a + 0.1, b+ 0.1], args=(t, rt), bounds=((1e-10, None), (1e-10, None), (1e-10, None),))
Which gives estimates of m,a,b: array([0.43835767, 0.25823306, 0.14769243])