Skip to content

Commit 658c364

Browse files
committed
Univariate completed
Changed file structure so each type of Hawkes process is in its own folder. `Intensity`, `simulation`, `time_change` and `fit` implemented for univariate processes. Missing `time_change` and `fit` for multivariate processes.
1 parent b185a76 commit 658c364

23 files changed

Lines changed: 762 additions & 366 deletions

docs/src/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ HawkesProcess
107107
### Univariate
108108

109109
```@docs
110-
UnivariateHawkesProcess
111110
UnmarkedUnivariateHawkesProcess
111+
UnivariateHawkesProcess
112112
```
113113

114114
### Multivariate

src/PointProcesses.jl

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module PointProcesses
1010
using DensityInterface: DensityInterface, HasDensity, densityof, logdensityof
1111
using Distributions: Distributions, UnivariateDistribution, MultivariateDistribution
1212
using Distributions: Categorical, Exponential, Poisson, Uniform, Dirac
13-
using Distributions: fit, suffstats, probs, mean, support
13+
using Distributions: fit, suffstats, probs, mean, support, pdf
1414
using LinearAlgebra: dot, diagm
1515
using Random: rand
1616
using Random: AbstractRNG, default_rng
@@ -73,6 +73,20 @@ include("hawkes/hawkes_process.jl")
7373
include("hawkes/simulation.jl")
7474
include("hawkes/intensity.jl")
7575
include("hawkes/fit.jl")
76-
include("hawkes/time_change.jl")
76+
include("hawkes/unmarked_univariate/unmarked_univariate_hawkes.jl")
77+
include("hawkes/unmarked_univariate/intensity.jl")
78+
include("hawkes/unmarked_univariate/time_change.jl")
79+
include("hawkes/unmarked_univariate/fit.jl")
80+
include("hawkes/unmarked_univariate/simulation.jl")
81+
include("hawkes/univariate/univariate_hawkes.jl")
82+
include("hawkes/univariate/intensity.jl")
83+
include("hawkes/univariate/time_change.jl")
84+
include("hawkes/univariate/fit.jl")
85+
include("hawkes/univariate/simulation.jl")
86+
include("hawkes/multivariate/multivariate_hawkes.jl")
87+
include("hawkes/multivariate/intensity.jl")
88+
# include("hawkes/multivariate/time_change.jl")
89+
# include("hawkes/multivariate/fit.jl")
90+
include("hawkes/multivariate/simulation.jl")
7791

7892
end

src/hawkes/fit.jl

Lines changed: 21 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,29 @@
1-
"""
2-
StatsAPI.fit(rng, ::Type{UnmarkedUnivariateHawkesProcess{T}}, h::History; step_tol::Float64 = 1e-6, max_iter::Int = 1000) where {T<:Real}
3-
4-
Expectation-Maximization algorithm from [E. Lewis, G. Mohler (2011)](https://arxiv.org/pdf/1801.08273)).
5-
The relevant calculations are in page 4, equations 6-13.
6-
7-
Let (t₁ < ... < tₙ) be the event times over the interval [0, T). We use the immigrant-descendant representation,
8-
where immigrants arrive at a constant base rate μ and each each arrival may generate descendants following the
9-
activation function α exp(-ω(t - tᵢ)).
10-
11-
The algorithm consists in the following steps:
12-
1. Start with some initial guess for the parameters μ, ψ, and ω. ψ = α ω is the branching factor.
13-
2. Calculate λ(tᵢ; μ, ψ, ω) (`lambda_ts` in the code) using the procedure in [Ozaki (1979)](https://doi.org/10.1007/bf02480272).
14-
3. For each tᵢ and each j < i, calculate Dᵢⱼ = P(tᵢ is a descendant of tⱼ) as
15-
16-
Dᵢⱼ = ψ ω exp(-ω(tᵢ - tⱼ)) / λ(tᵢ; μ, ψ, ω).
17-
18-
Define D = ∑_{j < i} Dᵢⱼ (expected number of descendants) and div = ∑_{j < i} (tᵢ - tⱼ) Dᵢⱼ.
19-
4. Update the parameters as
20-
μ = (N - D) / T
21-
ψ = D / N
22-
ω = D / div
23-
5. If convergence criterion is met, return updated parameters, otherwise, back to step 2.
24-
25-
Notice that, in the implementation, the process is normalized so the average inter-event time is equal to 1 and,
26-
therefore, the interval of the process is transformed from T to N. Also, in equation (8) in the paper,
27-
28-
∑_{i=1:n} pᵢᵢ = ∑_{i=1:n} (1 - ∑_{j < i} Dᵢⱼ) = N - D.
29-
30-
"""
31-
function StatsAPI.fit(
32-
::Type{<:UnmarkedUnivariateHawkesProcess{T}},
33-
h::History;
1+
#=
2+
Method for fitting the parameters of marked and unmarked Hawkes
3+
processes. For unmarked processes, the marks in `history` must
4+
be either all equal to `nothing` or equal to 1.
5+
=#
6+
function fit_hawkes_params(
7+
::Type{R},
8+
h::History,
9+
div_ψ::Real;
3410
step_tol::Float64=1e-6,
3511
max_iter::Int=1000,
3612
rng::AbstractRNG=default_rng(),
37-
) where {T<:Real}
13+
) where {R<:Real}
14+
T = float(R)
15+
div_ψ = T(div_ψ)
3816
n = nb_events(h)
39-
n == 0 && return HawkesProcess(zero(T), zero(T), zero(T))
17+
nT = T(n)
18+
n == 0 && return (zero(T), zero(T), zero(T))
4019

4120
tmax = T(duration(h))
4221
# Normalize times so average inter-event time is 1 (T -> n)
4322
norm_ts = T.(h.times .* (n / tmax))
4423

4524
# preallocate
46-
A = zeros(T, n) # A[i] = sum_{j<i} exp(-ω (t_i - t_j))
47-
S = zeros(T, n) # S[i] = sum_{j<i} (t_i - t_j) exp(-ω (t_i - t_j))
25+
A = zeros(T, n) # A[i] = sum_{j<i} mⱼ exp(-ω (t_i - t_j))
26+
S = zeros(T, n) # S[i] = sum_{j<i} mⱼ (t_i - t_j) exp(-ω (t_i - t_j))
4827
lambda_ts = similar(A) # λ_i
4928

5029
# Λ(T) ≈ n after normalization
@@ -65,9 +44,8 @@ function StatsAPI.fit(
6544
for i in 2:n
6645
Δ = norm_ts[i] - norm_ts[i - 1]
6746
e = exp(-ω * Δ)
68-
Ai_1 = A[i - 1]
69-
A[i] = e * (one(T) + Ai_1)
70-
S[i] = e * (S[i - 1] + Δ * (one(T) + Ai_1))
47+
A[i] = update_A(A[i - 1], e, h.marks[i - 1]) # Different updates for real marks and no marks
48+
S[i] =* A[i]) + (e * S[i - 1])
7149
lambda_ts[i] = μ +* ω) * A[i]
7250
end
7351

@@ -81,8 +59,8 @@ function StatsAPI.fit(
8159
end
8260

8361
# Steps 4–5: M-step updates + convergence check
84-
new_μ = one(T) - (D / n)
85-
new_ψ = D / n
62+
new_μ = one(T) - (D / nT)
63+
new_ψ = D / div_ψ
8664
new_ω = D / (div + eps(T)) # small guard to avoid div=0
8765

8866
step = max(abs- new_μ), abs- new_ψ), abs- new_ω))
@@ -97,13 +75,5 @@ function StatsAPI.fit(
9775

9876
# Unnormalize back to original time scale (T -> tmax):
9977
# parameters in normalized space (') relate to original by μ0=μ'*(n/tmax), ω0=ω'*(n/tmax), α0=(ψ'ω')*(n/tmax)
100-
return HawkesProcess* (n / tmax), ψ * ω * (n / tmax), ω * (n / tmax))
101-
end
102-
103-
# Type parameter for `HawkesProcess` was NOT explicitly provided
104-
function StatsAPI.fit(
105-
HP::Type{UnmarkedUnivariateHawkesProcess}, h::History{H,M}; kwargs...
106-
) where {H<:Real,M}
107-
T = promote_type(Float64, H)
108-
return fit(HP{T}, h; kwargs...)
78+
return* (nT / tmax), ψ * ω * (nT / tmax), ω * (nT / tmax))
10979
end

src/hawkes/hawkes_process.jl

Lines changed: 10 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -5,183 +5,13 @@ Common interface for all subtypes of `HawkesProcess`.
55
"""
66
abstract type HawkesProcess <: AbstractPointProcess end
77

8-
"""
9-
UnivariateHawkesProcess{R<:Real,D} <: HawkesProcess
10-
11-
Univariate Hawkes process with exponential decay kernel and mark
12-
distribution `D`.
13-
14-
Denote the events of the process by (tᵢ, mᵢ), where tᵢ is the event time
15-
and mᵢ ∈ M the corresponding mark. The conditional intensity function
16-
of the Hawkes process is given by
17-
18-
λ(t) = μ + ∑_{tᵢ < t} α mᵢ exp(-ω (t - tᵢ)).
19-
20-
Notice that the mark only affects the jump size.
21-
22-
# Fields
23-
- `μ::R`: baseline intensity (immigration rate)
24-
- `α::R`: jump size
25-
- `ω::R`: decay rate.
26-
- `mark_dist::D`: distribution of marks
27-
"""
28-
struct UnivariateHawkesProcess{T<:Real,D} <: HawkesProcess
29-
μ::T
30-
α::T
31-
ω::T
32-
mark_dist::D
33-
end
34-
35-
function Base.show(io::IO, hp::UnivariateHawkesProcess)
36-
return print(io, "UnivariateHawkesProcess($(hp.μ), $(hp.α), $(hp.ω), $(hp.mark_dist))")
37-
end
38-
39-
"""
40-
UnmarkedUnivariateHawkesProcess{R<:Real} <: HawkesProcess
41-
42-
Unmarked univariate Hawkes process with exponential decay kernel.
43-
44-
Denote the events of the process by tᵢ. The conditional intensity function
45-
of the Hawkes process is given by
46-
47-
λ(t) = μ + ∑_{tᵢ < t} α exp(-ω (t - tᵢ)).
48-
49-
# Fields
50-
- `μ::R`: baseline intensity (immigration rate)
51-
- `α::R`: jump size
52-
- `ω::R`: decay rate.
53-
54-
Alias for UnivariateHawkesProcess{R, Dirac{Nothing}}.
55-
"""
56-
const UnmarkedUnivariateHawkesProcess{R<:Real} = UnivariateHawkesProcess{R,Dirac{Nothing}}
57-
58-
function Base.show(io::IO, hp::UnmarkedUnivariateHawkesProcess)
59-
return print(io, "UnmarkedUnivariateHawkesProcess$((hp.μ, hp.α, hp.ω))")
60-
end
61-
62-
"""
63-
MultivariateHawkesProcess{R} <: HawkesProcess
64-
65-
Unmarked multivariate temporal Hawkes process with exponential decay
66-
67-
For a process with m = 1, 2, ..., M marginal processes, the conditional intensity
68-
function of the m-th process is given by
69-
70-
λₘ(t) = μₘ + ∑_{l=1,...,M} ∑_{tˡᵢ < t} αₘₗ exp(-βₘₗ(t - tˡᵢ)),
71-
72-
where tˡᵢ is the i-th element in the l-th marginal process.
73-
μ is a vector of length M with elements μₘ. α and ω are M×M
74-
matrices with elements αₘₗ and ωₘₗ.
75-
76-
The process is represented as a marked process, where each marginal
77-
process m = 1, 2, ..., M is represented by events (tᵐᵢ, m), tᵐᵢ being
78-
the i-th element with mark m.
79-
80-
# Fields
81-
- `μ::Vector{<:Real}`: baseline intensity (immigration rate)
82-
- `α::Matrix{<:Real}`: Jump size.
83-
- `ω::Matrix{<:Real}`: Decay rate.
84-
"""
85-
struct MultivariateHawkesProcess{T<:Real} <: HawkesProcess
86-
μ::T
87-
α::Matrix{T}
88-
ω::Matrix{T}
89-
mark_dist::Categorical{Float64,Vector{Float64}} # To keep consistent with PoissonProcess. Helps in simulation.
90-
end
91-
92-
function Base.show(io::IO, hp::MultivariateHawkesProcess{T}) where {T<:Real}
93-
return print(
94-
io,
95-
"MultivariateHawkesProcess\nμ = $(T.(hp.μ .* probs(hp.mark_dist)))\nα = $(hp.α)\nω = $(hp.ω)",
96-
)
97-
end
98-
99-
Base.length(mh::MultivariateHawkesProcess) = size(mh.α)[1]
100-
101-
## Constructors
102-
### UnivariatehawkesProcess
103-
function HawkesProcess::Real, α::Real, ω::Real, mark_dist; check_args::Bool=true)
104-
check_args && check_args_Hawkes(μ, α, ω, mark_dist)
105-
return UnivariateHawkesProcess(promote(μ, α, ω)..., mark_dist)
106-
end
107-
108-
function HawkesProcess::Real, α::Real, ω::Real; check_args::Bool=true)
109-
return HawkesProcess(μ, α, ω, Dirac(nothing); check_args=check_args)
110-
end
111-
112-
### MultivariateHawkesProcess
113-
function HawkesProcess(
114-
μ::Vector{<:Real}, α::Matrix{<:Real}, ω::Matrix{<:Real}; check_args::Bool=true
115-
)
116-
check_args && check_args_Hawkes(μ, α, ω)
117-
R = promote_type(eltype(μ), eltype(α), eltype(ω))
118-
return MultivariateHawkesProcess(R(sum(μ)), R.(α), R.(ω), Categorical/ sum(μ)))
119-
end
120-
121-
# For M independent marginal processes
122-
function HawkesProcess(
123-
μ::Vector{<:Real}, α::Vector{<:Real}, ω::Vector{<:Real}; check_args::Bool=true
124-
)
125-
check_args && check_args_Hawkes(μ, diagm(α), diagm(ω))
126-
R = promote_type(eltype(μ), eltype(α), eltype(ω))
127-
return MultivariateHawkesProcess(R(sum(μ)), R.(α), R.(ω), Categorical/ sum(μ)))
128-
end
129-
130-
# Check args
131-
## Univariate Hawkes
132-
function check_args_Hawkes::Real, α::Real, ω::Real, mark_dist)
133-
if any((μ, α, ω) .< 0)
134-
throw(
135-
DomainError(
136-
"μ = , α = , ω = ",
137-
"HawkesProcess: All parameters must be non-negative.",
138-
),
139-
)
140-
end
141-
mean_α = mark_dist isa Dirac{Nothing} ? α : mean(mark_dist) * α
142-
if mean_α >= ω
143-
throw(
144-
DomainError(
145-
"α = $(mean_α), ω = ",
146-
"HawkesProcess: mᵢα must be, on average, smaller than ω. Stability condition.",
147-
),
148-
)
149-
end
150-
if !isa(mark_dist, Dirac{Nothing}) && minimum(mark_dist) < 0
151-
throw(
152-
DomainError(
153-
"Mark distribution support = $((support(mark_dist).lb, support(mark_dist).ub))",
154-
"HawkesProcess: Support of mark distribution must be contained in non-negative numbers",
155-
),
156-
)
157-
end
158-
return nothing
159-
end
160-
161-
## Multivariate Hawkes
162-
function check_args_Hawkes::Vector{<:Real}, α::Matrix{<:Real}, ω::Matrix{<:Real})
163-
if length(μ) != size(α)[2]
164-
throw(DimansionMismatch("α must have size $(length(μ))×$(length(μ))"))
165-
end
166-
if length(μ) != size(ω)[2]
167-
throw(DimansionMismatch("ω must have size $(length(μ))×$(length(μ))"))
168-
end
169-
if any(sum./ ω; dims=1) .>= 1)
170-
throw(
171-
DomainError(
172-
"α = , ω = ",
173-
"HawkesProcess: Sum of α/β over each row must be smaller than 1. Stability condition.",
174-
),
175-
)
176-
end
177-
if any.< zero(μ)) || any.< zero(α)) || any.< zero(ω))
178-
throw(
179-
DomainError(
180-
"μ = , α = , ω = ",
181-
"HawkesProcess: All elements of μ, α and ω must be non-negative.",
182-
),
183-
)
184-
end
185-
ω[α .== 0] .= 1 # Protects against division by 0 in simulation
186-
return nothing
187-
end
8+
# #=
9+
# If type parameter for `HawkesProcess` was NOT explicitly provided,
10+
# use `Float64` as the standard type
11+
# =#
12+
# function StatsAPI.fit(
13+
# HP::Type{<:HawkesProcess}, h::History{H,M}; kwargs...
14+
# ) where {H<:Real,M}
15+
# T = promote_type(Float64, H)
16+
# return fit(HP{T}, h; kwargs...)
17+
# end

0 commit comments

Comments
 (0)