Skip to content

Commit f2f5cc0

Browse files
committed
Restructure and fix hard_core_spatial
1 parent da9b9ce commit f2f5cc0

File tree

7 files changed

+150
-112
lines changed

7 files changed

+150
-112
lines changed

src/PartialRejectionSampling.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ include("dominated_cftp.jl")
3131
include("grid_prs.jl")
3232

3333
# Spatial point processes
34+
include("poisson.jl")
3435
include("strauss.jl")
3536
include("hard_core_spatial.jl")
3637

src/dominated_cftp.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,35 @@
33
# - Huber, Perfect Simulation
44
# - [Kendall's notes on perfect simulation](https://warwick.ac.uk/fac/sci/statistics/staff/ academic-research/kendall/personal/ppt/428.pdf)
55
#
6-
# Requirement: the target spatial point process must have the followingmethods
6+
# Requirement: the target spatial point process must have the following methods
77
# - function papangelou_conditional_intensity end
88
# - function upper_bound_papangelou_conditional_intensity end
99
# - function window end
1010

1111
function generate_sample_dcftp(
1212
pp::AbstractSpatialPointProcess{T};
13-
n₀::Int64=1,
13+
n₀::Int=1,
1414
win::Union{Nothing,AbstractWindow}=nothing,
1515
rng=-1
1616
)::Vector{T} where {T}
1717

1818
@assert n₀ >= 1
1919
rng = getRNG(rng)
2020

21-
win_ = win === nothing ? window(pp) : win
21+
window_ = win === nothing ? window(pp) : win
2222
β = upper_bound_papangelou_conditional_intensity(pp)
23-
birth_rate = β * volume(win_)
23+
birth_rate = β * volume(window_)
2424

2525
# Dominating process
26-
k = rand(rng, Distributions.Poisson(birth_rate))
27-
D = Set{T}(rand(win_; rng=rng) for _ in 1:k)
26+
hp = HomogeneousPoissonPointProcess(β, window_)
27+
D = Set{T}(eachcol(generate_sample(hp; rng=rng)))
2828

2929
M = Float64[] # Marking process
3030
R = T[] # Recording process
3131

3232
steps = -1:-1:-n₀
3333
while true
34-
backward_update!(D, M, R, steps, birth_rate, win_; rng=rng)
34+
backward_update!(D, M, R, steps, birth_rate, window_; rng=rng)
3535
coupling, L = forward_coupling(D, M, R, pp, β)
3636
coupling && return collect(L)
3737
steps = (steps.stop-1):-1:(2*steps.stop)
@@ -44,7 +44,7 @@ function backward_update!(
4444
R::Vector{T}, # Recording process
4545
steps::StepRange, # Number of backward steps
4646
birth_rate::Real,
47-
window::AbstractWindow;
47+
win::AbstractWindow;
4848
rng=-1
4949
) where {T}
5050
rng = getRNG(rng)
@@ -58,7 +58,7 @@ function backward_update!(
5858
pushfirst!(M, rand(rng))
5959
else
6060
# forward birth (push) ≡ backward death (pushfirst)
61-
x = rand(window; rng)
61+
x = rand(win; rng=rng)
6262
push!(D, x)
6363
pushfirst!(R, x)
6464
pushfirst!(M, 0.0)
@@ -69,9 +69,9 @@ end
6969
function forward_coupling(
7070
D::Set{T}, # Dominating process
7171
M::Vector{Float64}, # Marking process
72-
R::Vector{T}, # Recording process
72+
R::Vector{T}, # Recording process
7373
pp::AbstractSpatialPointProcess{T},
74-
β::Real # Upper bound on papangelou conditional intensity
74+
β::Real # Upper bound on papangelou conditional intensity
7575
) where {T}
7676
# L ⊆ X ⊆ U ⊆ D, where X is the target process
7777
L, U = empty(D), copy(D)

src/hard_core_spatial.jl

Lines changed: 6 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ function HardCorePointProcess(β, r, c, w)
3030
return HardCorePointProcess(β, r, spatial_window(c, w))
3131
end
3232

33-
window(hc::HardCorePointProcess) = hc.window
34-
dimension(hc::HardCorePointProcess) = dimension(window(hc))
35-
3633
## Sampling
3734

3835
# Used in dominated CFTP, see dominated_cftp.jl
@@ -62,14 +59,10 @@ function gibbs_interaction(
6259
return !any(any(Distances.euclidean(x, y) <= hc.r for x in c_i) for y in c_j)
6360
end
6461

65-
# Partial Rejection Sampling
66-
# The implementation relies on Distances.pairwise, points are stored as columns of a Matrix.
62+
# Partial Rejection sampling
6763

6864
"""
6965
Sample from spatial hard core model a.k.a Poisson disk sampling with intensity λ and radius r on [0, 1]^2, using Partial Rejection Sampling [https://arxiv.org/pdf/1801.07342.pdf](H. Guo, M. Jerrum).
70-
!!! warning "Side effects"
71-
72-
In the current implementation, the return sample may contain some point outside of the box! (see generate_sample_poisson_union_disks)
7366
"""
7467
function generate_sample_prs(
7568
hc::HardCorePointProcess{T};
@@ -80,83 +73,20 @@ function generate_sample_prs(
8073
rng = getRNG(rng)
8174
window_ = win === nothing ? window(hc) : win
8275

83-
n = rand(rng, Distributions.Poisson(hc.β * volume(window_)))
84-
points = Matrix{Float64}(undef, dimension(hc), n)
85-
for x in eachcol(points)
86-
x .= rand(window_; rng=rng)
87-
end
76+
points = generate_sample(HomogeneousPoissonPointProcess(hc.β, window_); rng=rng)
8877

8978
while true
9079
bad = vec(any(pairwise_distances(points) .< hc.r, dims=2))
9180
!any(bad) && break
9281

93-
resampled = generate_sample_poisson_union_disks(hc.β, hc.r, points[:, bad]; rng=rng)
82+
resampled = generate_sample_poisson_union_balls(hc.β, points[:, bad], hc.r;
83+
win=window_, rng=rng)
9484
points = hcat(points[:, .!bad], resampled)
9585
end
96-
return [points[:, i] for i in 1:size(points, 2)]
97-
end
98-
99-
"""
100-
Sample from Poisson(λ) on ⋃ᵢ B(cᵢ, r) (union of disks of radius r centered at cᵢ)
101-
102-
Use the independence property of the Poisson point process
103-
104-
- Sample from Poisson(λ) on B(c₁, r)
105-
- Sample from Poisson(λ) on B(c₂, r) ∖ B(c₁, r)
106-
- Sample from Poisson(λ) on B(cⱼ, r) ∖ ⋃_i<j B(cᵢ, r)
107-
- ...
108-
"""
109-
function generate_sample_poisson_union_disks(
110-
λ::Real,
111-
r::Real,
112-
centers::Matrix;
113-
rng=-1
114-
)::Matrix{Float64}
115-
rng = getRNG(rng)
116-
𝒫 = Distributions.Poisson* π * r^2)
117-
points = Matrix{Float64}(undef, 2, 0)
118-
for (i, c) in enumerate(eachcol(centers))
119-
n = rand(rng, 𝒫)
120-
n == 0 && continue
121-
proposed_points = generate_sample_uniform_in_disk(n, c, r; rng=rng)
122-
if i == 1
123-
points = hcat(points, proposed_points)
124-
continue
125-
end
126-
centers_ = @view centers[:, 1:i-1]
127-
accept = vec(all(pairwise_distances(centers_, proposed_points) .> r, dims=1))
128-
points = hcat(points, proposed_points[:, accept])
129-
end
130-
return points
131-
end
132-
133-
"""
134-
generate_sample_uniform_in_disk(n, center, radius,
135-
136-
Sample `n` points uniformly in ``D(c, r)`` (disk with `center` and `radius`)
137-
"""
138-
function generate_sample_uniform_in_disk(
139-
n::Integer,
140-
center::AbstractVector,
141-
radius::Real;
142-
rng=-1
143-
)::Matrix{Float64}
144-
@assert radius > 0
145-
@assert length(center) == 2
146-
147-
n == 0 && return Matrix{Float64}(undef, 2, 0)
148-
rng = getRNG(rng)
149-
150-
sample = repeat(center, 1, n)
151-
for x in eachcol(sample)
152-
r = radius * sqrt(rand(rng))
153-
theta = 2π * rand(rng)
154-
x .+= [r * cos(theta), r * sin(theta)]
155-
end
156-
return sample
86+
return collect(T, eachcol(points))
15787
end
15888

159-
# Default sample generate_sample
89+
# Default sampler generate_sample
16090

16191
"""
16292
Default sampler for HardCorePointProcess

src/poisson.jl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
struct HomogeneousPoissonPointProcess{T} <: AbstractSpatialPointProcess{T}
2+
β::Float64
3+
window::AbstractSpatialWindow{Float64}
4+
end
5+
6+
function HomogeneousPoissonPointProcess::Real, window::AbstractSpatialWindow)
7+
@assert β > 0
8+
return HomogeneousPoissonPointProcess{Vector{Float64}}(β, window)
9+
end
10+
11+
## Sampling
12+
13+
"""
14+
Sample from a homogenous Poisson point process `hp` on window `win` for which the `volume` function is defined.
15+
"""
16+
function generate_sample(
17+
hp::HomogeneousPoissonPointProcess;
18+
win::Union{Nothing,AbstractWindow}=nothing,
19+
rng=-1
20+
)::Matrix{Float64}
21+
rng = getRNG(rng)
22+
win_ = win === nothing ? window(hp) : win
23+
n = rand(rng, Distributions.Poisson(hp.β * volume(win_)))
24+
return rand(win_, n; rng=rng)
25+
end
26+
27+
"""
28+
Sample from a homogenous Poisson point process Poisson(β) on ⋃ᵢ B(cᵢ, r) (union of balls centered at cᵢ with the same radius r)
29+
30+
Use the independence property of the Poisson point process on disjoint subsets in order to
31+
32+
- Sample from Poisson(β) on B(c₁, r)
33+
- Sample from Poisson(β) on B(c₂, r) ∖ B(c₁, r)
34+
- Sample from Poisson(β) on B(cⱼ, r) ∖ ⋃_i<j B(cᵢ, r)
35+
- ...
36+
"""
37+
function generate_sample_poisson_union_balls(
38+
β::Real,
39+
centers::Matrix,
40+
radius::Real;
41+
win::Union{Nothing,AbstractWindow}=nothing,
42+
rng=-1
43+
)::Matrix{Float64}
44+
rng = getRNG(rng)
45+
d = size(centers, 1)
46+
!(win === nothing) && @assert dimension(win) == d
47+
48+
𝒫 = Distributions.Poisson* volume(BallWindow(zeros(d), radius)))
49+
points = Matrix{Float64}(undef, d, 0)
50+
for (i, c) in enumerate(eachcol(centers))
51+
n = rand(rng, 𝒫)
52+
n == 0 && continue
53+
proposed = rand(BallWindow(c, radius), n; rng=rng)
54+
if i == 1
55+
points = hcat(points, proposed)
56+
continue
57+
end
58+
centers_ = @view centers[:, 1:i-1]
59+
accept = vec(all(pairwise_distances(centers_, proposed) .> radius, dims=1))
60+
points = hcat(points, proposed[:, accept])
61+
end
62+
63+
if win === nothing || isempty(points)
64+
return points
65+
else
66+
return points[:, vec(mapslices(x -> x in win, points; dims=1))]
67+
end
68+
end

src/strauss.jl

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,9 @@ function StraussPointProcess(β::Real, γ::Real, r::Real, window::AbstractSpatia
2323
@assert β > 0
2424
@assert 0 <= γ <= 1
2525
@assert r > 0
26-
return StraussPointProcess{Vector{Float64}}(float(β), float(γ), float(r), window)
26+
return StraussPointProcess{Vector{Float64}}(β, γ, r, window)
2727
end
2828

29-
function StraussPointProcess(β, γ, r, c, w)
30-
return StraussPointProcess(β, γ, r, spatial_window(c, w))
31-
end
32-
33-
window(strauss::StraussPointProcess) = strauss.window
34-
dimension(strauss::StraussPointProcess) = dimension(window(strauss))
35-
3629
## Sampling
3730

3831
# Used in dominated_cftp

src/utils.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
getRNG(seed::Integer = -1) = seed >= 0 ? Random.MersenneTwister(seed) : Random.GLOBAL_RNG
1+
getRNG(seed::Integer=-1) = seed >= 0 ? Random.MersenneTwister(seed) : Random.GLOBAL_RNG
22
getRNG(seed::Union{Random.MersenneTwister,Random._GLOBAL_RNG}) = seed
33

44
sigmoid(x) = @. 1 / (1 + exp(-x))
@@ -13,6 +13,13 @@ sigmoid(x) = @. 1 / (1 + exp(-x))
1313
return true
1414
end
1515

16+
function normalize_columns!(X::Matrix, p::Real=2)
17+
for x in eachcol(X)
18+
LA.normalize!(x, p)
19+
end
20+
return X
21+
end
22+
1623
## Graph functions
1724

1825
edgemap(g::LG.AbstractGraph) = Dict(LG.edges(g) .=> 1:LG.ne(g))

0 commit comments

Comments
 (0)