use crate::hawkes::errors;
use crate::hawkes::kernel::{ExcitationKernel, ExponentialKernel};
use crate::hawkes::quadrature;
use rand::Rng;
pub struct HawkesProcess<K: ExcitationKernel = ExponentialKernel> {
pub mu: f64,
pub kernel: K,
}
impl<K: ExcitationKernel> HawkesProcess<K> {
pub fn with_kernel(mu: f64, kernel: K) -> Result<Self, errors::HawkesError> {
if mu < 0.0 {
return Err(errors::HawkesError::HawkesInputTypeFailure);
}
Ok(Self { mu, kernel })
}
#[inline]
pub fn conditional_intensity_at(&self, state: &K::State) -> f64 {
self.mu + self.kernel.intensity_contribution(state)
}
pub fn generate_values_conditioned(
&self,
current_ts: f64,
history: &[f64],
n: usize,
) -> Vec<f64> {
let mut rng = rand::rng();
let mut event_times = Vec::with_capacity(n);
let mut current_time = current_ts;
let mut state = self.kernel.init_state(current_ts, history);
let max_attempts = n * 10_000;
let mut attempts = 0;
while event_times.len() < n && attempts < max_attempts {
attempts += 1;
let lambda_star = self.conditional_intensity_at(&state);
if lambda_star <= 0.0 || !lambda_star.is_finite() {
let u: f64 = rng.random();
let wait = -u.ln() / self.mu.max(1e-10);
current_time += wait;
state = self.kernel.decay_state(&state, wait);
state = self.kernel.excite_state(&state);
event_times.push(current_time);
continue;
}
let u1: f64 = rng.random();
let wait = -u1.ln() / lambda_star;
let candidate = current_time + wait;
let state_candidate = self.kernel.decay_state(&state, wait);
let lambda_cand = self.conditional_intensity_at(&state_candidate);
let u2: f64 = rng.random();
if u2 <= lambda_cand / lambda_star {
state = self.kernel.excite_state(&state_candidate);
event_times.push(candidate);
} else {
state = state_candidate;
}
current_time = candidate;
}
event_times
}
pub fn generate_values(&self, current_ts: f64, n: usize) -> Vec<f64> {
self.generate_values_conditioned(current_ts, &[], n)
}
pub fn conditional_mean_gap(&self, state: &K::State) -> f64 {
let state_clone = state.clone();
let kernel_clone = self.kernel.clone();
quadrature::conditional_mean_gap(
self.mu,
|s| kernel_clone.integrated_intensity_contribution(&state_clone, s),
1e-10,
)
}
pub fn forecast_conditional_means(
&self,
current_ts: f64,
history: &[f64],
n: usize,
) -> Vec<f64> {
let mut state = self.kernel.init_state(current_ts, history);
let mut t = current_ts;
let mut forecasts = Vec::with_capacity(n);
for _ in 0..n {
let gap = self.conditional_mean_gap(&state);
if !gap.is_finite() || gap <= 0.0 {
let fallback = if self.mu > 0.0 { 1.0 / self.mu } else { 1.0 };
t += fallback;
state = self.kernel.decay_state(&state, fallback);
state = self.kernel.excite_state(&state);
forecasts.push(t);
continue;
}
t += gap;
state = self.kernel.decay_state(&state, gap);
state = self.kernel.excite_state(&state);
forecasts.push(t);
}
forecasts
}
}
impl HawkesProcess<ExponentialKernel> {
pub fn hawkes_valid_inputs(
mu: f64,
alpha: f64,
beta: f64,
) -> Result<(), errors::HawkesError> {
match mu >= 0.0 && alpha >= 0.0 && beta > 0.0 {
true => Ok(()),
false => Err(errors::HawkesError::HawkesInputTypeFailure),
}
}
pub fn new(mu: f64, alpha: f64, beta: f64) -> Result<Self, errors::HawkesError> {
Self::hawkes_valid_inputs(mu, alpha, beta)?;
Ok(Self {
mu,
kernel: ExponentialKernel { alpha, beta },
})
}
pub fn lambda(&self, t: f64, event_times: &[f64]) -> f64 {
let mut intensity = self.mu;
for &event_time in event_times {
if event_time < t {
intensity +=
self.kernel.alpha * (-self.kernel.beta * (t - event_time)).exp();
}
}
intensity
}
#[inline]
pub fn init_auxiliary(&self, t: f64, history: &[f64]) -> f64 {
self.kernel.init_state(t, history)
}
}