Interface for Multivariate processes#92
Conversation
- Added mark distribution to the process
- Added `univariate_process.jl` and `multivariate_process.jl` files defining the common interfaces - Updated the multivariate processes to comply with the interface - Split univariate Hawkes implementation into thre files
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
I figured it would be a good idea to split this PR in two. |
|
Is this ready for me to review? |
|
Sorry. I guess I forgot to request the review. But it should be ready, yes |
| end | ||
| return l | ||
| end | ||
| DensityInterface.logdensityof |
| HawkesProcess(μ, α, ω) = HawkesProcess(μ, α, ω, NoMarks()) | ||
|
|
||
| ∑_{i=1:n} pᵢᵢ = ∑_{i=1:n} (1 - ∑_{j < i} Dᵢⱼ) = N - D. | ||
| function ground_intensity(hp::HawkesProcess, h::History, t) |
There was a problem hiding this comment.
This will throw on a Univariate hawkes no? as in univariate_process.jl the order is t, h?
There was a problem hiding this comment.
if yes we should also add a test for a univariate hawkes intensity
There was a problem hiding this comment.
The test is already there, but I switched the order in the tests too : P
| descendants = T[] | ||
| next_gen = immigrants | ||
| while !isempty(next_gen) | ||
| # OPTIMIZE: Can this be improved by avoiding allocations of `curr_gen` and `next_gen`? Or does the compiler take care of that? |
There was a problem hiding this comment.
probably. we would want to make a workspace type that we use inplace operations on
| function time_change(h::History, pp::AbstractUnivariateProcess) | ||
| Λ(t) = integrated_ground_intensity(pp, h, min_time(h), t) | ||
| return time_change(h, Λ) | ||
| end |
There was a problem hiding this comment.
redundant with abstract_point_process.jl:126?
| h_temp = History(sim, tmin, tmax) | ||
| marks = [sample_mark(hp.mark_dist, t, h_temp) for t in event_times(h_temp)] | ||
| return History(; times=sim, tmin=tmin, tmax=tmax, marks=marks) |
| """ | ||
| abstract type AbstractMultivariateProcess <: AbstractPointProcess end | ||
|
|
||
| Base.ndims(pp::AbstractMultivariateProcess) = length(pp.mark_dist) |
There was a problem hiding this comment.
This assumes there is a mark_dist field but IndependentMultivariate does not have one?
There was a problem hiding this comment.
Yes, IndependentMultivariateProcess is a bit different, since we are not really building a new process, but only gluing a bunch of processes together.
For new multivariate processes, I would expect we use the interface for mark distributions. The idea is that the mark distributions at time t are independent conditional on the history up to t, so we can model them separetely (well, technically not really true, only for processes in which the probability of simultaneous events is 0, which are all interesting processes I am aware of)
| @@ -0,0 +1,102 @@ | |||
| """ | |||
| StatsAPI.fit(rng, ::Type{HawkesProcess{T}}, h::History; step_tol::Float64 = 1e-6, max_iter::Int = 1000) where {T<:Real} | |||
| end | ||
|
|
||
| pp_est = fit(PP, h) | ||
| pp_est = fit(PP, h)::AbstractPointProcess # JET complains if not specified |
There was a problem hiding this comment.
this is bein caused by the type stability issue i'm p sure
There was a problem hiding this comment.
This is the message I get.
┌ MonteCarloTest(S::Type{<:Statistic}, PP::Type{<:AbstractPointProcess}, h::History; kwargs...) @ PointProcesses /home/jkling/projects/PointProcesses.jl/src/HypothesisTests/PPTests/monte_carlo_test.jl:114
│ no matching method found kwcall(::NamedTuple, ::Type{MonteCarloTest}, ::Type{<:Statistic}, ::Rician, ::History) (1/4 union split): Core.kwcall(merge(NamedTuple()::@NamedTuple{}, kwargs::@kwargs{…})::NamedTuple, MonteCarloTest, S::Type{<:Statistic}, pp_est::Union{Rician, HawkesProcess, IndependentMultivariateProcess, PoissonProcess}, h::History)
└────────────────────
Apparently, the static analysis thinks that Rician is a possible return type of fit (Rician is a Distributions.Distribution). Not sure how to deal with this if not by type annotating this line.
|
|
||
| # Estimate process and calculate statistic from data | ||
| pp_est = fit(PP, h; rng=rng) | ||
| pp_est = fit(PP, h; rng=rng)::AbstractPointProcess # JET complains if not specified |
c3eec46 to
774c595
Compare
774c595 to
4052588
Compare
| end | ||
|
|
||
| """ | ||
| function StatsAPI.fit( |
There was a problem hiding this comment.
These fit methods e.g., fit(HawkesProcess, h) were delted yet still referenced in docs (see index.md, hawkes.md, and montecarlo.md)
| sort!(sim) | ||
| h = History(eltype(sim)[], tmin, tmax, eltype(hp.mark_dist)[], Nothing[], 1) | ||
| for event in sim | ||
| push!(h, event, sample_mark(hp, event, h)) |
There was a problem hiding this comment.
| push!(h, event, sample_mark(hp, event, h)) | |
| push!(h, event, sample_mark(rng, hp, event, h)) |
| ``` | ||
| The default method uses a loop over events combined with `integrated_ground_intensity`, but it should be reimplemented for specific processes if faster computation is possible. | ||
| """ | ||
| function DensityInterface.logdensityof(pp::AbstractUnivariateProcess, h::History) |
There was a problem hiding this comment.
With the new type hierarchy, BoundedPointProcess now has no logdensityofmethod i.e., this would fail:
using PointProcesses
h = History([1.0, 2.0, 3.0], 0.0, 10.0)
logdensityof(BoundedPointProcess(PoissonProcess(1.0), 0.0, 10.0), h)| transformed_times = [zeros(T, nb_events(h, d)) for d in 1:ndims(h)] | ||
| for d in 1:ndims(pp) |
There was a problem hiding this comment.
A 3-dim process with a history whose keyword constructor computed N=length(unique(dims)) = 2, which split_into_chunks would do, would cause a BBounds error. I think changiing transformed tiems to index by 1:ndims(pp) would fix.
| return sum(log.(hp.μ .+ (hp.α .* A))) - # Value of intensity at each event | ||
| (hp.μ * duration(h)) - # Integral of base rate | ||
| ((hp.α / hp.ω) * sum(1 .- exp.(-hp.ω .* (duration(h) .- h.times)))) + # Integral of each kernel | ||
| sum(log.([densityof(hp.mark_dist, t, h, m) for (t, m) in zip(h.times, h.marks)])) # Loglikelihood of marks |
There was a problem hiding this comment.
| sum(log.([densityof(hp.mark_dist, t, h, m) for (t, m) in zip(h.times, h.marks)])) # Loglikelihood of marks | |
| sum(log(densityof(hp.mark_dist, t, h, m)) for (t, m) in zip(h.times, h.marks); init=0.0) # Loglikelihood of marks |
| return PoissonProcess(λ, mark_dist) | ||
| end | ||
|
|
||
| function StatsAPI.fit(pptype::Type{PoissonProcess{R,D}}, args...; kwargs...) where {R,D} |
There was a problem hiding this comment.
this deletion means we no longer support weighted fitting. is that intentional?
| end | ||
| return sum(log.(hp.μ .+ (hp.α .* A))) - # Value of intensity at each event | ||
| (hp.μ * duration(h)) - # Integral of base rate | ||
| ((hp.α / hp.ω) * sum(1 .- exp.(-hp.ω .* (duration(h) .- h.times)))) + # Integral of each kernel |
There was a problem hiding this comment.
should be h.tmax .- h.times ?
| function ground_intensity(hp::HawkesProcess, t, h::History) | ||
| activation = sum(exp.(hp.ω .* (@view h.times[1:(searchsortedfirst(h.times, t) - 1)]))) | ||
| return hp.μ + (hp.α * activation / exp(hp.ω * t)) | ||
| end |
There was a problem hiding this comment.
need the shift invariant form
| dims = fill(nothing, length(times)) | ||
| N = 1 | ||
| else | ||
| N = length(unique(dims)) |
There was a problem hiding this comment.
This counts distinct dim values present, not the actual dimension count
| any((μ, α, ω) .< 0) && | ||
| throw(DomainError((μ, α, ω), "All parameters must be non-negative.")) | ||
| (α > 0 && α >= ω) && | ||
| throw(DomainError((α, ω), "Parameter ω must be strictly smaller than α")) |
|
found a few more bugs. i'll see if i can figure out the JET thing |
This PR has the objective of implementing multivariate Hawkes processes.
In the process, some other modifications were carried out.
AbstractUnivariateProcessandAbstractMultivariateProcess