/
DIVAndrun.jl
318 lines (252 loc) · 11.6 KB
/
DIVAndrun.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
function DIVAndrun(
operatortype,
mask::BitArray{N},
pmnin,
xiin,
x,
f::Vector{T},
lin,
epsilon2;
velocity = (),
primal::Bool = true,
factorize = true,
tol = 1e-6,
maxit = 100,
minit = 0,
constraints = (),
inversion = :chol,
moddim = [],
fracindex = Matrix{T}(undef, 0, 0),
alpha = [],
keepLanczosVectors = 0,
compPC = DIVAnd_pc_none,
progress = (iter, x, r, tol2, fun, b) -> nothing,
fi0 = zeros(size(mask)),
f0 = zeros(size(f)),
alphabc = 1.0,
scale_len = true,
btrunc = [],
MEMTOFIT = 16.0,
topographyforfluxes = (),
fluxes = (),
epsfluxes = 0,
epsilon2forfractions = 0,
RTIMESONESCALES = (),
QCMETHOD = (),
coeff_laplacian::Vector{Float64} = ones(ndims(mask)),
coeff_derivative2::Vector{Float64} = zeros(ndims(mask)),
mean_Labs = nothing,
) where {N,T}
# check pmn .* len > 4
#checkresolution(mask,pmnin,lin)
@debug "alphabc $alphabc for size $(size(mask))"
pmn, xi, len = DIVAnd_bc_stretch(mask, pmnin, xiin, lin, moddim, alphabc)
# observation error covariance (scaled)
# Note: iB is scaled such that diag(inv(iB)) is 1 far from the
# boundary
# For testing this version of alphabc deactivate the other one
s = DIVAnd_background(
operatortype,
mask,
pmn,
len,
alpha,
moddim,
scale_len,
[];
btrunc = btrunc,
coeff_laplacian = coeff_laplacian,
coeff_derivative2 = coeff_derivative2,
mean_Labs = mean_Labs,
)
s.betap = 0
s.primal = primal
s.factorize = factorize
s.tol = tol
s.maxit = maxit
s.minit = minit
s.inversion = inversion
s.keepLanczosVectors = keepLanczosVectors
s.compPC = compPC
s.progress = progress
#@info "Creating observation error covariance matrix"
R = DIVAnd_obscovar(epsilon2, length(f))
# add observation constrain to cost function
#@info "Adding observation constraint to cost function"
obscon = DIVAnd_obs(s, xi, x, f, R, fracindex)
s = DIVAnd_addc(s, obscon)
# check inputs moved here to keep trace of obs location in s
if !any(mask[:])
@warn "No sea points in mask, will return NaN"
return fill(NaN, size(mask)), s
end
# add advection constraint to cost function
if !isempty(velocity)
#@info "Adding advection constraint to cost function"
velcon = DIVAnd_constr_advec(s, velocity)
s = DIVAnd_addc(s, velcon)
end
if !isempty(topographyforfluxes)
#@info "Adding integral constraints"
fluxcon = DIVAnd_constr_fluxes(s, topographyforfluxes, fluxes, epsfluxes, pmnin)
s = DIVAnd_addc(s, fluxcon)
end
if epsilon2forfractions > 0
#@info "Adding constraints on fractions"
fluxcon = DIVAnd_constr_fractions(s, epsilon2forfractions)
s = DIVAnd_addc(s, fluxcon)
end
# add all additional constrains
for i = 1:length(constraints)
#@info "Adding additional constrain - $(i)"
s = DIVAnd_addc(s, constraints[i])
end
# factorize a posteriori error covariance matrix
# or compute preconditioner
#@info "Factorizing a posteriori error covariance matrix"
DIVAnd_factorize!(s)
# @info "Solving..."
fi0_pack = statevector_pack(s.sv, (fi0,))[:, 1]
#@code_warntype DIVAnd_solve!(s,fi0_pack,f0)
fi = DIVAnd_solve!(s, fi0_pack, f0; btrunc = btrunc)::Array{T,N}
# @info "Done solving"
# iii = findfirst(x[1] .== 12.43212345678)
# if iii !== nothing
# if f[iii] !== 1.0
# @show iii
# @show f[iii]
# @show obscon.H[iii,:]
# @show (obscon.H* pack(s.sv,(fi,)))[iii]
# JLD2.@save "/tmp/test_fi.jld2" fi
# end
# end
return fi, s
end
function DIVAndrun(mask::Array{Bool,N}, args...; kwargs...) where {N}
return DIVAndrun(convert(BitArray{N}, mask), args...; kwargs...)
end
# the same as DIVAndrun, but just return the field fi
DIVAndrunfi(args...) = DIVAndrun(args...)[1]
"""
DIVAndrun(mask,pmn,xi,x,f,len,epsilon2; <keyword arguments>)
Perform an n-dimensional variational analysis of the observations `f` located at
the coordinates `x`. The array `fi` represent the interpolated field at the grid
defined by the coordinates `xi` and the scales factors `pmn`.
# Input:
* `mask`: binary mask delimiting the domain. true is inside and false outside.
For oceanographic application, this is the land-sea mask where sea is true and land is false.
* `pmn`: scale factor of the grid. pmn is a tuple with n elements. Every
element represents the scale factor of the corresponding dimension. Its
inverse is the local resolution of the grid in a particular dimension.
For example, in two dimensions, `pmn` is a tuple `(pm,pn)` where `pm` is
the inverse of the local resolution in first dimension and `pn` is the the inverse
of the local resolution in second dimension.
* `xi`: tuple with n elements. Every element represents a coordinate
of the final grid on which the observations are interpolated.
* `x`: tuple with n elements. Every element represents a coordinate of
the observations.
* `f`: value of the observations *minus* the background estimate (vector of
`m` elements where `m` is the number of observations). See also note.
* `len`: tuple with n elements. Every element represents the correlation length for a given dimension.
* `epsilon2`: error variance of the observations (normalized by the error variance of the background field). `epsilon2` can be a scalar (all observations have the same error variance and their errors are decorrelated), a vector (all observations can have a different error variance and their errors are decorrelated) or a matrix (all observations can have a different error variance and their errors can be correlated). If `epsilon2` is a scalar, it is thus the *inverse of the signal-to-noise ratio*.
# Optional input arguments specified as keyword arguments
* `velocity`: velocity of the advection constraint. It is a tuple of n arrays and
each array represents a single velocity component. The individual array should have the
same size as the final grid. The first (second,..) element of the velocity
is the velocity compomenent along the first (second,...) dimension.
The `velocity` has the units of a length-scale. If this parameter is derived
from ocean currents, then the later must be multiplied by a factor
(to be determined for example by cross-validation) and this factor has the units of a time-scale.
The default is no-advection constraint.
* `alpha`: alpha is vector of coefficients multiplying various terms in the
cost function. The first element multiplies the norm.
The other i-th element of alpha multiplies the (i+1)-th derivative.
Per default, the highest derivative is m = ceil(1+neff/2) where neff is the
effective dimension of the problem (the number of dimensions with a nonzero
correlation length) and `ceil` is the ceiling function (rounding up).
The values of alpha is the (m+1)th row of the Pascal triangle:
m=0 1
m=1 1 1
m=1 1 2 1 (n=1,2)
m=2 1 3 3 1 (n=3,4)
...
* `constraints`: a structure with user specified constraints (see `DIVAnd_addc`).
* `moddim`: modulo for cyclic dimension (vector with n elements).
Zero is used for non-cyclic dimensions. One should not include a boundary
zone (sometimes called a ghost zone or halo) for cyclic dimensions.
For example if the first dimension
is cyclic, then the grid point corresponding to `mask[1,j]` should be
between `mask[end,j]` (left neighbor) and `mask[2,j]` (right neighbor).
* `fracindex`: fractional indices (n-by-m array). If this array is specified,
then x and xi are not used.
* `inversion`: direct solver (`:chol` for Cholesky factorization), an
interative solver (`:pcg` for preconditioned conjugate gradient [1]) can be
used or `:cg_amg_sa` for a multigrid method with preconditioned conjugate
gradient. The two last methods are iterative methods who a controlled by
the number of iterations `maxit` and the tolerance `tol`.
* `compPC`: function that returns a preconditioner for the primal formulation
if inversion is set to `:pcg`. The function has the following arguments:
fun = compPC(iB,H,R)
where iB is the inverse background error covariance, H the observation
operator and R the error covariance of the observation. The function `compPC` returns the
preconditioner `fun(x,fx)` computing fx = `M \\ x` (the inverse of M times x)
where `M` is a positive defined symmetric matrix [1].
Effectively, the system E⁻¹ A (E⁻¹)ᵀ (E x) = E⁻¹ b is solved for (E x) where E Eᵀ = M.
Ideally, M should this be similar to A, so that E⁻¹ A (E⁻¹)ᵀ is close to the identity matrix.
* `fi0`: starting field for iterative primal algorithm (same size as `mask`).
* `f0`: starting field for iterative dual algorithm (same size as the observations `f`).
* `operatortype`: Val{:sparse} for using sparse matrices (default) or Val{:MatFun} or using functions
to define the constrains.
* `scale_len`: true (default) if the correlation length-scale should be scaled
such that the analytical
kernel reaches 0.6019072301972346 (besselk(1.,1.)) at the same distance
than in 2D. The kernel behaves thus similar to
the default kernel in two dimensions (alpha = [1,2,1]).
* `alphabc`: numerical value defining how the last grid points are stretched outward.
If `alphabc` is 1, the default value mimics an infinite domain.
To have previous behaviour of finite domain use alphabc equal to `0`.
* `btrunc`: if provided defines where to truncate the calculation of the
covariance matrix B. Only values up and including alpha[btrunc] will be
calculated. If the iterative solution is calculated, the missing terms will
be calculated on the fly during the conjugate gradient calculations.
Default value is none and full covariance calculation.
# Output:
* `fi`: the analysed field
* `s`: a structure with an array `s.P` representing the analysed error covariance
# Note:
If zero is not a valid first guess for your variable (as it is the case for
e.g. ocean temperature), you have to subtract the first guess from the
observations before calling DIVAnd and then add the first guess back in.
# Example:
see `DIVAnd_simple_example.jl`
# References
[1] [The preconditioned conjugate gradient method](https://en.wikipedia.org/w/index.php?title=Conjugate_gradient_method&oldid=761287292#The_preconditioned_conjugate_gradient_method)
"""
function DIVAndrun(
mask::BitArray,
pmnin,
xiin,
x,
f::Vector{T},
lin,
epsilon2;
operatortype = Val{:sparse},
kwargs...,
) where {T}
return DIVAndrun(operatortype, mask, pmnin, xiin, x, f, lin, epsilon2; kwargs...)
end
# Copyright (C) 2008-2017 Alexander Barth <barth.alexander@gmail.com>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, see <http://www.gnu.org/licenses/>.
# LocalWords: fi DIVAnd pmn len diag CovarParam vel ceil moddim fracdim