diff --git a/src/PointProcesses.jl b/src/PointProcesses.jl index e37f5da..bbfeb02 100644 --- a/src/PointProcesses.jl +++ b/src/PointProcesses.jl @@ -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 @@ -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 @@ -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") diff --git a/src/abstract_point_process.jl b/src/abstract_point_process.jl index 787b7b5..9209ff7 100644 --- a/src/abstract_point_process.jl +++ b/src/abstract_point_process.jl @@ -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) diff --git a/src/history.jl b/src/history.jl index 3502d96..8816694 100644 --- a/src/history.jl +++ b/src/history.jl @@ -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) @@ -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) diff --git a/src/mark_distributions.jl b/src/mark_distributions.jl new file mode 100644 index 0000000..392902f --- /dev/null +++ b/src/mark_distributions.jl @@ -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 diff --git a/src/multivariate/multivariate_poisson_process.jl b/src/multivariate/multivariate_poisson_process.jl index 43a2190..052d3db 100644 --- a/src/multivariate/multivariate_poisson_process.jl +++ b/src/multivariate/multivariate_poisson_process.jl @@ -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( diff --git a/src/simulation.jl b/src/simulation.jl index 08d4fd5..928c265 100644 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -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 @@ -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 diff --git a/src/univariate/poisson/fit.jl b/src/univariate/poisson/fit.jl index 0c34b0e..52ade53 100644 --- a/src/univariate/poisson/fit.jl +++ b/src/univariate/poisson/fit.jl @@ -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} diff --git a/src/univariate/poisson/inhomogeneous/fit.jl b/src/univariate/poisson/inhomogeneous/fit.jl index abba084..a38c1a2 100644 --- a/src/univariate/poisson/inhomogeneous/fit.jl +++ b/src/univariate/poisson/inhomogeneous/fit.jl @@ -81,7 +81,7 @@ function StatsAPI.fit( end function StatsAPI.fit( - ::Type{<:InhomogeneousPoissonProcess{F,Dirac{Nothing}}}, + ::Type{<:InhomogeneousPoissonProcess{F,NoMarks}}, h::History, init_params; integration_config=IntegrationConfig(), @@ -89,7 +89,7 @@ function StatsAPI.fit( ) 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( @@ -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..., @@ -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( diff --git a/src/univariate/poisson/inhomogeneous/inhomogeneous_poisson_process.jl b/src/univariate/poisson/inhomogeneous/inhomogeneous_poisson_process.jl index 0ce8e44..29e027a 100644 --- a/src/univariate/poisson/inhomogeneous/inhomogeneous_poisson_process.jl +++ b/src/univariate/poisson/inhomogeneous/inhomogeneous_poisson_process.jl @@ -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 @@ -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 """ diff --git a/src/univariate/poisson/poisson_process.jl b/src/univariate/poisson/poisson_process.jl index 4b13ad9..1f372fc 100644 --- a/src/univariate/poisson/poisson_process.jl +++ b/src/univariate/poisson/poisson_process.jl @@ -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 @@ -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) @@ -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 diff --git a/src/univariate/poisson/simulation.jl b/src/univariate/poisson/simulation.jl index aa22d8d..63d58c4 100644 --- a/src/univariate/poisson/simulation.jl +++ b/src/univariate/poisson/simulation.jl @@ -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 \ No newline at end of file diff --git a/test/bounded_point_process.jl b/test/bounded_point_process.jl index 494d756..e5ece17 100644 --- a/test/bounded_point_process.jl +++ b/test/bounded_point_process.jl @@ -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]) diff --git a/test/hypothesis_tests.jl b/test/hypothesis_tests.jl index 76cfa9e..e75a203 100644 --- a/test/hypothesis_tests.jl +++ b/test/hypothesis_tests.jl @@ -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 diff --git a/test/inhomogeneous_poisson_process.jl b/test/inhomogeneous_poisson_process.jl index 9d18257..0ce768b 100644 --- a/test/inhomogeneous_poisson_process.jl +++ b/test/inhomogeneous_poisson_process.jl @@ -39,7 +39,6 @@ rng = Random.seed!(12345) @test ground_intensity(pp, 0.0, h_empty) ≈ 1.0 @test ground_intensity(pp, 2.0, h_empty) ≈ 2.0 @test mark_distribution(pp, 0.0, h_empty) isa Normal - @test mark_distribution(pp) isa Normal end @testset "Simulation" begin @@ -791,8 +790,7 @@ end @test pp isa InhomogeneousPoissonProcess @test pp.intensity_function === intensity - @test pp.mark_dist isa Dirac{Nothing} - @test pp.mark_dist.value === nothing + @test pp.mark_dist isa NoMarks @test pp.integration_config isa IntegrationConfig # Test that it simulates correctly @@ -808,7 +806,7 @@ end @test pp isa InhomogeneousPoissonProcess @test pp.intensity_function === intensity - @test pp.mark_dist isa Dirac{Nothing} + @test pp.mark_dist isa NoMarks @test pp.integration_config === custom_config @test pp.integration_config.abstol == 1e-10 @test pp.integration_config.reltol == 1e-10 @@ -837,27 +835,26 @@ end end end -@testset "Fitting - PiecewiseConstant with Dirac (unmarked)" begin +@testset "Fitting - PiecewiseConstant with no marks" begin @testset "Fit with explicit breakpoints vector" begin # Generate data from known piecewise constant process breakpoints_true = [0.0, 3.0, 7.0, 10.0] rates_true = [1.5, 4.0, 2.5] intensity_true = PiecewiseConstantIntensity(breakpoints_true, rates_true) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 10.0) # Fit using same breakpoints pp_est = fit( - InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},NoMarks}, h, breakpoints_true, ) @test pp_est isa InhomogeneousPoissonProcess @test pp_est.intensity_function isa PiecewiseConstantIntensity - @test pp_est.mark_dist isa Dirac{Nothing} - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks @test length(pp_est.intensity_function.rates) == 3 # Check breakpoints are preserved @@ -877,13 +874,13 @@ end # Create uneven breakpoints custom_breakpoints = [0.0, 2.0, 5.0, 12.0] intensity_true = PiecewiseConstantIntensity([0.0, 6.0, 12.0], [3.0, 2.0]) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 12.0) # Fit with different breakpoints than data generation pp_est = fit( - InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},NoMarks}, h, custom_breakpoints, ) @@ -891,7 +888,7 @@ end @test pp_est isa InhomogeneousPoissonProcess @test pp_est.intensity_function.breakpoints == custom_breakpoints @test length(pp_est.intensity_function.rates) == 3 - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks # All rates should be positive @test all(r >= 0 for r in pp_est.intensity_function.rates) @@ -904,7 +901,7 @@ end h = History([4.5, 4.8, 5.2, 5.5, 5.9], 0.0, 10.0) pp_est = fit( - InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},NoMarks}, h, breakpoints, ) @@ -924,19 +921,19 @@ end # Edge case: single bin (constant rate) breakpoints = [0.0, 10.0] intensity_true = PolynomialIntensity([3.0]) # constant - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 10.0) pp_est = fit( - InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float64},NoMarks}, h, breakpoints, ) @test pp_est isa InhomogeneousPoissonProcess @test length(pp_est.intensity_function.rates) == 1 - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks # Rate should be approximately count / duration expected_rate = length(h.times) / 10.0 @@ -949,7 +946,7 @@ end h = History([1.0, 2.0, 6.0, 7.0, 8.0], 0.0, 10.0) pp_est = fit( - InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float32},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PiecewiseConstantIntensity{Float32},NoMarks}, h, breakpoints_f32, ) @@ -959,25 +956,24 @@ end end end -@testset "Fitting - ParametricIntensity with Dirac (unmarked)" begin - @testset "Polynomial intensity with Dirac marks" begin +@testset "Fitting - ParametricIntensity with no marks" begin + @testset "Polynomial intensity with no marks" begin # Generate data from linear process intensity_true = PolynomialIntensity([2.0, 0.3]) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 20.0) # Fit polynomial intensity pp_est = fit( - InhomogeneousPoissonProcess{PolynomialIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PolynomialIntensity{Float64},NoMarks}, h, [2.0, 0.3], ) @test pp_est isa InhomogeneousPoissonProcess @test pp_est.intensity_function isa PolynomialIntensity - @test pp_est.mark_dist isa Dirac{Nothing} - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks # Check parameters are reasonable @test abs(pp_est.intensity_function.coefficients[1] - 2.0) < 1.0 @@ -992,13 +988,13 @@ end @testset "Polynomial intensity with log link" begin # Generate data with log link (ensures positivity) intensity_true = PolynomialIntensity([1.5, 0.05]; link=:log) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 30.0) # Fit with log link pp_est = fit( - InhomogeneousPoissonProcess{PolynomialIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PolynomialIntensity{Float64},NoMarks}, h, [1.5, 0.05]; link=:log, @@ -1006,7 +1002,7 @@ end @test pp_est isa InhomogeneousPoissonProcess @test pp_est.intensity_function.link === :log - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks # Verify positivity over range @test all(pp_est.intensity_function(t) > 0 for t in 0.0:0.5:30.0) @@ -1016,24 +1012,23 @@ end @test abs(pp_est.intensity_function.coefficients[2] - 0.05) < 0.1 end - @testset "Exponential intensity with Dirac marks" begin + @testset "Exponential intensity with no marks" begin # Generate data from exponential process intensity_true = ExponentialIntensity(3.0, 0.08) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 15.0) # Fit exponential intensity pp_est = fit( - InhomogeneousPoissonProcess{ExponentialIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{ExponentialIntensity{Float64},NoMarks}, h, [log(3.0), 0.08], ) @test pp_est isa InhomogeneousPoissonProcess @test pp_est.intensity_function isa ExponentialIntensity - @test pp_est.mark_dist isa Dirac{Nothing} - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks # Check parameters are in reasonable range @test 0.5 * intensity_true.a <= @@ -1045,16 +1040,16 @@ end @test all(pp_est.intensity_function(t) > 0 for t in 0.0:0.5:15.0) end - @testset "Sinusoidal intensity with Dirac marks" begin + @testset "Sinusoidal intensity with no marks" begin # Generate data from sinusoidal process intensity_true = SinusoidalIntensity(6.0, 2.5, 2π, 0.0) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 8.0) # Fit sinusoidal intensity pp_est = fit( - InhomogeneousPoissonProcess{SinusoidalIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{SinusoidalIntensity{Float64},NoMarks}, h, [log(6.0), 0.4, 0.0]; ω=2π, @@ -1062,8 +1057,7 @@ end @test pp_est isa InhomogeneousPoissonProcess @test pp_est.intensity_function isa SinusoidalIntensity - @test pp_est.mark_dist isa Dirac{Nothing} - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks @test pp_est.intensity_function.ω ≈ 2π # Check constraint a >= |b| is satisfied @@ -1080,44 +1074,44 @@ end @testset "Custom integration config" begin # Test that integration_config is properly passed through intensity_true = PolynomialIntensity([2.5, 0.2]) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 20.0) # Fit with custom integration config custom_config = IntegrationConfig(abstol=1e-10, reltol=1e-10, maxiters=5000) pp_est = fit( - InhomogeneousPoissonProcess{PolynomialIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PolynomialIntensity{Float64},NoMarks}, h, [2.5, 0.2]; integration_config=custom_config, ) @test pp_est isa InhomogeneousPoissonProcess - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks # Verify fitting succeeded with custom config @test pp_est.intensity_function isa PolynomialIntensity @test abs(pp_est.intensity_function.coefficients[1] - 2.5) < 1.5 end - @testset "Quadratic polynomial with Dirac marks" begin + @testset "Quadratic polynomial with no marks" begin # Test higher-degree polynomial intensity_true = PolynomialIntensity([1.0, 0.5, 0.05]) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 15.0) # Fit quadratic pp_est = fit( - InhomogeneousPoissonProcess{PolynomialIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PolynomialIntensity{Float64},NoMarks}, h, [1.0, 0.5, 0.05], ) @test pp_est isa InhomogeneousPoissonProcess @test length(pp_est.intensity_function.coefficients) == 3 - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks # Check all coefficients are in reasonable range for i in 1:3 @@ -1130,13 +1124,13 @@ end @testset "Pass through additional kwargs" begin # Test that optimizer kwargs are properly passed through intensity_true = PolynomialIntensity([2.0, 0.1]) - pp_true = InhomogeneousPoissonProcess(intensity_true, Dirac(nothing)) + pp_true = InhomogeneousPoissonProcess(intensity_true, NoMarks()) h = simulate(rng, pp_true, 0.0, 25.0) # Fit with custom optimizer settings pp_est = fit( - InhomogeneousPoissonProcess{PolynomialIntensity{Float64},Dirac{Nothing}}, + InhomogeneousPoissonProcess{PolynomialIntensity{Float64},NoMarks}, h, [2.0, 0.1]; optimizer=LBFGS(), @@ -1145,6 +1139,6 @@ end @test pp_est isa InhomogeneousPoissonProcess @test pp_est.intensity_function isa PolynomialIntensity - @test pp_est.mark_dist.value === nothing + @test pp_est.mark_dist isa NoMarks end end diff --git a/test/poisson_process.jl b/test/poisson_process.jl index 23ea80b..00ea0f0 100644 --- a/test/poisson_process.jl +++ b/test/poisson_process.jl @@ -13,8 +13,8 @@ rng = Random.seed!(63) @test pp2.λ == 1.0 @test pp3.λ == 1.0 @test pp1.mark_dist == Normal() - @test pp2.mark_dist == Dirac(nothing) - @test pp3.mark_dist == Dirac(nothing) + @test pp2.mark_dist == NoMarks() + @test pp3.mark_dist == NoMarks() @test_throws DomainError PoissonProcess(-1.0) end @@ -27,7 +27,7 @@ pp = PoissonProcess(1.0, Normal()) h = History([0.1, 0.5], 0, 1, [0.0, 0.0]) @test ground_intensity(pp, 0, h) == 1.0 - @test mark_distribution(pp) == Normal() + @test mark_distribution(pp, 0.0, h) == Normal() @test intensity(pp, 0.5, 0, h) == 1.0 * pdf(Normal(), 0.5) @test log_intensity(pp, 0.5, 0, h) == log(1.0) + logpdf(Normal(), 0.5) @test integrated_ground_intensity(pp, h, 0.0, 1.0) == 1.0