Skip to content

Commit

Permalink
Define a custom type for the random field
Browse files Browse the repository at this point in the history
With this change we also take advantage of a new change introduced in
GaussianRandomFields v2.1.1 that allows us to reduce memory allocations when
sampling.  For example, with current master:

```julia
julia> using BenchmarkTools, TDAC

julia> x = 1.:200.;

julia> y = 1.:200.;

julia> grf = TDAC.init_gaussian_random_field_generator(1.0,1.0,1.0,x,y,0);

julia> f = zeros(40000);

julia> rnn = rand(160000);

julia> @Btime TDAC.sample_gaussian_random_field!($f, $grf, $rnn);
  1.461 ms (10 allocations: 2.75 MiB)
```

With this change:

```julia
julia> using BenchmarkTools, TDAC

julia> x = 1.:200.;

julia> y = 1.:200.;

julia> grf = TDAC.init_gaussian_random_field_generator(1.0,1.0,1.0,x,y,0,true);

julia> f = zeros(40000);

julia> rnn = randn(size(grf.grf.data[1]));

julia> @Btime TDAC.sample_gaussian_random_field!($f, $grf, $rnn);
  1.366 ms (3 allocations: 128 bytes)
```

The speed-up is modest, the major benefit is from having much less memory
allocations.
  • Loading branch information
giordano committed May 14, 2020
1 parent fdd0983 commit 449593f
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 105 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"

[compat]
Distributions = "0.22, 0.23"
GaussianRandomFields = "2.1"
GaussianRandomFields = "2.1.1"
HDF5 = "0.13"
YAML = "0.4"
julia = "1.3"
Expand Down
74 changes: 3 additions & 71 deletions src/TDAC.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module TDAC

using Random, Distributions, Statistics, Distributed, Base.Threads, YAML, GaussianRandomFields, HDF5
using Random, Distributions, Statistics, Distributed, Base.Threads, YAML, HDF5

export tdac, main

Expand All @@ -10,6 +10,8 @@ include("llw2d.jl")
using .Default_params
using .LLW2d

include("noise.jl")

# grid-to-grid distance
get_distance(i0, j0, i1, j1, dx, dy) =
sqrt((float(i0 - i1) * dx) ^ 2 + (float(j0 - j1) * dy) ^ 2)
Expand Down Expand Up @@ -171,76 +173,6 @@ function get_axes(nx::Int, ny::Int, dx::Real, dy::Real)
return x,y
end

function init_gaussian_random_field_generator(params::tdac_params)

x, y = get_axes(params)
return init_gaussian_random_field_generator(params.lambda,params.nu, params.sigma, x, y, params.padding)

end

# Initialize a gaussian random field generating function using the Matern covariance kernel
# and circulant embedding generation method
# TODO: Could generalise this
function init_gaussian_random_field_generator(lambda::T,
nu::T,
sigma::T,
x::AbstractVector{T},
y::AbstractVector{T},
pad::Int) where T

# Let's limit ourselves to two-dimensional fields
dim = 2

cov = CovarianceFunction(dim, Matern(lambda, nu, σ = sigma))
grf = GaussianRandomField(cov, CirculantEmbedding(), x, y, minpadding=pad, primes=true)

end

# Get a random sample from gaussian random field grf using random number generator rng
function sample_gaussian_random_field!(field::AbstractVector{T},
grf::GaussianRandomFields.GaussianRandomField,
rng::Random.AbstractRNG) where T

field .= @view(GaussianRandomFields.sample(grf, xi=randn(rng, randdim(grf)))[:])

end

# Get a random sample from gaussian random field grf using random_numbers
function sample_gaussian_random_field!(field::AbstractVector{T},
grf::GaussianRandomFields.GaussianRandomField,
random_numbers::AbstractVector{T}) where T

field .= @view(GaussianRandomFields.sample(grf, xi=random_numbers)[:])

end

function add_random_field!(state::AbstractVector{T},
grf::GaussianRandomFields.GaussianRandomField,
rng::Random.AbstractRNG,
params::tdac_params) where T

add_random_field!(state, grf, rng, params.n_state_var, params.dim_grid)

end

# Add a gaussian random field to each variable in the state vector of one particle
function add_random_field!(state::AbstractVector{T},
grf::GaussianRandomFields.GaussianRandomField,
rng::Random.AbstractRNG,
nvar::Int,
dim_grid::Int) where T

random_field = Vector{Float64}(undef, dim_grid)

for ivar in 1:nvar

sample_gaussian_random_field!(random_field, grf, rng)
@view(state[(nvar-1)*dim_grid+1:nvar*dim_grid]) .+= random_field

end

end

function add_noise!(vec::AbstractVector{T}, rng::Random.AbstractRNG, params::tdac_params) where T

add_noise!(vec, rng, params.obs_noise_amplitude)
Expand Down
82 changes: 82 additions & 0 deletions src/noise.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using GaussianRandomFields

struct RandomField{F<:GaussianRandomField,W<:AbstractArray,Z<:AbstractArray}
grf::F
w::W
z::Z
end

function init_gaussian_random_field_generator(params::tdac_params)

x, y = get_axes(params)
return init_gaussian_random_field_generator(params.lambda,params.nu, params.sigma, x, y, params.padding, params.primes)

end

# Initialize a gaussian random field generating function using the Matern covariance kernel
# and circulant embedding generation method
# TODO: Could generalise this
function init_gaussian_random_field_generator(lambda::T,
nu::T,
sigma::T,
x::AbstractVector{T},
y::AbstractVector{T},
pad::Int,
primes::Bool) where T

# Let's limit ourselves to two-dimensional fields
dim = 2

cov = CovarianceFunction(dim, Matern(lambda, nu, σ = sigma))
grf = GaussianRandomField(cov, CirculantEmbedding(), x, y, minpadding=pad, primes=primes)
v = grf.data[1]
w = Array{complex(float(eltype(v)))}(undef, size(v))
z = Array{eltype(grf.cov)}(undef, length.(grf.pts))

return RandomField(grf, w, z)
end

# Get a random sample from gaussian random field grf using random number generator rng
function sample_gaussian_random_field!(field::AbstractVector{T},
grf::RandomField,
rng::Random.AbstractRNG) where T

field .= @view(GaussianRandomFields._sample!(grf.w, grf.z, grf.grf, randn(rng, size(grf.grf.data[1])))[:])

end

# Get a random sample from gaussian random field grf using random_numbers
function sample_gaussian_random_field!(field::AbstractVector{T},
grf::RandomField,
random_numbers::AbstractArray{T}) where T

field .= @view(GaussianRandomFields._sample!(grf.w, grf.z, grf.grf, random_numbers)[:])

end

function add_random_field!(state::AbstractVector{T},
grf::RandomField,
rng::Random.AbstractRNG,
params::tdac_params) where T

add_random_field!(state, grf, rng, params.n_state_var, params.dim_grid)

end

# Add a gaussian random field to each variable in the state vector of one particle
function add_random_field!(state::AbstractVector{T},
grf::RandomField,
rng::Random.AbstractRNG,
nvar::Int,
dim_grid::Int) where T

random_field = Vector{Float64}(undef, dim_grid)

for ivar in 1:nvar

sample_gaussian_random_field!(random_field, grf, rng)
@view(state[(nvar-1)*dim_grid+1:nvar*dim_grid]) .+= random_field

end

end
65 changes: 34 additions & 31 deletions src/params.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,39 @@ tdac_params()
Parameters for TDAC run. Arguments:
* `nx` : number of grid points in the x direction
* `ny` : number of grid points in the y direction
* `dim_grid` : Grid size
* `dim_state` : State vector size (height, velocity_x, velocity_y) at each grid point
* `dx` : Distance (m) between grid points in the x direction
* `dy` : Distance (m) between grid points in the y direction
* `nobs` : Number of observation stations
* `station_separation` : Distance between stations in station_dx/dx grid points
* `station_boundary` : Distance between bottom left edge of box and first station in station_dx/dx grid points
* `station_dx` : Scaling factor for distance between stations in the x direction
* `station_dy` : Scaling factor for distance between stations in the y direction
* `ntmax` : Number of time steps
* `dt` : Time step length (unit?)
* `verbose` : Flag to write output
* `output_filename` : Name of output file
* `state_prefix` : Prefix of the time slice data groups in output
* `title_da` : Suffix of the data assimilated data group in output
* `title_syn` : Suffix of the true state data group in output
* `title_grid` : Name of the grid data group in output
* `title_params` : Name of the parameters data group in output
* `ntdec` : Number of time steps between output writes in output
* `nprt` : Number of particles for particle filter
* `da_period` : Number of time steps between particle resamplings
* `rr` : Length scale for covariance decay
* `inv_rr` : Inverse of length scale, stored for performance
* `source_size` : Initial condition parameter
* `bathymetry_setup` : Bathymetry set-up.
* `lambda` : Length scale for Matérn covariance kernel (could be same as rr)
* `nu` : Smoothess parameter for Matérn covariance kernel
* `sigma` : Marginal standard deviation for Matérn covariance kernel
* `padding` : Min padding for circulant embedding gaussian random field generator
* `nx::Int` : Number of grid points in the x direction
* `ny::Int` : Number of grid points in the y direction
* `n_state_var::Int`:
* `dim_grid::Int` : Grid size
* `dim_state::Int` : State vector size (height, velocity_x, velocity_y) at each grid point
* `dx::AbstractFloat` : Distance (m) between grid points in the x direction
* `dy::AbstractFloat` : Distance (m) between grid points in the y direction
* `nobs::Int` : Number of observation stations
* `station_separation::Int` : Distance between stations in station_dx/dx grid points
* `station_boundary::Int` : Distance between bottom left edge of box and first station in station_dx/dx grid points
* `station_dx::AbstractFloat` : Scaling factor for distance between stations in the x direction
* `station_dy::AbstractFloat` : Scaling factor for distance between stations in the y direction
* `ntmax::Int` : Number of time steps
* `dt::AbstractFloat` : Time step length (unit?)
* `verbose::Bool` : Flag to control whether to write output
* `output_filename::String` : Name of output file
* `state_prefix::String` : Prefix of the time slice data groups in output
* `title_da::String` : Suffix of the data assimilated data group in output
* `title_syn::String` : Suffix of the true state data group in output
* `title_grid::String` : Name of the grid data group in output
* `title_params::String` : Name of the parameters data group in output
* `ntdec::Int` : Number of time steps between output writes in output
* `nprt::Int` : Number of particles for particle filter
* `da_period::Int` : Number of time steps between particle resamplings
* `rr::AbstractFloat` : Length scale for covariance decay
* `inv_rr::AbstractFloat` : Inverse of length scale, stored for performance
* `source_size::AbstractFloat` : Initial condition parameter
* `bathymetry_setup::AbstractFloat` : Bathymetry set-up.
* `lambda::AbstractFloat` : Length scale for Matérn covariance kernel (could be same as rr)
* `nu::AbstractFloat` : Smoothess parameter for Matérn covariance kernel
* `sigma::AbstractFloat` : Marginal standard deviation for Matérn covariance kernel
* `padding::Int` : Min padding for circulant embedding gaussian random field generator
* `primes::Int`: Whether the size of the minimum circulant embedding of the covariance matrix can be written as a product of small primes (2, 3, 5 and 7). Default is `false`.
* `obs_noise_amplitude`: Multiplier for noise added to observations of the true state
* `random_seed` : Seed number for the pseudorandom number generator
"""
Expand Down Expand Up @@ -82,6 +84,7 @@ Base.@kwdef struct tdac_params{T<:AbstractFloat}
nu::T = 2.5
sigma::T = 1.0
padding::Int = 100
primes::Bool = true
obs_noise_amplitude::T = 1.0

random_seed::Int = 12345
Expand Down
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ end
# Test gaussian random field sampling
x = 1.:2.
y = 1.:2.
grf = TDAC.init_gaussian_random_field_generator(1.0,1.0,1.0,x,y,0)
grf = TDAC.init_gaussian_random_field_generator(1.0,1.0,1.0,x,y,0,false)
f = zeros(4)
rnn = [9.,9.,9.,9.]
rnn = [9.0 9.0; 9.0 9.0]
TDAC.sample_gaussian_random_field!(f,grf,rnn)
@test f [16.2387054353321, 5.115956753643808, 5.115956753643809, 2.8210669567042155]

Expand Down

0 comments on commit 449593f

Please sign in to comment.