Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start sequence at zero #35

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Manifest.toml
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Sobol"
uuid = "ed01d8cd-4d21-5b2a-85b4-cc3bdc58bad4"
version = "1.5.0"
version = "2.0.0"

[deps]
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Expand Down
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,9 @@ where `lb` and `ub` are arrays (or other iterables) of length `N`, giving
the lower and upper bounds of the hypercube, respectively. For example,
`SobolSeq([-1,0,0],[1,3,2])` generates points in the box [-1,1]×[0,3]×[0,2]. (Although the generated points are `Float64` by default, in general the precision is determined by that of `lb` and `ub`.)

If you know in advance the number `n` of points that you plan to
generate, some authors suggest that better uniformity can be attained
by first skipping the initial portion of the LDS. In particular,
we skip 2ᵐ−1 for the largest `m` where `2ᵐ-1 ≤ n` (see [Joe and Kuo, 2003][joe03] for a similar suggestion). This
If you know in advance the number `n` of points that you plan to generate,
some authors suggest that skipping the initial portion of the LDS.
In particular, we skip 2ᵐ points for the largest `m` where `2ᵐ ≤ n` (see [Joe and Kuo, 2003][joe03] for a similar suggestion). This
facility is provided by:
```julia
skip(s, n)
Expand All @@ -111,8 +110,15 @@ Skipping exactly `n` elements is also possible:
skip(s, n, exact=true)
```

Note, however, that generally it is not recommended to skip (arbitrary)
number of points since this can ruin the digital net property and lead
to worse convergence (as illustrated in [Owen, 2021](https://arxiv.org/abs/2008.08051) for the
common proactice of dropping the initial point).

`skip` returns `s`, so you can simply do `s = skip(SobolSeq(N))` or similar.

Note, however, that generally it is not recommended to skip arbitrary

## Example

Here is a simple example, generating 1024 points in two dimensions and
Expand All @@ -123,9 +129,12 @@ the [0,1]×[0,1] unit square!
using Sobol
using PyPlot
s = SobolSeq(2)
p = reduce(hcat, next!(s) for i = 1:1024)'
p = Matrix{Float64}(undef, 2, 1024)
for x in eachcol(p)
next!(s, x)
end
subplot(111, aspect="equal")
plot(p[:,1], p[:,2], "r.")
plot(p[1, :], p[2, :], "r.")
```
![plot of 1024 points of a 2d Sobol sequence](sobol1024.png "1024 points of a 2d Sobol sequence")

Expand Down
Binary file modified sobol1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 29 additions & 24 deletions src/Sobol.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,46 +52,51 @@ function next!(s::SobolSeq, x::AbstractVector{<:AbstractFloat})
if s.n == typemax(s.n)
return rand!(x)
end

s.n += one(s.n)
c = UInt32(trailing_zeros(s.n))
sb = s.b
sx = s.x
sm = s.m
for i=1:ndims(s)
@inbounds b = sb[i]
# note: ldexp on Float64(sx[i]) is exact, independent of precision of x[i]
if b >= c
@inbounds sx[i] = sx[i] ⊻ (sm[i,c+1] << (b-c))
@inbounds x[i] = ldexp(Float64(sx[i]), ((~b) % Int32))
else
@inbounds sx[i] = (sx[i] << (c-b)) ⊻ sm[i,c+1]
@inbounds sb[i] = c
@inbounds x[i] = ldexp(Float64(sx[i]), ((~c) % Int32))

if iszero(s.n)
# Initial point
fill!(x, 0.0)
else
# Subsequent points
c = UInt32(trailing_zeros(s.n))
sb = s.b
sx = s.x
sm = s.m
for i=1:ndims(s)
@inbounds b = sb[i]
# note: ldexp on Float64(sx[i]) is exact, independent of precision of x[i]
if b >= c
@inbounds sx[i] = sx[i] ⊻ (sm[i,c+1] << (b-c))
@inbounds x[i] = ldexp(Float64(sx[i]), ((~b) % Int32))
else
@inbounds sx[i] = (sx[i] << (c-b)) ⊻ sm[i,c+1]
@inbounds sb[i] = c
@inbounds x[i] = ldexp(Float64(sx[i]), ((~c) % Int32))
end
end
end

s.n += one(s.n)

return x
end
next!(s::SobolSeq) = next!(s, Array{Float64,1}(undef, ndims(s)))

# if we know in advance how many points (n) we want to compute, then
# adopt a suggestion similar to the Joe and Kuo paper, which in turn
# is taken from Acworth et al (1998), of skipping a number of
# points one less than the largest power of 2 smaller than n+1.
# we can adopt a suggestion similar to Joe and Kuo (2003), which in turn
# is taken from Acworth et al (1998), of skipping a number of initial
# points. The number of points is the largest power of 2 smaller than n+1.
# if exactly n points are to be skipped, use the keyword exact=true.
# (Ackworth and Joe and Kuo seem to suggest skipping exactly
# a power of 2, but skipping 1 less seems to produce much better
# results: issue #21.)
#
# skip!(s, n) skips 2^m - 1 such that 2^m < n 2^(m+1)
# skip!(s, n) skips 2^m such that 2^m n < 2^(m+1)
# skip!(s, n, exact=true) skips m = n

function skip!(s::SobolSeq, n::Integer, x; exact=false)
if n ≤ 0
n == 0 && return s
throw(ArgumentError("$n is not non-negative"))
end
nskip = exact ? n : (1 << floor(Int,log2(n+1)) - 1)
nskip = exact ? n : prevpow(2, n)
for unused=1:nskip; next!(s,x); end
return s
end
Expand Down
17 changes: 11 additions & 6 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ using Sobol, Test
for line in eachline(exp_results_file)
values = [parse(Float64, item) for item in split(line)]
if length(values) > 0
@test x == values
next!(s, x)
@test x == values
end
end
end
Expand All @@ -32,7 +32,7 @@ end
using Base.Iterators: take
@testset "iterators" begin
# issue #8
@test [x[1] for x in collect(take(Sobol.SobolSeq(1),5))] == [0.5,0.75,0.25,0.375,0.875]
@test [x[1] for x in collect(take(Sobol.SobolSeq(1),5))] == [0.0,0.5,0.75,0.25,0.375]
end

@testset "scaled" begin
Expand All @@ -44,8 +44,13 @@ end
@test s isa ScaledSobolSeq{3,Int}
@test eltype(s) == Vector{Float64}
@test eltype(SobolSeq(Float32.(lb),Float32.(ub))) == Vector{Float32}
@test first(s) == [0,1.5,1]
@test first(SobolSeq((x for x in lb), (x for x in ub))) == [0,1.5,1]
@test next!(s) == [-1,0,0]
@test next!(s) == [0,1.5,1]

s = SobolSeq((x for x in lb), (x for x in ub))
@test next!(s) == [-1,0,0]
@test next!(s) == [0,1.5,1]

@test SobolSeq(N,lb,ub) isa ScaledSobolSeq{3,Int}
@test_throws BoundsError SobolSeq(2,lb,ub)
end
Expand All @@ -71,10 +76,10 @@ end
p = map(_->next!(s), 1:16)
sort!(map(x -> sum((x .≥ 0.5) .* (2 .^ (0:3))), p)) == 0:15

for n in (7,8)
for n in (7,8,9)
s = skip(SobolSeq(2), n)
p = [next!(s) for _ = 1:n]
s2 = skip(SobolSeq(2), 7, exact=true)
s2 = skip(SobolSeq(2), n == 7 ? 4 : 8, exact=true)
p2 = [next!(s2) for _ = 1:n]
@test p == p2
end
Expand Down