Skip to content

Commit 385f824

Browse files
authored
Merge pull request #26 from Eliassj/master
Merge ARSampling.jl (JuliaRegistries/General/151430)
2 parents 8653023 + a37c3da commit 385f824

14 files changed

Lines changed: 1811 additions & 376 deletions

.github/workflows/Docs.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Docs
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- "docs/**"
9+
- "src/**"
10+
- "*.toml"
11+
tags: ["*"]
12+
pull_request:
13+
branches:
14+
- master
15+
paths:
16+
- "docs/**"
17+
- "src/**"
18+
- "*.toml"
19+
types: [opened, synchronize, reopened]
20+
21+
concurrency:
22+
# Skip intermediate builds: always.
23+
# Cancel intermediate builds: only if it is a pull request build.
24+
group: ${{ github.workflow }}-${{ github.ref }}
25+
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
26+
27+
jobs:
28+
docs:
29+
permissions:
30+
actions: write
31+
contents: write
32+
pull-requests: read
33+
statuses: write
34+
name: Documentation
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v6
38+
- uses: julia-actions/setup-julia@v2
39+
with:
40+
version: "1"
41+
- name: Use Julia cache
42+
uses: julia-actions/cache@v2
43+
- name: Instantiate environment with development version of the package
44+
run: |
45+
julia --project=docs -e '
46+
using Pkg
47+
Pkg.develop(PackageSpec(path=pwd()))
48+
Pkg.instantiate()'
49+
- name: Run doctest
50+
run: |
51+
julia --project=docs -e '
52+
using Documenter: DocMeta, doctest
53+
using ARSampling
54+
DocMeta.setdocmeta!(ARSampling, :DocTestSetup, :(using ARSampling); recursive=true)
55+
doctest(ARSampling)'
56+
- name: Generate and deploy documentation
57+
run: julia --project=docs docs/make.jl
58+
env:
59+
JULIA_PKG_SERVER: ""
60+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61+
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
62+
GKSwstype: "100" # https://discourse.julialang.org/t/generation-of-documentation-fails-qt-qpa-xcb-could-not-connect-to-display/60988

.github/workflows/ReusableTest.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Reusable test
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
version:
7+
required: false
8+
type: string
9+
default: "1"
10+
os:
11+
required: false
12+
type: string
13+
default: ubuntu-latest
14+
arch:
15+
required: false
16+
type: string
17+
default: x64
18+
allow_failure:
19+
required: false
20+
type: boolean
21+
default: false
22+
run_codecov:
23+
required: false
24+
type: boolean
25+
default: false
26+
secrets:
27+
codecov_token:
28+
required: true
29+
30+
jobs:
31+
test:
32+
name: Julia ${{ inputs.version }} - ${{ inputs.os }} - ${{ inputs.arch }} - ${{ github.event_name }}
33+
runs-on: ${{ inputs.os }}
34+
continue-on-error: ${{ inputs.allow_failure }}
35+
36+
steps:
37+
- uses: actions/checkout@v6
38+
- uses: julia-actions/setup-julia@v2
39+
with:
40+
version: ${{ inputs.version }}
41+
arch: ${{ inputs.arch }}
42+
- name: Use Julia cache
43+
uses: julia-actions/cache@v2
44+
- uses: julia-actions/julia-buildpkg@v1
45+
- uses: julia-actions/julia-runtest@v1
46+
- uses: julia-actions/julia-processcoverage@v1
47+
if: ${{ inputs.run_codecov }}
48+
- uses: codecov/codecov-action@v4
49+
if: ${{ inputs.run_codecov }}
50+
with:
51+
file: lcov.info
52+
token: ${{ secrets.codecov_token }}

.github/workflows/Test.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
tags: ["*"]
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
uses: ./.github/workflows/ReusableTest.yml
13+
with:
14+
os: ${{ matrix.os }}
15+
version: ${{ matrix.version }}
16+
arch: ${{ matrix.arch }}
17+
allow_failure: ${{ matrix.allow_failure }}
18+
run_codecov: ${{ matrix.version == '1' && matrix.os == 'ubuntu-latest' }}
19+
secrets:
20+
codecov_token: ${{ secrets.CODECOV_TOKEN }}
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
version:
25+
- "lts"
26+
- "1"
27+
os:
28+
- ubuntu-latest
29+
- macOS-latest
30+
31+
- windows-latest
32+
33+
arch:
34+
- x64
35+
allow_failure: [false]

Project.toml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,27 @@ uuid = "c75e803d-635f-53bd-ab7d-544e482d8c75"
33
version = "0.2.1"
44

55
[deps]
6+
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
7+
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
68
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
79
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
10+
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
811
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
912

13+
[weakdeps]
14+
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
15+
1016
[compat]
11-
ForwardDiff = "0.10.1"
12-
StatsBase = "0.24, 0.34"
13-
julia = "0.7.0, 1"
17+
Compat = "4.18.1"
18+
DifferentiationInterface = "0.7"
19+
ForwardDiff = "1"
20+
Makie = "0.24.9"
21+
SpecialFunctions = "2"
22+
StatsBase = "0.34.10"
23+
julia = "1"
24+
25+
[extensions]
26+
MakieExtension = "Makie"
1427

1528
[extras]
1629
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

README.md

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,62 @@ This package is useful for efficient sampling from log-concave univariate densit
1212
## Examples
1313

1414
```julia
15-
using AdaptiveRejectionSampling
16-
using Plots
15+
using AdaptiveRejectionSampling: Objective, ARSampler, sample!, eval_hull, abscissae
16+
using SpecialFunctions: gamma
17+
using CairoMakie
1718
```
1819

19-
2020
### Sampling from a shifted normal distribution
2121

2222

2323
```julia
24-
# Define function to be sampled
25-
μ, σ = 1.0, 2.0
26-
f(x) = exp(-0.5(x - μ)^2 / σ^2) / sqrt(2pi * σ^2)
27-
support = (-Inf, Inf)
24+
const μ, σ = 1.0, 2.0
2825

29-
# Build the sampler and simulate 10,000 samples
30-
sampler = RejectionSampler(f, support, max_segments = 5)
31-
@time sim = run_sampler!(sampler, 10000);
26+
function bench()
27+
# Define function to be sampled
28+
f(x) = log(exp(-0.5(x - μ)^2 / σ^2) / sqrt(2pi * σ^2))
29+
support = (-10., 10.)
30+
31+
# Build the sampler and simulate 10,000 samples
32+
obj = Objective(f)
33+
sampler = ARSampler(obj, support)
34+
b = @be deepcopy(sampler) sample!(_, 10000, true, 10);
35+
return sampler, b
36+
end
37+
s, b = bench();
38+
b
3239
```
3340

34-
0.010434 seconds (192.15 k allocations: 3.173 MiB)
35-
41+
Benchmark: 126 samples with 1 evaluation
42+
min 616.951 μs (17 allocs: 80.523 KiB)
43+
median 641.532 μs (17 allocs: 80.523 KiB)
44+
mean 749.812 μs (17.10 allocs: 80.625 KiB, 0.75% gc time)
45+
max 13.744 ms (20 allocs: 83.711 KiB, 94.06% gc time)
3646

3747
Let's verify the result
3848

3949

4050
```julia
4151
# Plot the results and compare to target distribution
52+
f(x) = log(exp(-0.5(x - μ)^2 / σ^2) / sqrt(2pi * σ^2))
4253
x = range(-10.0, 10.0, length=100)
43-
envelop = [eval_envelop(sampler.envelop, xi) for xi in x]
44-
target = [f(xi) for xi in x]
45-
46-
histogram(sim, normalize = true, label = "Histogram")
47-
plot!(x, [target envelop], width = 2, label = ["Normal(μ, σ)" "Envelop"])
54+
envelop = eval_hull.(s.upper_hull, x)
55+
target = f.(x)
56+
samples = sample!(s, 10000, max_segments = 5)
57+
58+
fig, ax, p = hist(samples, bins=25, normalization = :pdf, label = "Samples");
59+
lines!(ax, -10..10, x -> exp(f(x)), label = "Target", color = :orange)
60+
lines!(ax, x, exp.(envelop), label = "Envelop", color = :black, alpha = 0.8)
61+
absc = ARS.abscissae(s.upper_hull)
62+
scatter!(ax, absc, exp.(eval_hull.(s.upper_hull, absc)), label = "Abscissae", color = :red)
63+
axislegend(ax)
64+
fig
65+
# histogram(sim, normalize = true, label = "Histogram")
66+
# plot!(x, [target envelop], width = 2, label = ["Normal(μ, σ)" "Envelop"])
4867
```
4968

5069

51-
![](img/example1.png)
70+
![](img/example1.svg)
5271

5372

5473
### Let's try a Gamma
@@ -59,24 +78,28 @@ plot!(x, [target envelop], width = 2, label = ["Normal(μ, σ)" "Envelop"])
5978
f(x) = β^α * x^-1) * exp(-β*x) / gamma(α)
6079
support = (0.0, Inf)
6180

62-
# Build the sampler and simulate 10,000 samples
63-
sampler = RejectionSampler(f, support)
64-
@time sim = run_sampler!(sampler, 10000)
65-
66-
# Plot the results and compare to target distribution
67-
x = range(0.0, 5.0, length=100)
68-
envelop = [eval_envelop(sampler.envelop, xi) for xi in x]
69-
target = [f(xi) for xi in x]
70-
71-
histogram(sim, normalize = true, label = "Histogram")
72-
plot!(x, [target envelop], width = 2, label = ["Gamma(α, β)" "Envelop"])
81+
obj = Objective(x -> log(f(x)))
82+
sam = ARSampler(obj, support)
83+
@time sim = sample!(sam, 10000, max_segments = 5)
84+
85+
# Verify result
86+
x = range(0.0, 8.0, length=100)
87+
envelop = eval_hull.(sam.upper_hull, x)
88+
target = f.(x)
89+
mx = maximum(sim)
90+
fig, ax, p = hist(sim, bins=50, normalization = :pdf, label = "Samples");
91+
lines!(ax, 0..mx, y -> f(y), label = "Target", color = :orange)
92+
lines!(ax, 0..mx, y -> exp(eval_hull(sam.upper_hull, y)), label = "Envelop", color = :black, alpha = 0.8)
93+
absc = abscissae(sam.upper_hull)
94+
scatter!(ax, absc, exp.(eval_hull.(sam.upper_hull, absc)), label = "Abscissae", color = :red)
95+
axislegend(ax)
96+
fig
7397
```
7498

75-
0.007299 seconds (182.00 k allocations: 3.027 MiB)
76-
99+
0.000772 seconds (100 allocations: 85.211 KiB)
77100

78101

79-
![](img/example2.png)
102+
![](img/example2.svg)
80103

81104
### Truncated distributions and unknown normalization constant
82105

ext/MakieExtension.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
module MakieExtension
2+
using Makie
3+
import Makie: mixin_colormap_attributes
4+
using AdaptiveRejectionSampling
5+
import AdaptiveRejectionSampling: hullplot, hullplot!, eval_hull, abscissae, ARSampler
6+
7+
@recipe Hullplot begin
8+
abscissae = true
9+
target = true
10+
upper_hull = true
11+
lower_hull = true
12+
target_linewidth = @inherit linewidth
13+
upper_hull_linewidth = @inherit linewidth
14+
lower_hull_linewidth = @inherit linewidth
15+
target_label = "Target"
16+
upper_hull_label = "Upper hull"
17+
lower_hull_label = "Lower hull"
18+
abscissae_label = "Abscissae"
19+
end
20+
21+
22+
function Makie.plot!(p::Hullplot{<:Tuple{<:Any, <:ARSampler}})
23+
24+
sam = p[2][]
25+
upper_hull = sam.upper_hull
26+
lower_hull = sam.lower_hull
27+
obj = sam.objective
28+
xs = p[1]
29+
30+
i = 1
31+
if p.lower_hull[]
32+
lines!(
33+
p,
34+
xs,
35+
x -> exp(eval_hull(lower_hull, x)),
36+
color=Cycled(i),
37+
linewidth = p.lower_hull_linewidth,
38+
label = p.lower_hull_label
39+
)
40+
i += 1
41+
end
42+
if p.upper_hull[]
43+
lines!(
44+
p,
45+
xs,
46+
x -> exp(eval_hull(upper_hull, x)),
47+
linewidth = p.upper_hull_linewidth,
48+
color=Cycled(i),
49+
label = p.upper_hull_label
50+
)
51+
i += 1
52+
end
53+
if p.target[]
54+
lines!(
55+
p,
56+
xs,
57+
x -> exp(obj.f(x)),
58+
color=Cycled(i),
59+
label = p.target_label,
60+
linewidth = p.target_linewidth
61+
)
62+
i += 1
63+
end
64+
if p.abscissae[]
65+
absc = abscissae(upper_hull)
66+
scatter!(
67+
p,
68+
absc,
69+
exp.(eval_hull.(upper_hull, absc)),
70+
color = Cycled(i),
71+
label = p.abscissae_label
72+
)
73+
end
74+
75+
end
76+
77+
function Makie.get_plots(plot::Hullplot)
78+
return plot.plots
79+
end
80+
81+
end # End of module

0 commit comments

Comments
 (0)