From 449593f0f7418d937fb9c2e9f10772cb8b2afc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Thu, 14 May 2020 14:10:24 +0100 Subject: [PATCH] Define a custom type for the random field 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. --- Project.toml | 2 +- src/TDAC.jl | 74 ++----------------------------------------- src/noise.jl | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ src/params.jl | 65 ++++++++++++++++++++------------------ test/runtests.jl | 4 +-- 5 files changed, 122 insertions(+), 105 deletions(-) create mode 100644 src/noise.jl diff --git a/Project.toml b/Project.toml index 315af79d..b624cd66 100644 --- a/Project.toml +++ b/Project.toml @@ -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" diff --git a/src/TDAC.jl b/src/TDAC.jl index c3c8b174..e8e50e7d 100644 --- a/src/TDAC.jl +++ b/src/TDAC.jl @@ -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 @@ -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) @@ -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) diff --git a/src/noise.jl b/src/noise.jl new file mode 100644 index 00000000..a70ee862 --- /dev/null +++ b/src/noise.jl @@ -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 diff --git a/src/params.jl b/src/params.jl index ea9aaf3d..fd311471 100644 --- a/src/params.jl +++ b/src/params.jl @@ -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 """ @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index 996675e4..a8a7e6f5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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]