pointprocesses/temporal/
hawkes.rs

1//! Implements the Hawkes self-exciting jump process.
2use super::traits::*;
3use rand::prelude::*;
4
5use ndarray::prelude::*;
6
7use crate::poisson::{PoissonProcess, VariablePoissonProcess};
8
9
10/// Kernel $g$ for the Hawkes process.
11pub trait Kernel {
12    fn eval(&self, t: f64) -> f64;
13}
14
15/// The Hawkes process is a self-exciting point process:
16/// the intensity process is stochastic and defined by
17/// $$ \lambda_t = \lambda_0(t) + \int_0^t g(t-s) dN_s $$
18/// where $g$ is called the *kernel* of the Hawkes process.
19#[derive(Debug)]
20pub struct Hawkes<T, K: Kernel> {
21    background: T,
22    kernel: K
23}
24
25impl<T, K: Kernel> Hawkes<T, K> {
26    /// Get Hawkes kernel object.
27    pub fn get_kernel(&self) -> &K {
28        &self.kernel
29    }
30
31    /// Get Hawkes background intensity.
32    pub fn get_background(&self) -> &T {
33        &self.background
34    }
35}
36
37
38// BACKGROUND INTENSITIES
39
40/// Constant background intensity $\lambda_0$ for the Hawkes process.
41pub type ConstBackground = PoissonProcess;
42
43
44/// Deterministic background intensity $\lambda_0(t)$
45pub type DeterministicBackground<F> = VariablePoissonProcess<F>;
46
47
48/// Exponential kernel for the Hawkes process, of the form 
49/// $$g(t) = \alpha \exp(-\beta t)$$
50#[derive(Debug)]
51pub struct ExpKernel {
52    pub alpha: f64,
53    pub beta: f64
54}
55
56impl Kernel for ExpKernel {
57    fn eval(&self, t: f64) -> f64 {
58        self.alpha * (-self.beta * t).exp()
59    }
60}
61
62// SUM OF EXPONENTIALS KERNEL
63
64/// Sum-of-exponentials kernel, has the form: 
65/// $$g(t) = \sum_{j=1}^p \alpha_j  \exp(-\beta_j t)$$
66#[derive(Debug)]
67pub struct SumExpKernel {
68    num_exp: usize,
69    alphas: Vec<f64>,
70    betas: Vec<f64>
71}
72
73impl SumExpKernel {
74    /// Create a new SumExpKernel.
75    pub fn new(alphas: Vec<f64>, betas: Vec<f64>) -> Self {
76        // Sanity check
77        assert_eq!(alphas.len(), betas.len());
78        
79        SumExpKernel {
80            num_exp: alphas.len(),
81            alphas,
82            betas
83        }
84    }
85}
86
87impl Kernel for SumExpKernel {
88    fn eval(&self, t: f64) -> f64 {
89        let alphabetazip = self.alphas.iter().zip(self.betas.iter());
90        let mut res = 0.;
91        for (alpha,beta) in alphabetazip {
92            res += alpha * (-beta*t).exp();
93        }
94        res
95    }
96}
97
98
99// POWER LAW HAWKES
100
101/// The power law kernel for the Hawkes process has the form
102/// $$ g(t) = \frac{\alpha}{(\delta + t)^\beta}$$
103#[derive(Debug)]
104pub struct PowerLawKernel {
105    alpha: f64,
106    beta: f64,
107    delta: f64
108}
109
110impl Kernel for PowerLawKernel {
111    fn eval(&self, t: f64) -> f64 {
112        self.alpha / (self.delta + t).powf(self.beta)
113    }
114}
115
116/// Hawkes model with a power-law kernel and constant background intensity.
117pub type PowerLawHawkes = Hawkes<ConstBackground, PowerLawKernel>;
118
119impl PowerLawHawkes {
120    /// Create a new power law Hawkes model instance.
121    pub fn new(alpha: f64, beta: f64, delta: f64, lambda0: f64) -> Self {
122        // Set the kernel.
123        let kernel = PowerLawKernel {
124            alpha, beta, delta
125        };
126        Self {
127            background: ConstBackground::new(lambda0), kernel
128        }
129    }
130}
131
132// EXPONENTIAL HAWKES
133
134/// Hawkes model with an exponential kernel.
135pub type ExpHawkes = Hawkes<ConstBackground, ExpKernel>;
136
137impl ExpHawkes {
138    pub fn new(alpha: f64, beta: f64, lambda0: f64) -> Self {
139        let kernel = ExpKernel {
140            alpha, beta
141        };
142        let background = ConstBackground::new(lambda0);
143        
144        Self {
145            background, kernel
146        }
147    }
148}
149
150impl TemporalProcess for ExpHawkes {
151    fn sample(&self, tmax: f64) -> TimeProcessResult {
152        simulate_hawkes_exp_const_bk(self, tmax)
153    }
154}
155
156impl<F> Hawkes<DeterministicBackground<F>, ExpKernel>
157where F: Fn(f64) -> f64 + Send + Sync {
158    pub fn new(alpha: f64, beta: f64, func: F, max_lbda0: f64) -> Self {
159        let kernel = ExpKernel { alpha, beta };
160        let background = DeterministicBackground::new(func, max_lbda0);
161        Self {
162            background, kernel
163        }
164    }
165}
166
167impl<F> TemporalProcess for Hawkes<DeterministicBackground<F>, ExpKernel>
168where F: Fn(f64) -> f64 + Send + Sync {
169    fn sample(&self, tmax: f64) -> TimeProcessResult {
170        simulate_hawkes_exp_var_bk(self, tmax)
171    }
172}
173
174// NUMERICAL ALGORITHM
175
176/// Simulate a trajectory of an exponential kernel Hawkes jump process,
177/// using Ogata's algorithm (1982).
178/// Variant for constant background intensity.
179fn simulate_hawkes_exp_const_bk(model: &ExpHawkes, tmax: f64) -> TimeProcessResult {
180    let kernel = &model.kernel;
181    let alpha = kernel.alpha;
182    let decay = kernel.beta;
183    let lambda0 = &model.background.intensity(0.);
184    
185    let mut rng = thread_rng(); // random no. generator
186    let mut timestamps = Vec::new();
187    let mut intensities = Vec::new();
188    // compute a first event time, occurring as a standard poisson process
189    // of intensity lambda0
190    let mut s = -1.0/lambda0*rng.gen::<f64>().ln();
191    let mut cur_lambda = lambda0 + alpha;
192    let mut lbda_max = cur_lambda;
193
194    while s < tmax {
195        let u: f64 = rng.gen();
196        // candidate time
197        let ds = -1.0/lbda_max*u.ln();
198        // compute process intensity at new time s + ds
199        // by decaying over the interval [s, s+ds]
200        cur_lambda = lambda0 + (cur_lambda-lambda0)*(-decay*ds).exp();
201        s += ds; // update s
202        if s > tmax {
203            // time window is over, finish simulation loop
204            break;
205        }
206
207        // rejection sampling step
208        let d: f64 = rng.gen();
209        if d < cur_lambda/lbda_max {
210            // accept the event
211            cur_lambda = cur_lambda + alpha; // boost the intensity with the jump
212            timestamps.push(s);
213            intensities.push(cur_lambda);
214        }
215        // update the intensity upper bound
216        lbda_max = cur_lambda; 
217    }
218
219    let timestamps = Array1::from_vec(timestamps);
220    let intensities = Array1::from_vec(intensities);
221
222    TimeProcessResult {
223        timestamps, intensities
224    }
225}
226
227fn simulate_hawkes_exp_var_bk<F>(
228    model: &Hawkes<DeterministicBackground<F>, ExpKernel>, 
229    tmax: f64
230) -> TimeProcessResult where F: Fn(f64) -> f64 + Send + Sync
231{
232    let kernel = &model.kernel;
233    let alpha = kernel.alpha;
234    let beta = kernel.beta;
235    let lambda0 = &model.background;  // background intensity
236    let max_lbda0 = model.background.get_max_lambda();
237
238    let mut rng = thread_rng(); // random no. generator
239    let mut timestamps = Vec::new();
240    let mut intensities = Vec::new();
241
242    // Algorithm: we compute the intensity for each candidate time
243    // by updating the background intensity and excited parts separately
244
245    // Running maximum process used to sample before
246    // acception-rejection step
247    let mut max_lbda = max_lbda0;
248    let mut s = 0.;
249    let mut cur_slbda = 0.;  // current self-exciting intensity
250
251    while s < tmax {
252        let u: f64 = rng.gen();
253        // candidate next event time
254        let ds = -u.ln()/max_lbda;
255        s += ds;
256        // background intensity
257        let cur_blbda = lambda0.intensity(s);
258        // decay the self-exciting part
259        cur_slbda = cur_slbda * (-beta * ds).exp();
260        let cur_lbda = cur_blbda + cur_slbda;  // total intensity
261        
262        if s > tmax {
263            // end sampling
264            break;
265        }
266
267        // rejection sampling step
268        let acceptance = cur_lbda / max_lbda;
269        let d: f64 = rng.gen();
270        if d < acceptance {
271            // accept the candidate event time.
272            cur_slbda += alpha; // add jump to self-exciting intens
273            // record event time and intensity
274            let cur_lbda = cur_blbda + cur_slbda;
275            timestamps.push(s);
276            intensities.push(cur_lbda);
277        }
278        max_lbda = max_lbda0 + cur_slbda;  // update max intensity
279    }
280
281    let timestamps = Array1::from_vec(timestamps);
282    let intensities = Array1::from_vec(intensities);
283
284    TimeProcessResult {
285        timestamps, intensities
286    }
287}
288