Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/PointProcesses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module PointProcesses
# Imports

using DensityInterface: DensityInterface, HasDensity, densityof, logdensityof
using Distributions: Distributions, UnivariateDistribution, MultivariateDistribution
using Distributions: Distributions, Distribution, UnivariateDistribution, MultivariateDistribution
using Distributions: Categorical, Exponential, Poisson, Uniform, Dirac, Gamma
using Distributions: fit, suffstats, probs
using Integrals: Integrals, IntegralProblem, solve, QuadGKJL
Expand Down Expand Up @@ -40,6 +40,7 @@ export time_change, split_into_chunks

## Point processes

export PointProcessMarkDistribution, AbstractMarkDistribution, NoMarks
export AbstractPointProcess, AbstractUnivariateProcess, AbstractMultivariateProcess
export BoundedPointProcess
export ground_intensity, mark_distribution
Expand Down Expand Up @@ -79,6 +80,7 @@ export PointProcessTest, BootstrapTest, MonteCarloTest

## General
include("history.jl")
include("mark_distributions.jl")
include("abstract_point_process.jl")
include("simulation.jl")
include("bounded_point_process.jl")
Expand Down
2 changes: 1 addition & 1 deletion src/abstract_point_process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ For multivariate processes, it returns a vector of mark distributions for each d

mark_distribution(pp, h, t, d) computes the mark distribution for a multivariate process `pp` at dimension `d`.
"""
function mark_distribution end
mark_distribution(pp::AbstractPointProcess, t, h) = mark_distribution(pp.mark_dist, t, h)

"""
intensity(pp, m, t, h)
Expand Down
7 changes: 6 additions & 1 deletion src/history.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ function Base.show(io::IO, h::History{T,M}) where {T,M}
)
end

function History(tmin::R1, tmax::R2, N::Int=1) where {R1<:Real, R2<:Real}
R = promote_type(R1, R2)
History(R[], tmin, tmax, [], [], N)
end

"""
event_times(h)

Expand Down Expand Up @@ -318,7 +323,7 @@ Add event `(t, m)` inside the interval `[h.tmin, h.tmax)` at the end of history
function Base.push!(h::History, t::Real, m=nothing, d=nothing; check_args=true)
if check_args
@assert h.tmin <= t < h.tmax
@assert (length(h) == 0) || (h.times[end] <= t)
@assert (length(h) == 0) || (h.times[end] < t)
@assert (d === nothing && h.N == 1) || 1 <= d <= h.N
end
push!(h.times, t)
Expand Down
54 changes: 54 additions & 0 deletions src/mark_distributions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

# Type definition
abstract type AbstractMarkDistribution end

const PointProcessMarkDistribution = Union{Distribution, AbstractMarkDistribution}

## Standard implementations. Override as needed
"""
mark_distribution(md, t, h)

Compute the distribution of marks at time `t` after history `h`.
"""
function mark_distribution end

function mark_distribution(md::AbstractMarkDistribution, t, h::History)
error("Type $(typeof(md)) subtypes `AbstractMarkDistribution` but has " *
"not implemented the required `mark_distribution(md, t, h)` method.")
end

"""
sample_mark(rng, md, t, h)

Return one sample from the distribution of marks at time `t` after history `h`, using the random number generator `rng`.
"""
sample_mark(rng::AbstractRNG, md::PointProcessMarkDistribution, t, h::History) = rand(rng, mark_distribution(md, t, h))

sample_mark(md::PointProcessMarkDistribution, t, h::History) = sample_mark(default_rng(), md, t, h)

Base.eltype(md::AbstractMarkDistribution) = eltype(mark_distribution(md, 0.0, History(0.0, 1.0)))

DensityInterface.densityof(md::PointProcessMarkDistribution, t, h::History, m) =
densityof(mark_distribution(md, t, h), m)

# Support for `Distributions.jl`
mark_distribution(d::Distribution, t, h::History) = d

StatsAPI.fit(D::Type{<:Distribution}, h::History) = fit(D, h.marks)

# Struct for non-marked processes
struct NoMarks <: AbstractMarkDistribution end

mark_distribution(::NoMarks, t, h::History) = Dirac(nothing)

sample_mark(::AbstractRNG, ::NoMarks, t, ::History) = nothing

Base.eltype(::NoMarks) = Nothing

StatsAPI.fit(::Type{NoMarks}, h) = NoMarks()

StatsAPI.fit(::Type{NoMarks}, marks, weights) = NoMarks()

DensityInterface.densityof(::NoMarks, t, h, ::Nothing) = 1.0

DensityInterface.densityof(::NoMarks, t, h, m) = 0.0
2 changes: 1 addition & 1 deletion src/multivariate/multivariate_poisson_process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function PoissonProcess(
end

function PoissonProcess(λ::AbstractVector{R}; check_args::Bool=true) where {R<:Real}
return PoissonProcess(λ, [Dirac(nothing) for _ in eachindex(λ)]; check_args=check_args)
return PoissonProcess(λ, [NoMarks() for _ in eachindex(λ)]; check_args=check_args)
end

function PoissonProcess(
Expand Down
4 changes: 2 additions & 2 deletions src/simulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ To infer the type of the marks, the implementation assumes that there is method
function simulate_ogata(
rng::AbstractRNG, pp::AbstractPointProcess, tmin::T, tmax::T
) where {T<:Real}
M = typeof(rand(mark_distribution(pp, tmin)))
M = eltype(pp.mark_dist)
h = History(; times=T[], marks=M[], tmin=tmin, tmax=tmax)
t = tmin
while t < tmax
Expand All @@ -34,8 +34,8 @@ function simulate_ogata(
U_max = ground_intensity(pp, t + τ, h) / B
U = rand(rng, typeof(U_max))
if U < U_max
m = rand(rng, mark_distribution(pp, t + τ, h))
if t + τ < tmax
m = sample_mark(pp.mark_dist, t + τ, h)
push!(h, t + τ, m; check_args=false)
end
end
Expand Down
11 changes: 0 additions & 11 deletions src/univariate/poisson/fit.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
## Fit MLE

#=
A separate `fit` method for unmarked Poisson processes is needed, because
`Distributions.jl` does not provide a `fit` method for the `Dirac` distribution
=#
function StatsAPI.fit(
::Type{PoissonProcess{R,Dirac{Nothing}}}, ss::PoissonProcessStats{R1,R2}; kwargs...
) where {R<:Real,R1<:Real,R2<:Real}
λ = convert(R, ss.nb_events / ss.duration)
return PoissonProcess(λ, Dirac(nothing))
end

function StatsAPI.fit(
::Type{PoissonProcess{R,D}}, ss::PoissonProcessStats{R1,R2}; kwargs...
) where {R<:Real,D,R1<:Real,R2<:Real}
Expand Down
8 changes: 4 additions & 4 deletions src/univariate/poisson/inhomogeneous/fit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ function StatsAPI.fit(
end

function StatsAPI.fit(
::Type{<:InhomogeneousPoissonProcess{F,Dirac{Nothing}}},
::Type{<:InhomogeneousPoissonProcess{F,NoMarks}},
h::History,
init_params;
integration_config=IntegrationConfig(),
kwargs...,
) where {F<:ParametricIntensity}
# For unmarked processes, skip fitting the mark distribution
intensity = fit(F, h, init_params; integration_config=integration_config, kwargs...)
return InhomogeneousPoissonProcess(intensity, Dirac(nothing))
return InhomogeneousPoissonProcess(intensity, NoMarks())
end

function StatsAPI.fit(
Expand Down Expand Up @@ -127,7 +127,7 @@ function StatsAPI.fit(
end

function StatsAPI.fit(
::Type{<:InhomogeneousPoissonProcess{PiecewiseConstantIntensity{R},Dirac{Nothing}}},
::Type{<:InhomogeneousPoissonProcess{PiecewiseConstantIntensity{R},NoMarks}},
h::History,
breakpoints::Vector;
kwargs...,
Expand All @@ -149,7 +149,7 @@ function StatsAPI.fit(
end

intensity = PiecewiseConstantIntensity(breakpoints, rates)
return InhomogeneousPoissonProcess(intensity, Dirac(nothing))
return InhomogeneousPoissonProcess(intensity, NoMarks())
end

function StatsAPI.fit(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ end
function InhomogeneousPoissonProcess(
f::F; integration_config::C=IntegrationConfig()
) where {F,C}
return InhomogeneousPoissonProcess{F,Dirac{Nothing},C}(
f, Dirac(nothing), integration_config
return InhomogeneousPoissonProcess{F,NoMarks,C}(
f, NoMarks(), integration_config
)
end

Expand All @@ -61,21 +61,12 @@ function Base.show(io::IO, pp::InhomogeneousPoissonProcess)
)
end

## Access methods

"""
Mark distribution is time-independent for this implementation.
"""
mark_distribution(pp::InhomogeneousPoissonProcess) = pp.mark_dist

## AbstractPointProcess interface

ground_intensity(pp::InhomogeneousPoissonProcess, t, h) = pp.intensity_function(t)
mark_distribution(pp::InhomogeneousPoissonProcess, t, h) = pp.mark_dist
mark_distribution(pp::InhomogeneousPoissonProcess, t) = pp.mark_dist

function intensity(pp::InhomogeneousPoissonProcess, m, t, h)
return ground_intensity(pp, t, h) * densityof(mark_distribution(pp, t, h), m)
return ground_intensity(pp, t, h) * densityof(pp.mark_dist, t, h, m)
end

"""
Expand Down
31 changes: 8 additions & 23 deletions src/univariate/poisson/poisson_process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Homogeneous temporal Poisson process with arbitrary mark distribution.

PoissonProcess(λ, mark_dist)
"""
struct PoissonProcess{R<:Real,D} <: AbstractUnivariateProcess
struct PoissonProcess{R<:Real,D<:PointProcessMarkDistribution} <: AbstractUnivariateProcess
λ::R
mark_dist::D

Expand All @@ -29,7 +29,7 @@ struct PoissonProcess{R<:Real,D} <: AbstractUnivariateProcess
end

function PoissonProcess(λ::R; check_args::Bool=true) where {R<:Real}
return PoissonProcess(λ, Dirac(nothing); check_args=check_args)
return PoissonProcess(λ, NoMarks(); check_args=check_args)
end

PoissonProcess() = PoissonProcess(1.0)
Expand All @@ -39,39 +39,24 @@ function Base.show(io::IO, pp::PoissonProcess)
end

## Access
ground_intensity(pp::PoissonProcess) = pp.λ
mark_distribution(pp::PoissonProcess) = pp.mark_dist
ground_intensity(pp::PoissonProcess, t, h) = pp.λ

## Intensity functions
function intensity(pp::PoissonProcess, m)
return ground_intensity(pp) * densityof(mark_distribution(pp), m)
end

function log_intensity(pp::PoissonProcess, m)
return log(ground_intensity(pp)) + logdensityof(mark_distribution(pp), m)
end
intensity(pp::PoissonProcess, m, t, h) = pp.λ * densityof(pp.mark_dist, t, h, m)

## Time change
function time_change(h::History, pp::PoissonProcess)
times = (h.times .- h.tmin) .* ground_intensity(pp)
tmax = (h.tmax - h.tmin) * ground_intensity(pp)
times = (h.times .- h.tmin) .* pp.λ
tmax = (h.tmax - h.tmin) * pp.λ
return History(; times=times, tmin=0, tmax=tmax, marks=h.marks)
end

## Implementing AbstractPointProcess interface

ground_intensity(pp::PoissonProcess, t, h) = ground_intensity(pp)
mark_distribution(pp::PoissonProcess, t, h) = mark_distribution(pp)
mark_distribution(pp::PoissonProcess, t) = mark_distribution(pp) # For simulate_ogata
intensity(pp::PoissonProcess, m, t, h) = intensity(pp, m)
log_intensity(pp::PoissonProcess, m, t, h) = log_intensity(pp, m)

function ground_intensity_bound(pp::PoissonProcess, t::T, h) where {T<:Real}
B = ground_intensity(pp)
B = pp.λ
L = typemax(T)
return (B, L)
end

function integrated_ground_intensity(pp::PoissonProcess, h, a, b)
return ground_intensity(pp) * (b - a)
return pp.λ * (b - a)
end
17 changes: 15 additions & 2 deletions src/univariate/poisson/simulation.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
function simulate(rng::AbstractRNG, pp::PoissonProcess, tmin::T, tmax::T) where {T<:Real}
times = simulate_poisson_times(rng, ground_intensity(pp), tmin, tmax)
marks = [rand(rng, mark_distribution(pp)) for _ in 1:length(times)]
times = simulate_poisson_times(rng, pp.λ, tmin, tmax)
h_temp = History(times, tmin, tmax)
marks = [sample_mark(pp.mark_dist, t, h_temp) for t in times]
return History(; times=times, marks=marks, tmin=tmin, tmax=tmax)
end

# function simulate(rng::AbstractRNG, pp::PoissonProcess, tmin::T, tmax::T) where {T<:Real}
# h = History(T[], tmin, tmax, eltype(pp.mark_dist)[])
# inter_dist = Exponential(inv(pp.λ))
# t = rand(rng, inter_dist)
# while t < tmax
# m = sample_mark(pp.mark_dist, t, h)
# push!(h, t, m; check_args=False)
# t = rand(rng, inter_dist)
# end
# return h
# end
2 changes: 1 addition & 1 deletion test/bounded_point_process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ h = simulate(rng, bpp)
@test max_time(bpp) == 1000.0
@test ground_intensity(bpp, 0, h) ≈ intensities
@test mark_distribution(bpp, 100.0, h, 1) == Normal()
@test mark_distribution(bpp, 0.0) == fill(Normal(), length(intensities))
@test mark_distribution(bpp, 0.0, h) == fill(Normal(), length(intensities))
@test intensity(bpp, 0.0, 0.0, h, 1) ≈ pdf(Normal(), 0.0) * intensities[1]
@test log_intensity(bpp, 1.0, 1.0, h, 2) ≈ log(pdf(Normal(), 1.0)) + log(intensities[2])

Expand Down
3 changes: 1 addition & 2 deletions test/hypothesis_tests.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
h1 = History([1, 2, 3, 4], 0, 5)
h_empty = History(Float64[], 0, 2)
PP = PoissonProcess{Float32,Dirac{Nothing}}
PP = PoissonProcess{Float32,NoMarks}
pp = PoissonProcess()

@testset "Statistics" begin
@test statistic(KSDistance{Uniform}, pp, h1) ≈ 0.2
@test statistic(KSDistance{Exponential}, pp, h1) ≈ 1 - exp(-1)

@test statistic(KSDistance{Uniform}, pp, h_empty) ≈ 1
@test statistic(KSDistance{Exponential}, pp, h_empty) ≈ 1
end
Expand Down
Loading