Model fitting is often approached as an optimization problem where the sum of the model errors are minimized by optimizing the model parameters. If some of the model parameters are non-linear, then a non-linear optimization algorithm must be used. This can be inefficient if some of the parameters are, in fact, linear.
The Varpro algorithm recasts the problem so that only the nonlinear parameters need to be considered by the nonlinear optimizer. For more details see the references below and the embedded docs in the source code.
This Julia code is a translation and extension the of the Matlab code that can be found here. The extensions involve handling complex inputs and a complex model (although the optimization objective function remains real since the objective is essentially the L2 norm of the residual (error) vector).
The best way to learn how to use Varpro is to read reference [4]. The usage in Julia differs somewhat from the MATLAB version. With this version, we first set up a FitContext by calling the constructor as in the following
ctx = FitContext(y, t, w, x_init, n, ind, f_exp, g_exp)
All of these are required parameters. The vector y is the data we wish to fit sampled at the times t (or whatever is the independent variable). The vector w is a weight vector for selectively weighting the data, all ones is usually a good first step. The vector x_init is the starting guess for the nonlinear parameters. Note that both y and x_init can be either real or complex, but they both must share the same type. The integer n is the number of basis functions, which is also the number of linear parameters. The matrix ind specifies the structure of the dphi matrix (see [4]). The functions f_exp and g_exp calculate the phi and dphi matrices.
The following is a complete example of fitting the H1 strain ringdown values of the recently discovered gravity wave GW150914 [5].
using Varpro
using PyPlot
using DelimitedFiles
function f_exp(alpha, ctx)
for j = 1:ctx.n
for i in 1:ctx.m
ctx.phi[i, j] = exp(-alpha[j] * ctx.t[i])
end
end
ctx.phi
end
function g_exp(alpha, ctx)
for i in 1:ctx.m
ctx.dphi[i, :] = -ctx.t[i] * ctx.phi[i, :]
end
ctx.dphi
end
""" Fit n complex exponentials to the measured data """
function exp_fit(n, y, t)
w = ones(length(t))
ind = [collect(1:n)'; collect(1:n)']
x_init = complex.(0.1*rand(n), 2.0*rand(n))
ctx = FitContext(y, t, w, x_init, n, ind, f_exp, g_exp)
(alpha, c, wresid, resid_norm, y_est, regression) = varpro(ctx)
end
function main()
h1 = readdlm("h1_whitened.txt")
t = h1[:, 1]
y = complex.(h1[:, 2]) # must be complex to match x_init
x, c, r, r_norm, y_est, reg = exp_fit(6, y, t)
println("Norm of residual error: ", r_norm)
plot(t, real(y), "o", label="measured H1 strain")
plot(t, real(y_est), label="modeled H1 strain")
xlabel("Time")
ylabel("Strain")
title("H1 Ringdown Model")
legend(loc="upper right")
savefig("modeled_GW150914_strain.png")
end
main()
The above code produces the following figure:
Varpro depends upon having an underlying nonlinear optimization algorithm. Here we default to use NL2sol a venerable FORTRAN code from the early 1980s. That code is compiled to NL2sol_jll which is then further wrapped by NL2sol.jl which is used here. The code and theory behind it are discussed in [6] and [7]
Only supported in Julia 1.4 and later.
If you are fitting a sum of exponentials, a common use case for Varpro, note that it can be a tricky business. If the optimzer takes a step too large in one parameter, the other parameters may be swamped and the problem doesn't have full rank at the current value of x. Varpro tries to take care of this with the SVD and then regularizing by throwing away small singular values and reducing the dimensionality of the problem. This doesn't always work.
[1] Golub, G.H., Pereyra, V.: "The differentiation of pseudoinverses and nonlinear least squares problems whose variables separate". SIAM Journal on Numerical Analysis 10, pp 413-432 (1973)
[2] Golub, G.H., Pereyra, V.: "Separable nonlinear least squares: The variable projection method and its applications". Inverse Problems 19 (2), R1–R26 (2003)
[3] Pereyra, V., Scherer, eds: "Exponential Data Fitting and its Applications" Bentham Books, ISBN: 978-1-60805-048-2 (2010)
[4] Dianne P. O'Leary, Bert W. Rust: "Variable projection for nonlinear least squares problems". Computational Optimization and Applications April 2013, Volume 54, Issue 3, pp 579-593 Available here
[5] B. P. Abbott el. al. "ASTROPHYSICAL IMPLICATIONS OF THE BINARY BLACK HOLE MERGER GW150914" The Astrophysical Journal Letters, Volume 818, Number 2
[6] J.E. Dennis, D.M. Gay, R.E. Welsch, "An Adaptive Nonlinear Least-Squares Algorithm", ACM Transactions on Mathematical Software (TOMS), Volume 7 Issue 3, Sept. 1981, pp 348-368, ACM New York, NY, USA see here
[7] J.E. Dennis, D.M. Gay, R.E. Welsch, "Algorithm 573: NL2SOL—An Adaptive Nonlinear Least-Squares Algorithm", ACM Transactions on Mathematical Software (TOMS), Volume 7 Issue 3, Sept. 1981, pp 369-383, ACM New York, NY, USA see here