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

Randomized qmc #57

Merged
merged 67 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
00830b2
add include and export from RQMC
dmetivie Nov 22, 2022
646b3cc
scrambling methods
dmetivie Nov 22, 2022
b8b5329
shifting methods
dmetivie Nov 22, 2022
ab44dd4
test if RQMC functions are working
dmetivie Nov 22, 2022
e73b2e4
Add ranmization section with example of current API
dmetivie Nov 22, 2022
563d554
fix bug in unif2bits + Type in bits2unif
dmetivie Nov 22, 2022
9f3a39e
test conversion unif2bits and bits2unif
dmetivie Nov 22, 2022
74d751d
update readme
dmetivie Nov 24, 2022
49be1e2
update Owen link
dmetivie Nov 24, 2022
b99865c
Delete .vscode directory
dmetivie Nov 24, 2022
ab885ba
Update README.md
ChrisRackauckas Nov 27, 2022
fc461d0
Merge branch 'SciML:master' into randomizedQMC
dmetivie Nov 28, 2022
e4c995f
Merge branch 'SciML:master' into randomizedQMC
dmetivie Nov 29, 2022
7e96838
format
ChrisRackauckas Nov 29, 2022
8a8eeac
comment about explicit Type in bits2unif
dmetivie Nov 30, 2022
6e2f76d
Merge branch 'randomizedQMC' of https://github.com/dmetivie/QuasiMont…
dmetivie Nov 30, 2022
ea84fb6
Merge branch 'master' into randomizedQMC
ParadaCarleton Dec 15, 2022
25ddd5c
struc of rand
dmetivie Dec 16, 2022
e2ada4e
Merge branch 'randomizedQMC' of https://github.com/dmetivie/QuasiMont…
dmetivie Dec 16, 2022
f94acf6
Merge branch 'SciML:master' into randomizedQMC
dmetivie Dec 16, 2022
a64f7b7
correction in function doc
dmetivie Dec 31, 2022
a21296b
in place version
dmetivie Dec 31, 2022
3080969
fix the convention n×d to d×n
dmetivie Jan 1, 2023
933c7f8
add rng to shift
dmetivie Jan 1, 2023
1e253f3
naming and typing
dmetivie Jan 1, 2023
fcadc55
add coma in export
dmetivie Jan 3, 2023
9279ed8
doc misprint
dmetivie Jan 3, 2023
0e07566
doc misprint
dmetivie Jan 3, 2023
f9658f0
Merge branch 'randomizedQMC' of https://github.com/dmetivie/QuasiMont…
dmetivie Jan 3, 2023
6facc73
mv DigitalShift into scrambling
dmetivie Jan 3, 2023
ab17bed
change API adding randomization function + methods
dmetivie Jan 3, 2023
bd94bae
naming
dmetivie Jan 3, 2023
e640045
Add randomize to Faure sample and struct
dmetivie Jan 3, 2023
f7e5f6c
fix typo
dmetivie Jan 3, 2023
3c6d39b
formatting
dmetivie Jan 3, 2023
762e110
formatting
dmetivie Jan 3, 2023
e8a591f
Merge branch 'randomizedQMC' of https://github.com/dmetivie/QuasiMont…
dmetivie Jan 3, 2023
2df85e7
apply JuliaFormatter
dmetivie Jan 4, 2023
445b8f8
cleaning
dmetivie Jan 4, 2023
4ad2f52
fix big typo!
dmetivie Jan 4, 2023
2a69efa
similar -> copy avoid error permutedims(#undef)
dmetivie Jan 4, 2023
5050730
add istms net function and test
dmetivie Jan 5, 2023
9859057
format
dmetivie Jan 5, 2023
03e46b3
fix(?) type in ScambleMethod
dmetivie Jan 9, 2023
c2fffdd
fix rand(0:b-1) in M>m Matoussek
dmetivie Jan 9, 2023
ffb13ad
force matrix and types in randomize!
dmetivie Jan 9, 2023
ede1984
fix iteration typo in DigitalShift
dmetivie Jan 9, 2023
55ff172
type in randomize! et co
dmetivie Jan 9, 2023
e80a2f2
rewrite Digital shift
dmetivie Jan 9, 2023
0f70820
change API for randomize_bit
dmetivie Jan 9, 2023
fb1a6cd
change struct RandomizationMethod field S -> R
dmetivie Jan 10, 2023
1e9dd2e
rm unused rng
dmetivie Jan 10, 2023
0249e6c
add Lattice randomize method field
dmetivie Jan 10, 2023
6ae1a0b
improve(?) typing in shift.jl
dmetivie Jan 10, 2023
080bace
add default randmethod to lattice
dmetivie Jan 10, 2023
5364d89
rebase generate_design_matrices
dmetivie Jan 10, 2023
5942843
add some doc
dmetivie Jan 11, 2023
3a25a7e
fix docstring
dmetivie Jan 11, 2023
092da9c
change keyword M to pad as in Base.digits
dmetivie Jan 11, 2023
a09042b
~3 to ~8 time speed up in function unif2bits
dmetivie Jan 11, 2023
d5730ea
Update README.md
ChrisRackauckas Jan 13, 2023
1f19588
update randomize part of readme
dmetivie Jan 16, 2023
bc70815
update and organize readme
dmetivie Jan 16, 2023
b1a45c0
change the generating_design_matrices methods
dmetivie Jan 17, 2023
96b1ec0
change i readme and docs
dmetivie Jan 17, 2023
f3068a3
add various comments + test LHS is tms net
dmetivie Jan 23, 2023
d49cefa
rm istmsnet from pkg to put in test
dmetivie Jan 24, 2023
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
95 changes: 95 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,98 @@ function sample(n,lb,ub,::NewAmazingSamplingAlgorithm)
end
end
```

## Randomization

API is meant to evolve.
ChrisRackauckas marked this conversation as resolved.
Show resolved Hide resolved

Given a matrix `x` of size `n×d` and `xᵢₛ∈[0,1]ᵈ` one obtain a randomized version `y` using one the following methods
* `nested_uniform_scramble(x, b; M = M)` where `b` is the base used to scramble and `M` the number of bits in base `b` used to represent digits.
* `linear_matrix_scramble(x, base; M = M)`.
* `digital_shift(x, base; M = M)`.
* `shift(x)`.

All these functions guarantee that the resulting array will have its components uniformly distributed `yᵢₛ∼𝐔([0,1]ᵈ)` (but not independent).

### Example

Randomization of a Faure sequence with various methods.

```julia
m = 4
d = 3
b = QuasiMonteCarlo.nextprime(d)
N = b^m # Number of points
M = m

# Unrandomized low discrepency sequence
x_faure = permutedims(QuasiMonteCarlo.sample(N, d, FaureSample()))

# Randomized version
x_nus = nested_uniform_scramble(x_faure, b; M = M)
x_lms = linear_matrix_scramble(x_faure, b; M = M)
x_digital_shift = digital_shift(x_faure, b; M = M)
x_shift = shift(x_faure)
x_uniform = rand(N, d) # plain i.i.d. uniform
```

```julia
using Plots
# Setting I like for plotting
default(fontfamily="Computer Modern", linewidth=1, label=nothing, grid=true, framestyle=:default)
```

Plot the resulting sequences along dimensions `1` and `3`.

```julia
begin
d1 = 1
d2 = 3
sequences = [x_uniform, x_faure, x_shift, x_digital_shift, x_lms, x_nus]
names = ["Uniform", "Faure (unrandomized)", "Shift", "Digital Shift", "Linear Matrix Scrambling", "Nested Uniform Scrambling"]
p = [plot(thickness_scaling=1.5, aspect_ratio=:equal) for i in sequences]
for (i, x) in enumerate(sequences)
scatter!(p[i], x[:, d1], x[:, d2], ms=0.9, c=1, grid=false)
title!(names[i])
xlims!(p[i], (0, 1))
ylims!(p[i], (0, 1))
yticks!(p[i], [0, 1])
xticks!(p[i], [0, 1])
hline!(p[i], range(0, 1, step=1 / 4), c=:gray, alpha=0.2)
vline!(p[i], range(0, 1, step=1 / 4), c=:gray, alpha=0.2)
hline!(p[i], range(0, 1, step=1 / 2), c=:gray, alpha=0.8)
vline!(p[i], range(0, 1, step=1 / 2), c=:gray, alpha=0.8)
end
plot(p..., size=(1500, 900))
end
```

![Different randomization methods of the same initial set of points](img/various_randomization.svg)

Faure nets and scrambled versions of Faure nets are digital $(t,d,m)$-net ([see this nice reference by A. Owen](https://artowen.su.domains/mc/qmcstuff.pdf)). It basically means that they have strong equipartition properties.
Here we can (visually) verify that with Nested Uniform Scrambling (it also works with Linear Matrix Scrambling and Digital Shift).
You must see one point per rectangle of volume $1/b^m$.

```julia
begin
d1 = 1
d2 = 3
x = x_nus
p = [plot(thickness_scaling=1.5, aspect_ratio=:equal) for i in 0:m]
for i in 0:m
j = m - i
xᵢ = range(0, 1, step=1 / b^(i))
xⱼ = range(0, 1, step=1 / b^(j))
scatter!(p[i+1], x[:, d1], x[:, d2], ms=2, c=1, grid=false)
xlims!(p[i+1], (0, 1.01))
ylims!(p[i+1], (0, 1.01))
yticks!(p[i+1], [0, 1])
xticks!(p[i+1], [0, 1])
hline!(p[i+1], xᵢ, c=:gray, alpha=0.2)
vline!(p[i+1], xⱼ, c=:gray, alpha=0.2)
end
plot(p..., size=(1500, 900))
end
```

![All the different elementary rectangle contain only one points](img/equidistribution.svg)
1,306 changes: 1,306 additions & 0 deletions img/equidistribution.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
945 changes: 945 additions & 0 deletions img/various_randomization.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 15 additions & 1 deletion src/QuasiMonteCarlo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ abstract type SamplingAlgorithm end

include("Faure.jl")
include("Kronecker.jl")
include("RandomizedQuasiMonteCarlo/conversion.jl")
include("RandomizedQuasiMonteCarlo/shifting.jl")
include("RandomizedQuasiMonteCarlo/scrambling_base_b.jl")

check_bounds(lb, ub) = all(x -> x[1] <= x[2], zip(lb, ub))
check_bounds(lb::Number, ub::Number) = lb <= ub
Expand Down Expand Up @@ -407,6 +410,13 @@ function generate_design_matrices(n, lb, ub, sampler, num_mats = 2)
[out[(j * d + 1):((j + 1) * d), :] for j in 0:(num_mats - 1)]
end

# See https://discourse.julialang.org/t/is-there-a-dedicated-function-computing-m-int-log-b-b-m/89776/10
function logi(b::Int, n::Int)
m = round(Int, log(b, n))
b^m == n || throw(ArgumentError("$n is not a power of $b"))
return m
end

export GridSample,
UniformSample,
SobolSample,
Expand All @@ -417,6 +427,10 @@ export GridSample,
GoldenSample,
KroneckerSample,
SectionSample,
FaureSample
FaureSample,
nested_uniform_scramble,
linear_matrix_scramble,
shift,
digital_shift

end # module
107 changes: 107 additions & 0 deletions src/RandomizedQuasiMonteCarlo/conversion.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""
unif2bits(x<:AbstractMatrix, b::Integer; M=32)
Return the b-adic decomposition of all element y of an array y = ∑ₖ yₖ/bᵏ a number yₖ∈[0,1[ -> [y₁, ⋯, yₘ]
"""
function unif2bits(x::AbstractArray, b::Integer; M = 32)
bits = zeros(Int, M, size(x)...)
unif2bits!(bits, x, b)
return bits
end

function unif2bits!(bits::AbstractArray, x::AbstractArray, b::Integer)
@assert all([size(bits, d + 1) == size(x, d) for d in ndims(x)]) "Bit array of size $(size(bits)) instead of (M, $(size(x)))"
for i in CartesianIndices(x)
unif2bits!(@view(bits[:, i]), x[i], b)
end
end

function unif2bits(x::AbstractArray; M = 32)
bits = BitArray(undef, M, size(x)...)
unif2bits!(bits, x, 2)
return bits
end
"""
unif2bits(y<:Real, b::Integer; M=32)
Return the b-adic decomposition y = ∑ₖ yₖ/bᵏ a number y∈[0,1[ -> [y₁, ⋯, yₘ]
"""
function unif2bits(y::Real, b::Integer; M = 32)
bits = zeros(Int, M)
unif2bits!(bits, y, b)
return bits
end
"""
unif2bits(y; M=32)
Return the binary decomposition y = ∑ₖ yₖ/2ᵏ a number y∈[0,1[ -> [y₁, ⋯, yₘ]
"""
function unif2bits(y; M = 32)
bits = BitArray(undef, M)
unif2bits!(bits, y, 2)
return bits
end

check_zero(a::Rational; kwargs...) = a == 0
#! isequal(0.18518518518518515... -1/3^2-2/3^3 , 0) Should be true but is not! It fouls the binary expansion! Hence the isapprox with a hand tuned tolerence
check_zero(a::AbstractFloat; atol = 3e-16) = isapprox(a, 0, atol = atol)

function unif2bits!(bits::AbstractVector{<:Integer}, y, b::Integer; kwargs...)
bits .= 0
for j in eachindex(bits), bb in (b - 1):-1:1
a = y - bb // b^j
if check_zero(a; kwargs...)
bits[j] = bb
break # it breaks from the nested loop (see here)[https://stackoverflow.com/questions/39796234/how-to-break-out-of-nested-for-loops-in-julia]
elseif a > 0
bits[j] = bb
y = a
end
end
end

"""
bits2unif(bits::AbstractVector{<:Integer}, b::Integer)
Convert a vector of M "bits" in base b into a number y∈[0,1[.
"""
function bits2unif(::Type{T}, bits::AbstractVector{<:Integer},
b::Integer) where {T <: Rational}
# Turn sequence of bits into a point in [0,1)
# First bits are highest order
y = zero(T)
for j in lastindex(bits):-1:1
y = (y + bits[j]) // b
end
return y
end

function bits2unif(::Type{T}, bits::AbstractVector{<:Integer},
b::Integer) where {T <: AbstractFloat}
# Turn sequence of bits into a point in [0,1)
# First bits are highest order
y = zero(T)
for j in lastindex(bits):-1:1
y = (y + bits[j]) / b
end
return y
end

function bits2unif(bits::AbstractVector{<:Integer}, b::Integer)
bits2unif(Float64, bits::AbstractVector{<:Integer}, b::Integer)
end

#? This seems faster than @evalpoly(b, $bit...)
#? bi = rand(0:2,32);
#? @btime @evalpoly(3, $bi...)
#? 500.515 ns (1 allocation: 272 bytes)
#? @btime QuasiMonteCarlo.bits2int($bi, 3)
#? 13.113 ns (0 allocations: 0 bytes)
"""
bits2int(bit::AbstractMatrix{<:Integer}, b::Integer)
Convert a vector of M "bits" in base b into an integer.
"""
function bits2int(bit::AbstractVector, b::Integer)
m = length(bit)
y = 0
for k in m:-1:1
y = y * b + bit[k]
end
return y
end
Loading